开篇 | 模块化与解耦式开发在蚂蚁金服 mPaaS 深度实践探讨
前言
今天很高兴有机会给大家分享支付宝的开发经验,具体的内容将分成三个部分展开:
支付宝架构设计与发展;
支付宝的敏捷发布与稳定性保障;
支付宝架构的优势与赋能。
1
支付宝架构设计与发展
首先看一下支付宝的发展历史,最开始支付宝只是作为支付功能支持淘宝业务,后来逐步发展成为独立的 App,并从简单的支付功能衍生出转账、水电煤支付等生活服务,现在的支付宝已经成为一个多应用生态的超级 App。生活中你想做任何事情,几乎都可以在支付宝上实现。
截止目前,支付宝实名注册用户已经超过了 8 亿,日活数亿。在研发上面,仅 Android、iOS 客户端开发人员近千人,客户端代码行数超过了数百万行,Android 版本支付宝的工程数业已近千个,每个工程都有独立的开发 owner 负责某一个具体的模块。虽然工程师团队及工程量越发庞大,支付宝依旧能够做到日发布的频率以确保业务快速迭代。即使业务功能日益复杂,但 App 闪退率仅 0.01%。
那么,为了达到这样的业务指标,我们做了什么呢?
随着互联网的应用场景进一步丰富,用户对交互、体验的要求也越来越高。由此,我们需要在 App 的各方面下足功夫,比如客户端的运行流畅性、最大努力降低电量、流量的能耗。
我们最近在推的一个项目便是支付宝的网络统一:在客户端建立一条与服务端的长连接,通过这个长连接通道进行网络通讯协议的封装,进行业务网络请求的通讯。一般情况下咱们每次发网络请求都会建立一个 HTTP 三次握手,这样会有一定的网络延迟和没必要的流量浪费,支付宝为了解决这样的问题做了网络统一库,提升了网络通讯效率。
此外,支付宝针对扫一扫功能的性能优化,不仅体现在扫码的及时反馈,也包括对于二维码的识别范围相对竞品会更广。同时,国内有著名手机厂商直接在自身的手机系统中集成了支付宝的扫一扫组件,作为系统默认的扫码能力,这从侧面反映了支付宝在细分领域的优势。
再来谈谈支付宝如何应对复杂的使用环境:我认为最典型的复杂使用环境的即手机没网的情况下支付宝如何做到顺利打开付款码?
一般情况下,出于安全考虑,付款码仅在几个小时内有效。但支付宝内置了阿里大安全部门提供的黑匣子,通过黑匣子能够有效保障移动端安全,因此即使你在弱网环境或长时间未开启付款页面,支付宝依然能够顺利完成支付。
接下来,我们来了解看看这一系列性能优化的背后,技术架构是如何设计和实现的。
这是支付宝客户端的总体架构图,从下往上看:
最底层是支付宝框架的容器层,包括类加载资源加载和安全模块;
第二层是我们抽离出来的组件层,包括网络库,日志库,缓存库,多媒体库,日志等等,简单说这些是一些通用的能力层;
第三层是我们定制的框架层,这是关键部分,是我们得以实现上千人,上千多个工程共同开发一个 App 的基础。
第四层是基于框架封装出来的业务服务层;
第五层便是具体的业务模块,其中每一个模块都是一个或多个具体的工程。为了搭建超级 App 的并行开发模式,我们必须保证模块与模块之间的低耦合,实现插件式灵活的开发,因此整体业务分为了上千多个工程。
上千个工程低耦合逻辑上是没有问题,比如你开发网络库工程,他开发扫一扫工程,我开发动态发布工程,咱们代码可以完全没有耦合性,但又能如何做到相互依赖?
支付宝移动端的技术架构设计灵感便来源于 Java 的 OSGi 模块化思想:内部对每个工程都叫做 bundle 工程,每个 bundle 有相应代码和开发资源。实际上,每个 bundle 工程都是一个 apk 工程,只是比 apk 多了一个 bundle 描述文件。这三个东西非常关键,回到刚才说的工程与工程之间存在依赖,对方要如何引用?
工程之间的依赖关系只有两种:
第一种:代码层面的依赖(即我需要调用对方写的代码);
第二种:资源层面的依赖(即我需要引用对方定义的资源,比如布局或者样式等)。
从代码转成可运行的程序可以简单分成两个时期:
1、编译期;
2、运行期。
在支付宝的架构里,编译参与的部分是和运行期参与的部分是分离的:编译期使用 bundle 的接口包,运行期使用 bundle 包本身。bundle 的接口包是 bundle 包的一部分,即刚才说的 bundle 的代码部分。bundle 的资源包同时打进接口包,在编译期提供给另一个 bundle 引用。
传统模式中,依赖的 SDK 既参与编译,也参与运行。我们定制了编译流程,使得编译与运行的依赖内容分离。
问题来了,如何保证 App 在运行期代码和资源不缺失呢?支付宝有一个容器工程(即 portal 工程),该工程中配置了所有 bundle 包的引用,在 portal 中把所有 bundle 包本身合并,最终形成 apk 包本身。这样 bundle 引用 bundle 的接口包,仅参与编译,最终所有 bundle 在 portal 中汇集,这样编译期没问题,运行期也没问题,从而实现了工程与工程之间依赖的解耦。
完成工程间的解耦,那么如何解决代码之间存在的强依赖关系?
举个简单例子,你引用了某个 Bundle 接口包中类 A 里的方法 m,这个方法 m 因为一些业务原因需要变动,那么你就被迫需要同时改动代码。针对这样的问题,我们通过框架层面提供的微服务来解决,简单来说,即面向接口编程:具体实现与调用的接口分离,因此只要保证接口包不变,具体实现可以由开发者任意调整而不影响使用方。
以上是代码与代码之间的解耦,接下来再看看业务与业务之间的解耦:
假设某个业务首页一开始叫 ActivityA,后来因为业务优化导致这个业务首页的入口被改,那么跳转到业务首页的使用方同样跟进改动。同理,对于 H5 前端来说,在不同平台想要跳转到同一个业务,但这两个平台跳转的入口是不一样的,前端怎么跳转?支付宝是通过框架层面的微服务和微应用来实现解耦的:
微服务是通过面向接口开发、引用,然后在框架中动态注册的方式来实现的接口隔离解耦。
微应用是通过预先定义好一个数字来唯一标识一个业务模块,然后动态注册到框架中,具体这个业务模块中有什么,入口叫什么,完全由开发负责人自己决定。
所有框架注册的微应用微服务最终会在框架启动中动态加载。
那么大家是否会有疑问,支付宝这么多工程师团队和工程量,微服务微应用之间仍需要协同配合,那么支付宝发布一个版本是不是很麻烦?
2
支付宝的敏捷发布与稳定性保障
传统的开发模式下大家势必互相影响,假如你提交的代码闪退了或者业务异常了,那么你业务关联的开发测试一定会受到影响,一般我们称这样的开发模式是「串行开发,互相等待」。而支付宝的工程解耦,代码解耦能给工程效能带来多大的效益?接下来我们来聊一聊支付宝的「并行开发模式」。
支付宝每次发布一个版本,都是由若干个 bundle 工程组成。若干个 bundle 工程合并在一起叫基线,每次发布版本之后,参与下一个版本迭代的同学都基于这个稳定的基线做独立的开发。而之所以能够支持独立开发,归功于我们介绍的工程解耦和代码解耦。
比如这张框架图,余额宝业务、ofo 小程序、蚂蚁森林、网络库等可以同时开发测试完成之后进入某一个大版本发布即可,如果存在依赖关系,只需要找和自己相关同学一起进发布,正因为如此支付宝做到了每天都有很多业务进基线,每天都在同时迭代业务。
既然发布确保了效率,那么支付宝又是如何保证发布稳定性的?
传统的开发模式是:开发测试、全量发版,然后进入下个版本的迭代,继续开发测试修 bug、全量发版。这样做有几个缺点:
1、如果测试阶段未发现缺陷,很可能导致线上用户出现大规模异常;
2、出现大规模异常没有修复能力,造成用户损失;
3、随着业务的迭代,业务越来越多,功能越来越复杂,卡顿现象频出,用户体验变差;
4、关键业务监控不到位,不知道用户怎么使用 App,或者出现业务异常无感知,传统的开发模式里开发测试的比重基本上占到了 90% 甚至 100%。
而对于支付宝来说,开发测试仅占整体工程量 25%,以下即相应的工作拆解:
3
支付宝架构的优势与赋能
最后,支付宝所有在移动端开发方面的技术积累和架构实践,已经作为蚂蚁金服金融科技的一部分对外开放,即我们今天见到的蚂蚁金服 mPaaS。mPaaS(Mobile Platform As A Service),源于支付宝 App 的移动开发平台,为移动开发、测试、运营及运维提供云到端的一站式解决方案,能有效降低技术门槛、减少研发成本、提升开发效率,协助企业快速搭建稳定高质量的移动 App。
秉承着「给世界带来小而美的变化」的理念,我们通过 mPaaS 帮助 12306 这样的国民级 App 重构了客户端,使得大家可以用上一个好的体验的 App 进行出行购票,用 mPaaS 这样成熟的底层框架搭建一个 12306 仅需要 2-3 个月的时间。除了 12306 还有支付宝香港版,广发银行发现精彩客户端,同样在短短几个月的时间内便完成了业务重构。
那么,对你来说,模块化与解耦式开发是否还有更好的方式?欢迎留言与我们一起探讨。