@qinyun
2018-03-12T17:49:21.000000Z
字数 5419
阅读 2585
未分类
随着滴滴出行业务的不断拓展,客户端开发团队人数激增,代码量急剧膨胀,开发人员物理地域不同(北京、上海、杭州),在如此前提下,如何保证稳定高效的协同开发?如何才能高效的进行代码复用,持续集成和按需集成?工程涵盖业务组件繁多,代码量巨大,单次构建耗时长,一个组件出错就要重新再来,如何避免这种情况?
滴滴出行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的问题,在我们修改了一些代码之后,这些修改的代码没有办法直接提交到组件仓库里的,如果BUG比较严重的话,修改的代码可能比较多,某一个开发者可能花了一天时间把错误解决了,但是他想把修改的代码往自己的组件库导入的时候,已经把修改过的部分给忘了。
因此,我们推翻了组件工程化,开始创建一个标准化的壳工程。
上图是标准壳工程的大概模型图,可以看到它分为两部分:一是本地的部分,这一部分开发者可以进行自由改造,还有一部分是同步的Workspace,这是不允许开发者进行修改的。
如果组件的开发者要想开发组件也很简单,只需把自己的组件克隆到Workspace里面,将自己的组件以LOCAL PODS方式加载进来就可以了。
标准壳工程的创建过程分为六个步骤:创建一个本地的Workspace,接下来是克隆同步的Workspace到本地的Workspace里面,创建一个基本的配置文件 ,第四步从Workspace里面拷贝一些文件到本地的文件里面,然后生成如图所示的本地的目录结构。
上图中间这一部分就是本地的Workspace,然后选中的右边是同步的Workspace,这其中有一些文件和文件夹是相同的,这就是第四步操作的结果,从同步Workspace里面拷贝一些必备的Workspace到工程里面,同时Podfile就被拆成了两部分。
以上的步骤还是很烦琐的,因为创建这样一个工程需要六步,每一步都有很小的细节,如果有程序员自己一步一步手工操作的话,非常容易出错,但这却是我们进行工具自动化的基础。
对于工具自动化,我们自研了OneTool,这是一套命令行工具,最初目的是使iOS开发构建更加简单快捷。下图就是基本的命令,其中包括了前面创建的一个基本标准壳工程。
OneTool这个工具已经是整个构建系统的桥梁,目前整个构建系统中任何一个环节都离不开OneTool的存在。
OneTool包括两个部分,第一部分是OneCommands,这是由于开发者主动调用的。还有另外一个部分是OnePods,下面就是pod one命令使用的情景。
OneTool很好地解决了开发者复杂问题的情况,减少了操作,但它毕竟是一个命令行的工作,所以它并不是很友好,每一个开发者的电脑环境是不能保证的,在过程中会出现了各种各样的问题。
所以,我们基于OneTool开发了一个界面,就是OneAPP,把所有OneTool的命令都集中到这个界面上,降低了使用门槛。OneAPP除了基本功能以外还提供了对所有的Workspace统一管理的功能,上图展示出了所有的Workspace,右面的主要区域是展示选用的Workspace的Pod的基本情况,它可以对任何一个pod进行版本的修改。
OneAPP的工具退出之后,在公司里面的成本降到0,最初我们没有工具的时候,仍然是以原始创建工程的方式,曾经我们公司一个新同学入职的时候,第一件事就是配个可以编译的Workspace,由于我们的Workspace非常复杂,最后那个同学直接离职了,但是现在这样的问题已经不存在了。使用这个工具之后,新同学在入职十分钟之内就可以创建出一个可以运行的Workspace,目前除了iOS开发者在使用它,QA也在用,PM为什么会用?前一阵苹果X出来之后,我们要做适配,PM可以拿出这个工具,通过简单操作配出一个工程,就可以看到适配之后的结果。
如果做组件化的架构体系,第一步首先尽早建立标准化壳工程,这样会减少很多的负担。统一组件库配置文件规范,也就是podspec文件,还要善于使用xcconfig文件。最后依赖配置文件的权限要控制好,一旦一个开发者误操作将文件进行了修改提交上去,就会造成灾难性后果,自动化和工具化是以上工作最好的实现方案,手工去做的话可能会出现一些问题。
滴滴出行有300多个开发者,每个人都要编一次工程,一次就需要300小时,所以优化是势在必行的任务。相同的工程在使用预编译优化之后,一次编译时间缩短到5分钟,带来了效率上的提升。
预编译优化的实现方案,第一步,vendored_libraries 加载静态库 件,找到一些参数,以及文件的地址,对文件进行加以修改,最后将修改之后的文件放在一起打包,就制作好了预编译包,将这个包上升到服务器,根据需要将这个预编译包下载下来,集成到工程中,就完成预编译包的创建和使用过程。
我们的预编译包也有自己的发布流程,每个组件的开发者开发好或者测试OK之后他们会将预编译包进行提交。
当源码中有预编译宏的话就会出现问题,预编译是将代码提前进行编译,最后是使用静态包的,我们编译的环境和运行环境是不一样的。我们在编译的时候有某个头文件,实际运行中没有头文件,这样会直接造成功能的缺失或者其他问题。
如果组件中定义了subspec,前面预编译包的创建流程是不ok的,做出来的包也是不能使用的。滴滴出行最早是不支持subspec的,一旦遇到subspec这个库只能以源码方式进行集成,经过不断的填坑过程,完美完成了subspec的支持与创建。
使用结构体和联合体,这种包在使用预编译的时候也会有一个隐讳的问题,两个库:地图库、依赖地图的导航库,地图库里面定义了C语言的结构体,导航库在刚开始使用时,可以完美地运行,当在某个版图地图库将结构体进行修改时,新增一个字段,没有通知导航库,这时候在编译阶段因为可以正常编译通过,导航库并没有使用这个字体,一旦到了线上就会直接造成错误。因为线上运行的时候内存结构不一致,就会造成错误。在这里有一个避免的方式,这个库定义的时候,大家可以参考一些结构方法,由库自身创建结构体,就可以解决使用时内存结构不一致的问题,还有如果podspec编写不规范,随意写的话,修改出来的包可能不能正常使用。
最开始的时候用的方法比较低端,人肉merge效率极低、错误后置,之后我们介入了jenkins,在每个开发者提交了一个tag之后,会进行一次编译检查,如果检查失败的话就不提交tag,每个组件都要创建单独的jok,jenkins每次只能添加一个组件,最后jenkins数据就爆炸了,如果多个库相互之间有依赖的话,一次需要集成三到四个库,jenkins自身一个job只支持修改一个组件库,这时候jenkins就处理不了,又会回到手工修改这种方式。由于人肉手工修改没有检查的保证,还有可能出现错误。
那么有没有更好的方式进行持续构建和集成呢?首先集成构建平台和jenkins相比有着更加友好的用户界面,另一个是集成构建平台支持各种自定义方式去集成构建,开发者可以想集成几个组件库就集成几个,针对任何一个组件库都可以进行自定义的设置,包括预编译包的属性等。
另外集成构建平台相比jenkins会增加一个统一管理的功能,借助这个功能,在技术平台上通过不同的组件的集成会包装成不同的APP,即支持多APP的集成,集成构建平台有一个功能就是会支持每次集成构建的快照,根据需要的时候进行恢复,如果一个历史线上的包出现问题的话,可以快速恢复那个包以前的状况,进行调试,是人肉手工效率的3倍左右。
任何一个开发者提交了代码之后,会触发集成、创建工程、克隆pod、打新tag、更新依赖、集成工程、编译、集成失败,会修改,如果成功:
目前我们持续集成的过程就是在不停地跑这样的循环,完成整个公司所有APP的持续集成过程。
上图是集成构建平台的界面,他有比jenkins更友好的界面,左面是支持不同的端和APP,右面是每次集成构建的历史,上面还有一些APP的管理和组件的管理等功能。
我们集成构建平台的设计思路,主要是为了解决jenkins的一些问题才推出的,首先支持各种自定义的需求,要支持多个APP和APP集成,需要支持公司内部所有的SDK,不能只针对iOS一个端使用,一定要多端通用。还有集成构建平台之后各个用户权限一定要有明确的划分。
最后,除了集成构建平台之外还有其他的平台,比如说测试、发版、数据统计,这些是独立的状态,我们推住集成构建平台之后会将这些平台全部打通,从最初的构建状况,到最后直接上线、数据收集状态,完全都是打通的。目前,集成构建平台已经上线一个多月,已经有次稳定发版,已经介入滴滴出行乘客端和企业端,打通平台的有发版平台和OneTool。
未来要做的就是要流量全切,但现在仍是灰度发布状态。一部分做集成构建,另外一部分做老的方式,未来要介入公司内所有端和其他平台进行打通,未来开发的重点是要做iOS和安卓一致性开发,目前在这两个方面上,整个开发流程是一致的,在某些细节上iOS和安卓有些区别,比如说iOS天生是支持源码的,经过修改之后才支持了预编译的包,安卓天生是支持预编译的包,在修改了之后才支持了源码的编译,未来我们就要做到一致性开发。
王涛,现任滴滴出行APP架构组架构师,在滴滴之前曾就职于百度等知名互联网公司,有多年移动开发经验。专注于移动开发领域,热爱开源,善于解决技术难题。在 Github 上维护有多个开源项目。
加入滴滴后,参与并完成了 iOS 动态化系统的设计与开发,使滴滴出行成为鲜有动态化能力的公司之一。随后又主导了整个 iOS 构建优化与流程标准化建设,开发了一系列集成构建效能工具。设计并推动整个集成构建平台的开发与上线使用。