支付宝移动端弹性动态架构实战与思考:从独木舟到航空母舰的演进

2024-06-06
来源:网络整理

介绍

本文是中悦在2019北京国际峰会上分享内容的总结,希望通过本文介绍支付宝近几年来如何面对超大业务体量的挑战,以及在移动端构建弹性动态架构方面做了哪些实践经验和思考,希望对读者有些帮助。

作为国民级应用,支付宝目前在中国拥有超过 8.7 亿年活跃用户,提供超过 200 项服务,其崩溃率始终保持在 0.5% 以下,而且支付宝每天都会推出新功能和改进。能取得今天这样的成绩实属不易,是长期实践经验的结晶。

支付宝的架构演进主要经历了三个阶段,可以分为独木舟、战舰、航空母舰三个阶段。

独木舟时代

支付宝刚上线手机APP的时候,架构很简单,除了一些工具组件分模块,业务代码都混杂在一起,刚开始没什么大问题,但是随着我们研发人员的快速增长,问题开始变得棘手,举几个例子就可以看出来。

研发同事晚上提交的可执行代码,在第二天早上更新的时候,变得完全不能用了,因为其他不相关团队提交的代码覆盖或者污染了自己的代码。

在发布日期临近的时候,通常是最忙碌的时候,但时间不是花在赶开发新功能上,而是花在解决合并代码带来的各种问题上。这不仅浪费了时间,也耽误了测试人员的宝贵时间。

即使最终发布了,稳定性和性能也很差,因为每个模块只关心自己的事情,没有统一的规范,也缺乏统一的监控。

最让开发者头疼的问题是当时没有-dex的解决方案。

这些严重的问题导致我们的产品开发迭代难以为继,所以我们决定做彻底的重构,进入战舰时代。

战舰时代

在设计下一代客户端架构的时候,我们从团队协作、研发效率、性能、稳定性三个方面进行考虑。

在团队协作方面,我们希望整个架构能够合理分层,在基础层面,将通用能力下放到底层,服务更多上层业务,避免重复造轮子;在业务层面,各个业务团队可以独立开发和管理,不影响无关业务。基于这样的初衷,我们形成了如下架构:

整个客户端架构分为四层:业务层、服务层、组件层、框架层。

我们把服务层、组件层、框架层在移动端统称为PaaS服务,这些PaaS服务是可以复用的,我们不仅在支付宝用,在其他集团应用中也有用,比如蚂蚁财富、网商银行等。

| 业务部门

要实现业务划分,最好的办法就是代码隔离,这样大家就不用在同一个目录下开发,避免代码合并冲突。一般是通过 aar 来实现,可惜我们重构的时候没有 aar,而且就算有了 aar 也还是存在打包时间随着代码量线性增长的问题。

我们的解决方案借鉴了 OSGi 的理念,将整个客户端划分为单元,每个单元可以包含自己的代码、页面和资源。读者可能会疑惑,这和 aar 有什么区别?其实区别还是很大的!

首先,里面的代码部分是编译好的dex,在编译apk的时候我们只需要合并dex就可以了,不需要像aar那样先编译成dex再合并,大大节省了打包时间;其次,里面可以独立运行,并且我们可以通过代理的方式加载基础组件等,使得服务的动态交付成为可能;最后,里面还包含了微应用、服务以及相关的配置信息,框架会根据这些信息来启动相应的组件。

服务跟框架中的服务类似,对外提供接口服务,用户不需要知道如何初始化服务实例、生命周期管理等,这些完全由框架来管理,用户只需要知道目标服务接口类的方法参数,调用时通过框架提供的API来获取实例即可。对于服务的发布者,他在自己的接口类以及由接口类派生的实例类中进行声明,并在的文件中注册相关信息。这种方式的本质思想就是减少类之间复杂的依赖关系,避免繁琐的初始化工作。

采用接口依赖的方式开发,可以解除服务使用者对服务提供者的依赖,在服务提供者还未完全开发完成时,使用者完全可以以mock的方式模拟服务,而不需要修改自己的业务代码,当然前提是双方已经商定好服务接口约定。

支付宝的页面非常多,直接入手是远远不够的。我们选择在它们之上增加一个微应用的概念。微应用在框架中有一个唯一的应用ID来标识自己的存在。微应用有统一的入口,并且根据用户传入的字典参数进行管理。这样可以带来很多好处:

只要应用程序ID和参数协议不变,用户就无需担心目标应用程序内部重建带来的影响,直接使用或者类名导致的引用过多的问题也不再存在。

利用微应用的ID和字典参数特性可以很方便的生成URL,使得外部应用可以通过URL跳转到应用内的页面。

从数据角度,我们可以按业务维度统计用户行为数据。

微应用的概念不仅适用于原生页面,也适用于H5、小程序。注册一个H5或者小程序类型的应用ID,框架会自动将启动流程交给H5或者小程序容器,用户无需关心应用ID对应的应用类型。

总结一下,微应用和服务接口赋予的特性,大大提高了团队间协作的效率,研发团队间的依赖关系更加简单,各个团队可以各奔东西,更加专注于自身服务的建设。

| 性能优化

我们一方面在架构上做了较大的改变,提高研发效率,另一方面也在不断的优化性能,提升用户体验。我们主要在三个层面下功夫:

建立统一的开发规范,业务方使用统一的线程池、存储、网络等组件,按需加载,避免不必要的启动和耗时操作。

如果业务模块在应用启动时需要进行初始化工作,则必须使用引入机制,框架根据业务优先级决定实际的业务初始化。

使用AOP方面统计常见路径所消耗的时间,并跟踪性能瓶颈。

我们对常见指标,例如崩溃、ANR、内存、存储、电量、流量等进行长期跟踪,可以清晰的了解各个版本之间这些指标的差异,进行抽样分析,定位和解决问题。

我们不仅在应用层面进行优化,也不断探索性能提升的可能性。在这方面,我们也取得了不少成果。比如在部分系统版本中,我们通过在启动阶段禁用 GC,可以减少 20%~30% 的启动时间;在 iOS 上,我们利用系统自身的机制,增加进程活跃时间,实现应用秒级启动。

航空母舰时代

随着移动支付的日益普及,面对海量的用户和业务需求,高可用性和弹性动态性成为支付宝客户端更加艰巨的挑战。作为集支付、金融、生活为一体的服务平台,支付宝需要能够快速稳定地发布服务、引入第三方服务,同时还要能够积极快速地响应用户的反馈和诉求。

| 动态研发模型

为了满足业务快速迭代的需求,我们改变了研发模式,逐步将业务从原生页面迁移到 Web 混合页面。原有的研发模式能够满足团队协作的需求,但随着业务规模的不断增长,代码量也随之膨胀,导致安装包过大,在 iOS 上一度超过代码段限制,无法通过审核。另外,基于集中时间点的迭代发布,通常一个月一个版本,已经远远不能满足业务的更新速度要求。相比原生应用开发,Web 应用的优势非常明显:

只需要一套代码,就可以将网页应用运行在iOS和客户端上,相对可以减少人员的投入。

每个用户日常使用的功能只是庞大的支付宝平台的一小部分,H5应用可以动态交付,从而省去冗余存储,减少包大小。

近年来,Weex 等动态渲染引擎在社区非常活跃,但经过小范围的应用,考虑到 Web 技术的不断发展和在业界的认可地位,我们最终选择 Web 技术作为动态研发模式的基础。

Web应用迭代摆脱了集中式客户端发布的束缚,各业务线的迭代计划变得自主可控。

| 完善 Web 体验

虽然 Web 应用有着明显的优势,但是其在移动设备上的不足也很明显,其提供的用户体验、性能、能力与原生应用相比有着很大的差距。为了弥补这些差距,我们做了很多改进,主要有以下几个方面:

前后端分离,离线页面资源,节省资源请求耗时,大幅提升页面打开速度,解决网络环境较差白屏问题,同时数据请求走网络通道,优化空间更大,安全性更高。

批量更新:客户端更新业务应用版本时,不需要下载完整的新版本资源包,而是下载发布平台根据客户端本地安装版本计算出的较小差异包,不仅节省带宽和流量,也提升了业务更新速度。

推送与拉取相结合可以解决业务最新版本覆盖的问题,每次发布新版本时,业务可以主动触发消息给客户端,客户端收到通知后更新业务应用版本。同时客户端会定期检查服务器是否有版本发布,这样版本发布后大部分用户都能在短时间内获取最新的应用。

容错补偿:客户端可能因为网络、安全或者存储权限等原因无法及时使用或获取离线包,我们也考虑到了这一点,在发布离线资源时,发布平台会自动生成对应的在线URL,并配置在应用信息中,当客户端加载Web应用时发现离线包不可用,会立即启用该URL加载内容,可以最大化业务可用性。

独立浏览器内核,碎片化的问题从诞生之初就存在,似乎没有解决的迹象。不同系统、不同厂商的浏览器内核也存在差异,导致层出不穷的兼容性问题让研发同仁头疼不已,而且这也违背了 Web 一统天下的愿景。为了彻底解决和掌控这些问题,我们引入了独立的 UC 浏览器内核,并集成到应用中,这样所有问题都集中到 UC 团队去解决,变得非常可控。据统计,使用 UC 浏览器内核之后,浏览器相关的崩溃、ANR 明显减少。同时,我们也能第一时间修复、发布安全漏洞,比厂商升级效率高很多。

Web应用全方面监控,资源加载异常、JS执行异常、白屏、加载时间等性能数据都会被收集并上报到后端,以便及时发现异常。

| 小程序

我们不仅自己提供丰富多彩的服务,还需要引入第三方服务,服务更多人。以前只能引入简单的第三方H5页面,只能使用支付宝提供的少量功能,而且开发者能力的差异导致用户体验不够理想。小程序充分开放了支付宝的能力,从开发到测试都有完整的IDE等工具链支持。同时DSL简单易用,对于第三方来说,可以快速开发并上线一个体验和功能比以前更强大的小程序。

| 在线高可用性保障系统

在支付宝,上线风险是每个研发人员在业务上线前必须梳理的事情,风险评估、风险防范、风险监控、风险应急预案都是上线前必须做好的。支付宝线上高可用保障体系由灰度发布、实时监控、诊断定位、灾备四部分组成。

灰度发布是防范风险的有效手段之一。对于客户端来说,无论线下测试做得多么完善,都无法保证在用户环境下一切都能正常工作。直接发布给所有用户是非常危险的操作,也是支付宝内部的严重违规行为。我们的发布平台提供了多种灰度策略,包括白名单灰度、时间窗口灰度、百分比灰度、基于模型、区域体系等维度灰度。新版本发布前,优先对活跃用户、问题率高的模型进行灰度。灰度期间,发现问题并修复,不断扩大灰度范围,直至闪退率、卡顿率等指标达到发布标准后,才进行全面发布。

首先,制定各项线上监控指标,包括崩溃、卡顿、流畅度、流量、内存、存储、业务不可用点等。

其次,上报策略实时上报闪退、卡顿、业务不可用点等高优先级指标,第一时间发现异常;数据上报采用独立流程,保证不影响主业务逻辑;当遇到业务高峰期,比如春节红包、双十一等大型活动,我们可以动态调整上报策略,缓解日志服务器的压力。除了自动上传、周期上传策略外,我们还向客户端发送诊断指令,获取日志等平时不使用但常驻在客户端的日志。

我们可以根据客户端上报的各种跟踪日志完整地描述用户的操作路径,并根据这些信息尝试重现用户的问题。数据的真实性比用户提供的信息更可靠,可以减少错误信息带来的干扰。另外诊断指令上传的日志可以提供更完整的信息,帮助我们更清晰地定位问题。所以我们通常会要求研发同学在写代码时输出更多有用的信息。

当故障发生时,第一要求是止血,避免损失扩大,我们通常会在业务逻辑中预设开关,当出现大规模业务异常或者止损时,后台推送业务开关到客户端,将业务暂时阻断下线。

如果客户端在启动过程中出现死锁、崩溃、首页异常等超过一定阈值的情况,会自动清理应用数据,将应用恢复到初始状态,可以部分解决数据异常导致的启动问题。

我们用技术去修复原生代码,这本身也是有风险的技术,所以要经过灰度发布阶段,逐步验证线上稳定性,一旦出现问题,要立即回滚。

最近写了一本6000页的Java学习手册,以及Java人必读的四本书合集,分享到知乎,已经收获了3万个赞!

整个套装专注于Java技术,包括Boot/、JVM、集合、多线程、JPA、、大数据、Git、、IDEA、算法、面试题等相关内容,配有文字和源码说明,还附赠了一波电子书。

内容包括但不限于: 相关(附答案) 精选面试题(附答案) 全系列试题(附答案) 相关(附答案) Boot教程与实用框架(附答案)(附答案)(附答案) Git(附答案)(附答案) IDEA教程&实用(附答案) Java基础:多线程、集合、JVM等(附答案) 技巧(附答案)(附答案)(附答案) ......

每篇文章都附有源代码。还有电子书合集。如果你想获得完整的 PDF,可以通过以下方式获取信息

欢迎大家扫描二维码关注公众号博主,本公众号会持续更新技术干货,不定期分享Java高级面试指南、Java核心知识、架构书籍电子版。

分享