一、背景
美团是全球最大的互联网+生活服务平台,为3.2亿活跃用户和500多万的优质商户提供一个连接线上与线下的电子商务服务。秉承“帮大家吃得更好,生活更好”的使命,我们的业务覆盖了超过200个品类和2800个城区县网络,在餐饮、外卖、酒店旅游、丽人、家庭、休闲娱乐等领域具有领先的市场地位。
随着各业务的蓬勃发展,大众点评移动研发团队从当初各自为战的“小作坊”已经发展成为可以协同作战的、拥有千人规模的“正规军”。我们的移动项目架构为了适应业务发展也发生了天翻地覆的变化,这对移动持续集成提出更高的要求,而整个移动研发团队也迎来了新的机遇和挑战。
二、问题与挑战
当前移动客户端的组件库超过600个,多个移动项目的代码量达到百万行级别,每天有几百次的发版集成需求。保证近千名移动研发人员顺利进行开发和集成,这是我们部门的重要使命。但是,前进的道路从来都不是平坦的,在通向目标的大道上,我们还面临着很多问题与挑战,主要包括以下几个方面:
项目依赖复杂
上图仅仅展示了我们移动项目中一小部分组件间的依赖关系,可以想象一下,这600多个组件之间的依赖关系,就如同一个城市复杂的道路交通网让人眼花缭乱。这种组件间错综复杂的依赖关系也必然会导致两个严重的问题,第一,如果某个业务需要修改代码,极有可能会影响到其它业务,牵一发而动全身,进而会让很多研发同学工作时战战兢兢,做项目更加畏首畏尾;第二,管理这些组件间繁琐的依赖关系也是一件令人头疼的事情,现在平均每个组件的依赖数有70多个,最多的甚至达到了270多个,如果依靠人工来维护这些依赖关系,难如登天。
研发流程琐碎
移动研发要完成一个完整功能需求,除了代码开发以外,需要经历组件发版、组件集成、打包、测试。如果测试发现Bug需要进行修复,然后再次经历组件发版、组件集成、打包、测试,直到测试通过交付产品。研发同学在整个过程中需要手动提交MR、手动升级组件、手动触发打包以及人工实时监控流程的状态,如此研发会被频繁打断来跟踪处理过程的衔接,势必严重影响开发专注度,降低研发生产力。
构建速度慢
目前大众点评的iOS项目构建时间,从两年前的20分钟已经增长到现在的60分钟以上,Android项目也从5分钟增长到11分钟,移动项目构建时间的增长,已经严重影响了移动端开发集成的效率。而且随着业务的快速扩张,项目代码还在持续不断的增长。为了适应业务的高速发展,寻求行之有效的方法来加快移动项目的构建速度,已经变得刻不容缓。
App质量保证
评价App的性能质量指标有很多,例如:CPU使用率、内存占用、流量消耗、响应时间、线上Crash率、包体等等。其中线上Crash直接影响着用户体验,当用户使用App时如果发生闪退,他们很有可能会给出“一星”差评;而包体大小是影响新用户下载App的重要因素,包体过大用户很有可能会对你的App失去兴趣。因此,降低App线上Crash率以及控制App包体大小是每个移动研发都要追求的重要目标。
项目依赖复杂、研发流程琐碎、构建速度慢、App质量保证是每个移动项目在团队、业务发展壮大过程中都会遇到的问题,本文将根据大众点评移动端多年来积累的实践经验,一步步阐述我们是如何在实战中解决这些问题的。
三、MCI架构
MCI(Mobile continuous integration)是大众点评移动端团队多年来实践总结出来的一套行之有效的架构体系。它能实际解决移动项目中依赖复杂、研发流程琐碎、构建速度慢的问题,同时接入MCI架构体系的移动项目能真正有效实现App质量的提升。
MCI完整架构体系如下图所示:
MCI架构体系包含移动CI平台、流程自动化建设、静态检查体系、日志监控&分析、信息管理配置,另外MCI还采取二进制集成等措施来提升MCI的构建速度。
构建移动CI平台
我们通过构建移动CI平台,来保证移动研发在项目依赖极其复杂的情况下,也能互不影响完成业务研发集成;其次我们设计了合理的CI策略,来帮助移动研发人员走出令人望而生畏的依赖关系管理的“泥潭”。
流程自动化建设
在构建移动CI平台的基础上,我们对MCI流程进行自动化建设来解决研发流程琐碎问题,从而解放移动研发生产力。
提升构建速度
在CI平台保证集成正确性的情况下,我们通过依赖扁平化以及优化集成方式等措施来提升MCI的构建速度,进一步提升研发效率。
静态检查体系
我们建立一套完整自研的静态检查体系,针对移动项目的特点,MCI上线全方位的静态检查来促进App质量的提升。
日志监控&分析
我们对MCI体系的完整流程进行日志落地,方便问题的追溯与排查,同时通过数据分析来进一步优化MCI的流程以及监控移动App项目的健康状况。
信息管理配置
最后,为了方便管理接入MCI的移动项目,我们建设了统一的项目信息管理配置平台。
接下来,我们将依次详细探讨MCI架构体系是如何一步步建立,进而解决我们面临的各种问题。
四、构建移动CI平台
4.1 搭建移动CI平台
我们对目前业内流行的CI系统,如:Travis CI、 CircleCI、Jenkins、Gitlab CI调研后,针对移动项目的特点,综合考虑代码安全性、可扩展性及页面可操作性,最终选择基于Gitlab CI搭建移动持续集成平台,当然我们也使用Jenkins做一些辅助性的工作。MCI体系的CI核心架构如下图所示:
名词解释:
- Gitlab CI:Gitlab CI是GitLab Continuous Integration(Gitlab持续集成)的简称。
- Runner:Runner是Gitlab CI提供注册CI服务器的接口。
- Pipeline:可以理解为流水线,包含CI不同阶段的不同任务。
- Trigger:触发器,Push代码或者提交Merge Request等操作会触发相应的触发器以进入下一流程。
该架构的优势是可扩展性强、可定制、支持并发。首先CI服务器可以任意扩展,除了专用的服务器可以作为CI服务器,普通个人PC机也可以作为CI服务器(缺点是性能比服务器差,任务执行时间较长);其次每个集成任务的Pipeline是支持可定制的,托管在MCI的集成项目可以根据自身需求定制与之匹配的Pipeline;最后,每个集成项目的任务执行是可并发的,因此各业务线间可以互不干扰的进行组件代码集成。
4.2 CI流程设计
一次完整的组件集成流程包含两个阶段:组件库发版和向目标App工程集成。如下图所示:
第一阶段,在日常功能开发完毕后,研发提PR到指定分支,在对代码进行Review、组件库编译及静态检查无误后,自动发版进入组件池中。所有进入组件池中的组件均可以在不同App项目中复用。
第二阶段,研发根据需要将组件合入指定App工程。组件A本身的正确性已经在第一阶段的组件库发版中验证,第二阶段是检查组件A的改变是否对目标App中原有依赖它的其它组件造成影响。所以首先需要分析组件A被目标App中哪些组件所依赖,目标App工程按照各自的准入标准,对合入的组件库进行编译和静态分析,待检查无误后,最终合入发布分支。
通过组件发版和集成两阶段的CI流程,组件将被正确集成到目标项目中。而对于存在问题的组件则会阻挡在项目之外,因此不会影响其它业务的正常开发和发版集成,各业务研发流程独立可控。
4.3 设计合理的CI策略
组件的发版和集成能否通过CI检查,取决于组件当前的依赖以及组件本身是否与目标项目兼容。移动研发需要对组件当前依赖有足够的了解才能顺利完成发版集成,为了减小组件依赖管理的复杂度,我们设计了合理的发版集成策略来帮助移动研发走出繁琐的版本依赖管理的困境。
组件集成策略
每个组件都有自己的依赖项,不同组件可能会依赖同一个组件,组件向目标项目集成过程中会面临如下一些问题:
- 版本集成冲突:组件在集成过程中某个依赖项与目标项目中现有依赖的版本号存在冲突。
- App测试包不稳定:组件依赖项的版本发生变化导致在不同时刻打出不同依赖项的App测试包。
频繁的版本集成冲突会导致业务协同开发集成效率低下,App测试包的不稳定性会给研发追踪问题带来极大的困扰。问题的根源在于目标项目使用每个组件的依赖项来进行集成。因此我们通过在集成项目中显示指定组件版本号以及禁止动态依赖的方式,保证了App测试包的稳定性和可靠性,同时也解决了组件版本集成冲突问题。
组件发版策略
组件向组件池发版也一样会涉及依赖项的管理,简单粗暴的方法是指定所有依赖项的版本号,这样做的好处是直观明了,但研发需要对不同版本依赖项的功能有足够的了解。正如组件集成策略中所述,集成项目中每个组件的版本都是显示指定并且唯一确定的,组件中指定依赖项的版本号在集成项目中并不起作用。所以我们在组件发版时采用自动依赖组件池中最新版本的方式。这样设计的好处在于:
- 避免移动研发对版本依赖关系的处理。
- 给基础组件的变更迭代提供了强有力的推动机制。
当基础组件库的接口和设计发生较大变化时,可以强有力的推动业务层组件做相应适配,保证了在高度解耦的项目架构下保持高度的敏捷性。但这种能力不能滥用,需要根据业务迭代周期合理安排,并做好提前通知动员工作。
五、流程自动化建设
研发流程琐碎的主要原因是研发需要人工参与持续集成中每一步过程,一旦我们把移动研发从持续集成过程中解放出来,自然就能提高研发生产力。我们通过项目集成发布流程自动化以及优化测试包分发来优化MCI流程。
项目集成流程托管
研发流程中的组件发版、组件集成与App打包都是持续集成中的标准化流程,我们通过流程托管工具来完成这几个步骤的自动衔接,研发同学只需关注代码开发与Bug修复。
流程托管工具实现方案如下:
- 自动化流程执行:通过托管队列实现任务自动化顺序执行,webhook实现流程状态的监听。
- 关键节点通知:在关键性节点流程执行成功后发送通知,让研发对流程状态了然于胸。
- 流程异常通知:一旦持续集成流程执行异常,例如项目编译失败、静态检查没通过等,第一时间通知研发及时处理。
打包发布流程托管
无论iOS还是Android,在发布App包到市场前都需要做一系列处理,例如iOS需要导出ipa包进行备份,保存符号表来解析线上Crash,以及上传ipa包到iTC(iTunes Connect);而Android除了包备份,保存Mapping文件解析线上Crash外,还要发布App包到不同的渠道,整个打包发布流程更加复杂繁琐。
在没有MCI流程托管以前,每到App发布日,研发同学就如临大敌守在打包机器前,披荆斩棘,过五关斩六将,直到所有App包被“运送”到指定地点,搞得十分疲惫。如同项目集成流程托管一样,我们把整个打包发布流程做了全流程托管,无人值守的自动打包发布方式解放了研发同学,研发同学再也不用每次都披星戴月,早出晚归,跪键盘了(捂脸)。
包分发流程建设
对于QA和研发而言,上面的场景是否似曾相识。Bug是QA与研发之间沟通的桥梁,但由于缺乏统一的包管理和分发,这种模糊的沟通导致难以快速定位和追溯发生问题的包。为了减少QA和研发之间的无效沟通以及优化包分发流程,我们亟需一个平台来统一管理分发公司内部的App包,于是MCI App应运而生。
MCI App提供如下功能:
- 查看下载安装不同类型不同版本的App。
- 查看App包的基础信息(打包者、打包耗时、包版本、代码提交commit点等)。
- 查看App包当前版本集成的所有组件库信息。
- 查看App包体占用情况。
- 查询App发版时间计划。
- 分享安装App包下载链接。
未来MCI App还会支持查询项目集成状态以及App发布提醒、问题反馈,整合移动研发全流程。
六、提升构建速度
移动项目在构建过程中最为耗时的两个步骤分别为组件依赖计算和工程编译。
组件依赖计算
组件依赖计算是根据项目中指定的集成组件计算出所有相关的依赖项以及依赖版本,当项目中集成组件较多的时候,递归计算依赖项以及依赖版本是一件非常耗时的操作,特别是还要处理相关的依赖冲突。
工程编译
工程编译时间是跟项目工程的代码量成正比的,集团业务在快速发展,代码量也在快速的膨胀。
为了提升项目构建速度,我们通过依赖扁平化的方法来彻底去掉组件依赖计算耗时,以及通过优化项目集成方式的手段来减少工程编译时间。
依赖扁平化
依赖扁平化的核心思想是事先把依赖项以及依赖版本号进行显示指定,这样通过固定依赖项以及依赖版本就彻底去掉了组件依赖计算的耗时,极大的提高了项目构建速度。与此同时,依赖扁平化还额外带来了下面的好处:
- 减轻研发依赖关系维护的负担。
- App项目更加稳定,不会因为依赖项的自动升级出现问题。
优化集成方式
通常组件代码都是以源码方式集成到目标工程,这种集成方式的最大缺点是编译速度慢,对于上百万行代码的App,如果采用源码集成的方式,工程编译时间将超过40分钟甚至更长,这个时间,显然会令人崩溃。
使用源码集成
使用二进制集成
实际上组件代码还可以通过二进制的方式集成到目标工程:
相比源码方式集成,组件的二进制包都是预先编译好的,在集成过程中只需要进行链接无需编译,因此二进制集成的方式可以大幅提升项目编译速度。
二进制集成优化
为了进一步提高二进制集成效率,我们还做了几件小事:
(1)多线程下载
尽管二进制集成的方式能减少工程编译时间,但二进制包还是得从远端下载到CI服务器上。我们修改了默认单线程下载的策略,通过多线程下载二进制包提升下载效率。
(2)二进制包缓存
研发在MCI上触发不同的集成任务,这些集成任务间除了升级的组件,其它使用的组件二进制包大部分是相同的,因此我们在CI服务器上对组件二进制包进行缓存以便不同任务间进行共享,进一步提升项目构建速度。
二进制集成成果
我们在MCI中采用二进制集成并且经过一系列优化后,iOS项目工程的编译时间比原来减少60%,Android项目也比原来减少接近50%,极大地提升了项目构建效率。
七、静态检查体系
除了完成日常需求开发,提高代码质量是每个研发的必修课。如果每一位移动研发在平时开发中能严格遵守移动编程规范与最佳实践,那很多线上问题完全可以提前避免。事实上仅仅依靠研发自觉性,难以长期有效的执行,我们需要把这些移动编程规范和最佳实践切实落地成为静态检查强制执行,才能有效的将问题扼杀在摇篮之中。
静态检查基础设施
静态检查最简单的方式是文本匹配,这种方式检查逻辑简单,但存在局限性。比如编写的静态检查代码维护困难,再者文本匹配能力有限对一些复杂逻辑的处理无能为力。现有针对Objective-C和Java的静态分析工具也有不少,常见的有:OCLint、FindBugs、CheckStyle等等,但这些工具定制门槛较高。为了降低静态检查接入成本,我们自主研发了一个适应MCI需求的静态分析框架–Hades。
Hades的特点:
- 完全代码语义理解
- 具备全局分析能力
- 支持增量分析
- 接入成本低
Hades的核心思想是对源码生成的AST(Abstract Syntax Tree)进行结构化数据的语义表达,在此基础上我们就可以建立一系列静态分析工具和服务。作为一个静态分析框架,Hades并不局限于Lint工具的制作,我们也希望通过这种结构化的语义表达来对代码有更深层次的理解。因此,我们可以借助文档型数据库(如:CouchDB、MongoDB等)建立项目代码的语义模型数据库,这样我们能够通过JS的Map-Reduce建立视图从而快速检索我们需要查找的内容。关于Hades的技术实现原理我们将在后续的技术Blog中进行详细阐述,敬请期待。
MCI静态检查现状
目前MCI已经上线了覆盖代码基本规范、非空特性、多线程最佳实践、资源合法性、启动流程管控、动态行为管控等20多项静态检查,这些静态检查切实有效地促进了App代码质量的提高。
八、日志监控&分析
MCI作为大众点评移动端持续集成的重要平台,稳定高效是要达成的第一目标,日志监控是推动MCI走向稳定高效的重要手段。我们对MCI全流程的日志进行落地,方便问题追溯与排查,以下是部分线上监控项。
流程时间监控分析
通过监控分析MCI流程中每一步的执行时间,我们可以进行针对性的优化以提高集成速度。
异常流程监控分析
我们会对异常流程进行监控并且通知流程发起者,同时我们会对失败次数较多的Job分析原因。一部分CI环境或者网络问题MCI可以自动解决,而其它由于代码错误引起的异常MCI会引导移动研发进行问题的排查与解决。
包体监控分析
我们对包体总大小、可执行文件以及图片进行全方面的监控,包体变化的趋势一目了然,对于包体的异常变化我们可以第一时间感知。
除此之外,我们还对MCI集成成功率、二进制覆盖率等方面做了监控,做到对MCI全流程了然于胸,让MCI稳定高效的运行。
九、信息管理配置
目前MCI平台已经接入公司多个移动项目,为了接入MCI的项目进行统一方便的信息管理,我们建设了MCI信息管理平台——摩卡(Mocha)。Mocha平台的功能包含项目信息管理、配置静态检查项以及组件发版集成查询。
项目信息管理
Mocha平台负责注册接入MCI项目的基本信息,包含项目地址、项目负责人等,同时对各个项目的成员进行权限管理。
配置静态检查项
MCI支持不同项目自定义不同的静态检查项,在Mocha平台上可以完成项目所需静态检查项的定制,同时支持静态检查白名单的配置审核。
组件发版集成查询
Mocha平台支持组件历史发版集成的记录查询,方便问题的排查与追溯。
作为移动集成项目的可视化配置系统,Mocha平台是MCI的一个重要补充。它使得移动项目接入MCI变得简单快捷,未来Mocha平台还会加入更多的配置项。
十、总结与展望
本文从大众点评移动项目业务复杂度出发,详细介绍了构建稳定高效的移动持续集成系统的思路与最佳实践方案,解决项目依赖复杂所带来的问题,通过依赖扁平化以及二进制集成提升构建速度。在此基础上,通过自研的静态检查基础设施Hades降低静态检查准入的门槛,帮助提升App质量;最后MCI提供的全流程托管能力能显著提高移动研发生产力。
目前MCI为iOS、Android原生代码的项目集成已经提供了相当完善的支持。此外,MCI还支持Picasso项目的持续集成,Picasso是大众点评自研的高性能跨平台动态化框架,专注于横跨iOS、Android、Web、小程序四端的动态化UI构建。当然移动端原生项目的持续集成和动态化项目的持续集成有共通也有很多不同之处。未来MCI将在移动工程化领域进一步探索,为移动端业务蓬勃发展保驾护航。
作者简介
- 智聪,大众点评iOS技术专家,专注于移动工具链开发,对移动持续集成、静态分析平台建设有深刻理解和丰富的实践经验。
- 邢轶,大众点评Android技术专家,专注于移动持续集成、静态分析、静态化等App基础设施建设。
团队介绍
大众点评移动研发中心,Base上海,为美团提供移动端底层基础设施服务,包含网络通信、移动监控、推送触达、动态化引擎、移动研发工具等。同时团队还承载流量分发、UGC、内容生态、个人中心等业务研发工作,长年虚位以待专注于移动端研发的各路英雄豪杰。欢迎投递简历:dawei.xing#dianping.com。
如发现文章有错误、对内容有疑问,都可以关注美团技术团队微信公众号(meituantech),在后台给我们留言。
分享一线技术实践,沉淀成长学习经验