随着滴滴出行业务的不断拓展,客户端开发团队人数激增,代码量急剧膨胀,同时开发人员存在物理地域的不同(北京、上海、杭州),在如此前提下,如何保证稳定高效的协同开发?如何才能高效的进行代码复用,持续集成和按需集成?工程涵盖业务组件繁多,代码量巨大,单次构建耗时长,一个组件出错就要重新再来,如何避免这种情况?
滴滴出行 APP 架构组架构师王涛根据滴滴出行乘客端架构演进历程以及实践经验,在 2017 年 ArchSummit 全球架构师峰会北京站上分享了滴滴出行对以上问题的解决方案,并介绍了滴滴出行主推的构建平台的设计思路、现状和未来的规划,以下是分享的全部内容。
滴滴出行 1.0 版本功能比较简单,就是一个供需的匹配工具,业务比较单一,开发者也比较少,所有代码都在一个工程里面。
到了 2.0 版本,功能上仍然只有出租车一条业务线,但是产品形态发生了一个变化,变成一个交易平台,而代码仍然是以大锅饭的方式进行开发和集成。
到了 3.0 版本,产品出现多元化,新增了专车业务线,代码出现了初步的膨胀,但是因为这个时期只有两个业务线,集成构建仍然不是什么大问题。
在 4.0 版本,我们遇到了很多挑战和痛点,这个阶段被定义为平台化整合阶段,由于在这个时期竞争对手的出现导致应用需求需很快上线。时间短、业务重,快速上线造成了工程代码极度膨胀,高度冗余。在这个时间段滴滴出行和快的打车又进行了合并,开发者出现跨地域分布,因此跨地域的团队协作,资源分散在各地,团队间沟通和交流都出现了很大的问题。
对此,在每次发布新版本之前,我们都会预留一周时间进行代码合并。在这个时期我们成立了一个交流群,叫协同发版群,大家都在群里沟通代码要怎么合并的问题,而且这个时期的 QA 测试遇到了很多的灾难性问题,因为代码是高度耦合的,任何人只要修改了任何一条代码,都要进行一次回归测试,这个时期让我们很头疼,效率很低。
随着业务线增加,代码功能开发时间和代码合并时间的关系如下图的柱状图,功能开发时间的曲线是稳步上升的,但是代码合并所需时间出现了指数级的上升,这是失衡的。因此在架构上我们向组件化迁移,针对每个组件推进了组件工程化,使每一个组件都有一个自己独立的工程,解决了协同合作的问题,提升了开发效率,此时的架构也升级到了 5.0 阶段。
在平台组件化的阶段,随着后续的业务迭代和功能开发,乘客端里的组件有了高达 140 多个,同时出现了新问题,我们集成了这么多的组件,如何进行有效管理上升为一个很大的问题。组件越多,原代码越多,编译就需要很长的时间,这对于开发者来说,体验是非常不好的。
然后我们对构建系统进行了再次升级,推出了 OneTool 工具,标准化壳工程改造,是我们的壳工程经历了合久必分,分久必合的过程,以及基于 Cocoapods 的预编译优化。
这个是我们目前的基本架构图,大家可以看到下面是基础平台的部分,包括了一些通用的运行时组件,一些基础组件还有业务组件,在平台之上会有不同的 APP,每个不同的 APP 加入自己的个性化组件,包装成一个 APP。
由于滴滴出行有十多条业务线,构建系统面临一个最复杂的环境。因此我们首先要做的就是标准化壳工程。
在 4.0 阶段,我们推进了组件工程化,在当时很好地解决了我们的问题,提升了开发效率,但随着版本迭代,也出现了一些不适应的问题:
由于每个组件都有独立的工程,各自就会加入自己的配置,以及加入自己特异性的脚本,这样就会出现一个问题,组件在自己的工程里面可以正常使用,但是集中到总工程里面就无法使用了,导致无法集成。
组件工程化对新入职的开发者并不是十分友好,任何一个开发者加入一个公司之后可能负责 4-5 个组件进行开发,每个组件都有独立的工程,他们要同时接触 5 个不同的工程的创建和管理,对他们来说初始的挑战很大。
还有 Debug 的问题, Pods 文件夹下的代码是没有版本控制的,因此在我们修改了一些代码之后,这些修改的代码没有办法直接提交到组件仓库里的,如果 BUG 比较严重的话,修改的代码可能比较多,某一个开发者可能花了一天时间把错误解决了,但是他想把修改的代码往自己的组件库导入的时候,可能已经把修改过的部分给忘了。
因此,我们推翻了组件工程化,开始创建一个标准化的壳工程。
上图是标准壳工程的大概模型图,可以看到它分为两部分:一是本地的部分,这一部分开发者可以进行自由改造,还有一部分是同步的 Workspace,这是不允许开发者进行修改的。
如果组件的开发者要想开发组件也很简单,只需把自己的组件克隆到 Workspace 里面,将自己的组件以 LOCAL PODS 方式加载进来就可以了。
标准壳工程的创建过程分为六个步骤:创建一个本地的 Workspace,克隆同步的 Workspace 到本地的 Workspace 里面,创建一个基本的配置文件 ,第四步从 Workspace 里面拷贝一些文件到本地的 Workspace 里面,生成 Podfile,执行 pod install,最终生成如图所示的本地的目录结构。
上图中间这一部分就是本地的 Workspace,然后选中的右边是同步的 Workspace,这其中有一些文件和文件夹是相同的,这就是第四步操作的结果。
以上的步骤还是很烦琐的,因为创建这样一个工程需要六步,每一步都有很小的细节,如果有程序员自己一步一步手工操作的话,非常容易出错,但这却是我们进行工具自动化的基础。
对于工具自动化,我们自研了 OneTool,这是一套命令行工具,最初目的是使 iOS 开发构建更加简单快捷。下图就是基本的命令,其中就包括了前面的创建一个标准壳工程。
OneTool 这个工具已经是整个构建系统的桥梁,目前整个构建系统中任何一个环节都离不开 OneTool 的存在。
OneTool 包括两个部分,第一部分是 One Commands,这是由开发者主动调用的。还有另外一个部分是 OnePods,它是由 Cocoapods 主动调用的。
OneTool 很好地解决了开发者使用繁琐步骤创建工程的问题,减少了操作,但它毕竟是一个命令行的工具,所以它并不是很友好,同时每一个开发者的电脑环境是不能保证相同,在使用过程中就会出现各种各样的问题。
所以,我们基于 OneTool 开发了一个 GUI 工具,就是 OneApp,它把所有 OneTool 的命令都集中到这个界面上,降低了使用门槛。OneApp 除了基本功能以外还提供了对所有的 Workspace 统一管理的功能,图中左侧展示出了所有的 Workspace,右面的主要区域是展示选则的 Workspace 的 Pods 的基本情况,它可以对任何一个 pod 进行任何信息的修改。
OneApp 的工具推出之后,在公司内部将成本基本降低到了零,在最初我们没有工具,仍然是以原始创建工程的方式创建工程时,曾经发生过这样的事情,公司一个新同学入职的时候,第一件事就是配个可以编译的 Workspace,由于我们的 Workspace 非常复杂,他配置了一个星期也没能将工程 run 起来,最后那个同学直接离职了,但是现在这样的问题已经不存在了。使用这个工具之后,新同学在入职十分钟之内就可以创建出一个可以运行的 Workspace,目前除了 iOS 开发者在使用它以外,QA 和 PM 也在 使用用,PM 为什么会用?前一阵 iPhoneX 出来之后,我们要做适配,PM 可以拿出这个工具,通过简单操作配出一个工程,就可以看到适配之后的结果。
如果使用组件化的架构体系,首先尽早建立标准化壳工程,这样会减少很多的负担。其次要统一组件库配置文件规范,也就是 podspec 文件,还要善于使用 xcconfig 文件。最后依赖配置文件的权限要控制好,一旦一个开发者误操作将文件进行了修改提交上去,可能就会造成灾难性后果。自动化和工具化是以上工作最好的实现方案,因为手工去做的话很可能会出现一些问题。
滴滴出行的工程初次编译时间有着将近一个小时,假设有 300 多个开发者,每个人编一次工程,就需要 300 小时,所以编译时间优化是势在必行的任务。相同的工程在使用预编译优化之后,一次编译时间缩短到 5 分钟,带来了效率上的极大提升。
预编译优化的实现方案,第一步使用一个纯源码的工程,编译出静态库文件,通过 vendored_libraries 加载静态库文件,找到 podspec 文件修改一些参数,以及文件的地址,最后将修改之后的文件与静态库放在一起打包,就制作好了预编译包,将这个包上传到服务器,使用时根据需要将这个预编译包下载下来,集成到工程中,就完成预编译包的创建和使用。
我们的预编译包也有自己的发布流程,每个组件的开发者开发好以及本地测试 OK 之后他们会将预编译包进行提交触发一下的流程。
当源码中有预编译宏的话就会出现问题,预编译是将代码提前进行编译,最后是使用静态包的,由于我们编译的环境和运行环境很可能是不一样的。比如 if_has_include 宏,我们在编译的时候有某个头文件,实际运行中没有头文件,这样会直接造成功能的缺失或者 crash。
如果组件中定义了 subspec,前面的预编译包的创建流程是不 ok 的,做出来的包是不能使用的。滴滴出行最早是不支持 subspec 的,一旦遇到 subspec 这个库只能以源码方式进行集成,后来经过不断的踩坑填坑过程,如今完美的完成了 subspec 的支持。
使用结构体和联合体,这种在使用预编译的时候也会有一个隐讳的问题,举个例子有两个库:地图库、依赖地图的导航库,地图库里面定义了 C 语言的结构体,导航库在刚开始使用时,可以完美地运行,当在某个版本地图库将结构体进行修改,新增一个字段,没有通知导航库,这时候在编译阶段因为可以正常编译通过,因为导航库并没有使用这个字段,但是一旦到了线上就会直接造成 crash。因为线上运行的时候内存结构不一致,就会造成错误。在这里有一个避免的方式,大家可以参考一下系统库的结构体的实现方式,由库自身提供创建结构体的方法,比如 CGRectMake,使用这些方法创建结构体就可以解决使用时内存结构不一致的问题,还有如果 podspec 编写不规范,随意写的话,修改出来的包也可能不能正常使用。
最开始的时候用的方法比较低端,人肉 merge 效率极低、错误后置,之后我们接入了 jenkins,在每个开发者提交了一个 tag 之后,会进行一次编译检查,如果检查失败的话就不提交 tag。因为 Jenkins 每次只能添加一个组件,每个组件都要创建单独的 job,最后 Jenkins job 数量就爆炸了。如果多个库相互之间有依赖的话,一次需要集成三到四个库,而 Jenkins 自身一个 job 只支持修改一个组件库,这时候 Jenkins 就处理不了,又会回到手工修改这种方式。由于人肉手工修改没有检查的保证,很可能出现错误。
那么有没有更好的方式进行持续构建和集成呢?有!那就是集成构建平台,首先集成构建平台和 Jenkins 相比有着更加友好的用户界面,另一个是集成构建平台支持各种自定义方式去集成构建,开发者可以想集成几个组件就集成几个,针对任何一个组件库都可以进行自定义的设置,包括预编译包的属性设置等。
另外集成构建平台相比 Jenkins 会增加一个统一管理所有组件的功能,借助这个功能,在基础平台上通过不同的组件的集成就会包装出不同的 APP,即支持多 APP 的集成,集成构建平台还有一个功能就是会对每次集成构建创建快照,根据需要的时候能进行恢复,如果一个历史线上的包出现问题的话,可以快速恢复那个包的代码状态,进行调试,集成构建平台的投入使用是人肉手工效率的 3 倍左右。
任何一个开发者提交了代码之后,会触发集成、创建工程、克隆 pod、打新 tag、更新依赖、集成工程、编译、集成失败,会修改,如果成功:
预编译流程;
将修改之后的依赖配置文件提交到仓库里,最后通知给用户告诉他这次集成是成功的。
目前我们持续集成的过程就是在不停地跑这样的循环,完成整个公司所有 APP 的持续集成过程。
上图是集成构建平台的界面,他有着比 Jenkins 更友好的界面,左面是支持的不同端和不同的 APP,右面是每次集成构建的历史,上面还有一些 APP 的管理和组件的管理等功能。
我们集成构建平台的设计思路,主要是为了解决 Jenkins 的一些问题才推出的,首先它支持各种自定义的需求,以及要支撑多 APP 的集成,还要统一管理公司内部所有的 SDK,不能只针对 iOS 一个端使用,一定要多端通用。最后集成构建平台的各个用户权限一定要有明确的划分。
公司内部除了集成构建平台之外还有其他的平台,比如说测试、发版、数据统计平台,这些平台都是相互独立的状态,我们推出集成构建平台之后会将这些平台全部打通,从最初的构建情况,到最后直接上线、数据收集,完全都是打通的。目前,集成构建平台已经上线一个多月,已经有数次稳定发版,已经接入了滴滴出行乘客端和企业端,打通平台的有发版平台和 OneTool。
未来要做的就是要流量全切,现在仍是灰度发布状态。一部分使用集成构建平台,另外一部分做老的 Jenkins 方式,未来要接入公司内所有端,以及和其他平台进行打通,未来开发的重点是要做 iOS 和安卓的一致性开发,目前在这两个端上,整体开发流程是一致的,但在某些细节上 iOS 和安卓还有些区别,比如说 iOS 天生是支持源码的,经过修改之后才支持了预编译的包,而安卓天生是支持预编译的包,在修改了之后才支持了源码的编译,未来我们就要做到一致性开发。
王涛,现任滴滴出行 APP 架构组架构师,在滴滴之前曾就职于百度等知名互联网公司,有多年移动开发经验。专注于移动开发领域,热爱开源,善于解决技术难题。在 Github 上维护有多个开源项目。
加入滴滴后,参与并完成了 iOS 动态化系统的设计与开发,使滴滴出行成为鲜有动态化能力的公司之一。随后又主导了整个 iOS 构建优化与流程标准化建设,开发了一系列集成构建效能工具。设计并推动整个集成构建平台的开发与上线使用。
移动开发前线
「移动开发前线」是 InfoQ 旗下关注移动开发技术的垂直社群。投稿请发邮件到 editors@cn.infoq.com,注明“移动开发前线投稿”。
视频、3D、机器学习、算法工程化、IOT、智能硬件等新的命题正在被提出,丰富的内容生态以及内容形式在给传统开发的模式和线上的稳定性带来了新的挑战,而怪物级 APP 的移动开放生态也使得越来越多的移动开发者开始围绕着生态进行开发。QCon 北京 2018 会挑选部分话题来给大家分享,包括像跳一跳这样的全民小游戏的开发经验和思路,希望可以启发大家关于新移动技术的发展方向。
目前八折报名最后一周,立减 1360 元,有任何问题欢迎咨询购票经理 Hanna,电话:15110019061,微信:qcon-0410。