独家!支付宝小程序技术架构全解析
在轻应用混战的当下,小程序已经成为巨头们角逐的焦点,阿里自然也不甘落后。据阿里官方的数据,截止到今年 1 月 28 日为止,支付宝小程序应用数已经达到 12 万,总用户数突破 5 亿,日活跃用户数突破 2.3 亿,用户通过支付宝首页下拉入口进入小程序的日人均打开次数为 4 次,支付宝小程序也因此被称为“蚂蚁金服未来三年最重要的战略之一”。
然而,支付宝公开的信息更多面向的是普通用户,开发者能获知的信息少之又少,为此,InfoQ 采访了支付宝小程序首席架构师白招拒,为大家解读支付宝小程序的技术架构和开发特点,以下是采访的全部内容。
支付宝小程序从 2016 年开始立项算起,到现在也快 3 年的时间,在这 3 年的过程中,小程序的技术架构也是不断的升级和演进,在满足业务发展的同时对于小程序整体的高可用、性能优化、多端输出方面做了大量的工作。今天给大家分享下我们在支付宝小程序技术这块所做的一些工作。
小程序技术架构主要分成四个方面来讲:
1. 系统架构,主要给大家说下小程序的架构,以及其中的一些关键技术;
2. 性能体验,讲下我们在性能体验这块做的几个 case;
3. 开发者工具,怎么更好的帮助开发者开发和管理小程序,和保障线上小程序的质量;
4. 多端 inside,将支付宝小程序的技术输出给集团和外部的商户,让他们具备运行小程序的能力。
1、系统架构
支付宝小程序不是从零开始建设的一个产品,而是依托于蚂蚁技术部多年来的技术沉淀,再结合小程序的业务场景,逐步的发展起来的。
以上是支付宝小程序架构的示意图,最上面是支付宝钱包提供的主要的七个场景入口,开发者可以根据自己的业务场景运营这些场景入口,把这些入口的流量充分利用起来。中间框内的是小程序的核心引擎,上面是对开发者提供的基础组件和基础 API 能力,开发者根据这些组件和 API 来开发自己的小程序,满足用户的需求。
小程序前端框架这块借鉴了主流前端框架 React 的设计思路,从小程序的应用形态,提供了简洁的编程模型,定义了一套组件和 API 接口的规范,降低了学习门槛,方便开发者快速开发小程序。在小程序框架内部提供了小程序的生命周期管理,通过事件的方式把小程序每个阶段都注入到小程序里面,开发者可以通过这些事件来处理小程序每个阶段需要完成的业务逻辑。同时框架内部使用了虚拟 DOM 来处理页面的每次更新,提升了页面的渲染性能。
前端框架下面是小程序 native 引擎,包括了小程序容器、渲染引擎和 JavaScript 引擎,这块主要是把客户端 native 的能力和前端框架结合起来,给开发者提供系统底层能力的接口。在渲染引擎上面,支付宝小程序不仅提供 JavaScript+Webview 的方式,还提供 JavaScript+Native 的方式,在对性能要求较高的场景,可以选择 Native 的渲染模式,给用户更好的体验。
示意图左边和右边分别是面对开发者提供的研发支撑和运维支撑服务,可以帮助开发者更有效率的开发小程序,在上线后也提供众多的工具帮助开发者管理和运营线上的小程序。
运行时架构
小程序编程模型是分为多个页面,每个页面有自己的 template、CSS 和 JS,实际在运行的时候,业务逻辑的 JS 代码是运行在独立的 JavaScript 引擎中,每个页面的 template 和 CSS 是运行在各自独立的 webview 里面,页面之间是通过函数 navigateTo 进行页面的切换。
每个 webview 里面的页面和公共的 JavaScript 引擎里面的逻辑的交互方式是通过消息服务,页面的一些事件都会通过这个消息通道传给 JavaScript 引擎运行环境,这个运行环境会响应这个事件,做一些 API 调用,可调到客户端支付宝小程序提供的一些能力,处理之后会把这个数据再重新发送给对应的页面渲染容器来处理,把数据和模板结合在一起来,在产生最终的用户界面。
浏览器内核
小程序在 web 上的渲染引擎是浏览器内核,作为小程序的核心组件,经过多方面的考虑,我们采用的是 UC 提供的浏览器内核,UC 的同学在浏览器内核的性能、稳定性和兼容性上做了大量的工作,比系统提供的 webview 提升了不少。
- 稳定性:crash 率只有系统 webview 的三分之一到五分之一;
- 兼容性:不存在各种系统 webview 上的兼容性问题;
- 性能:针对内核启动逻辑,v8 引擎 codecache 深度优化,使得 js 代码解析和编译的时间减少 40% 左右;
- 工具:提供了丰富的工具保障 UC 内核的稳定性和性能;
下图是 UC 内核的稳定性保障体系:
同时 UC 内核针对内存做了大量的优化,主要分为几方面:
1.图片内存:针对低端机,做了更严格的图片缓存限制,在保持性能体验的情况下,进一步限制图片缓存的使用;多个 webview 共用图片缓存池;全面支持 webp、apng 这种更节省内存和 size 的图片格式。
2.渲染内存:Webview 在不可见的状态下,原生的内存管理没有特殊处理,UC 内核会将不可见 webview 的渲染内存释放;渲染内存的合理设置与调优,避免滚动性能的下降和占用过多内存。
3.JS 内存:更合理地处理 v8 内存 gc,在启动时延时执行 full gc,避免影响启动的耗时。
4.峰值内存管理:系统在内存紧张时,会通知内核,UC 内核能够在系统低内存时释放非关键内存占用的模块,避免出现 oom,也避免过度释放带来的渲染黑块;在部分 oom 的情况,规避原生内核主动崩溃的逻辑,在内存极低的情况,部分功能不可用,而不是崩溃。
2、性能体验
Google 的统计表明,页面打开时间超过 3 秒用户会流失 13%,超过 6 秒用户会流失 60%。反过来,打开时间每减少 1 秒可提升 27% 的转化率,给用户带来更好的用户体验一直是支付宝努力在做的事情。
支付宝 app 不同于社交类的 app,属于低频类的应用,所以在小程序的优化方式上会不同于高频的应用,由于高频的应用长期在系统层面是活跃的状态,所以高效的优化方式就是预加载,在后台把小程序相关的资源尽可能的提前加载好,在用户使用小程序时可以快速的启动起来。
而对于低频应用,更多的是冷启动,所以在这种情况下,我们更多的是从技术的角度来优化每一个环节的性能,在小程序用户体验上可以达到高频应用,下面我会分享几个我们性能优化方面的工作。
render 和 worker 交互优化
为了优化小程序的交互体验,目前传统的做法是把 render 层和 woker 层在两个不同的线程里面执行,可以让页面在渲染的时候不会因为业务逻辑的执行而产生卡顿,提升了渲染的速度。
通常的做法是在 webview 里面运行 render 的代码,然后另起一个线程运行 serviceworker,当 serviceworker 需要更新 dom 的时候把事件和数据通过 messagechannel 发送给 render 线程来执行,当业务需要传递到 render 层数据量较大,对象较复杂时,交互的性能就会比较差,因此针对这种情况我们提出一个优化的解决方案。
该方案将原始的 JS 虚拟机实例 (即 Isolate) 重新设计成了两个部分:Global Runtime 和 Local Runtime。
- Global Runtime 部分是存放共享的装置和数据,全局一个实例。
- Local Runtime 是存放实例自身相关的模块和私有数据,这些不会被共享。
在小程序里面需要做的事情包含两个部分:
1. 轻量级的 js 线程替换 serviceworker 来执行小程序业务逻辑的代码;
2. 更高效的 worker 层和 render 层交互方式。
对于这两个目标我们重新设计了现有的 JS 虚拟机 V8,提出了一种优化的隔离模型(Optimized isolation model, OIM)。OIM 的主要思路是共享 JS 虚拟机实例中与线程执行环境无关的数据和基础设施,以及不可变或不易变的 JS 对象,使得在保持 JS 层逻辑隔离的前提下,节省多实例场景下在内存和功耗上的开销。尽管有些实例间共享的数据会带来同步的开销,但是在隔离模型下,本方案所共享的数据、对象、代码和虚拟机基础设施都是不可变或者不易变的,所以很少发生竞争。
在新的隔离模型下,webview 里面的 v8 实例就是一个 Local Runtime,worker 线程里面的 v8 实例也是一个 Local Runtime,在 worker 层和 render 层交互时,setData 对象的会直接创建在 Shared Heap 里面,因此 render 层的 Local Runtime 可以直接读到该对象,并且用于 render 层的渲染,减少了对象的序列化和网络传输,极大的提升了启动性能和渲染性能。
首页离线缓存优化
首页的加载和渲染对于冷启动是非常关键的,为了减少用户在首页显示前的等待时间,我们采用离线缓存的方式来优化加载的流程。对于正常的加载逻辑,用户在点击小程序图标后就开始启动的过程,下载并解压小程序离线包,找到入口的页面 index.html,作为参数传给浏览器内核开始加载小程序页面。
在浏览器开始加载小程序页面时会先出现三个圆点的 Loading 页,然后在开始加载小程序的前端框架,在前端框架加载过程中会启动异步的 worker 线程加载业务的 js 逻辑代码,前端框架则继续加载小程序的页面,并渲染出首页展现给用户。
为了尽快的把首页展现给用户,在用户首次展现首页后我们会把首页的 UI 页面保存下来,在用户下次重新打开小程序的时候,会首先渲染上次保存下面的首页 UI 页面,把首页展现给用户,然后在后台继续加载前端框架和业务的代码,加载完成后再和离线缓存的首页 UI 进行合并,给用户展现动态的首页。
由于在渲染完离线缓存的首页 UI 到真正的业务代码加载完成,这个之间的时间大概在 1 秒左右,所以在用户看到首页并做出反应时动态的首页已经合并完成,并可以对用户的操作做出响应。
在实现首页离线缓存这个特性中,我们面临两个技术上的挑战:
1. 首页离线缓存页面保存的时机
由于小程序启动是受到生命周期的控制,从 onLaunch -> onLoad -> onShow -> onReady -> 用户操作 -> 离开首页这个流程,在这个过程中的任意一个环节都有可能被客观或者主观的原因打断,也就有可能导致保存的离线页面不准确,在启动的时候给用户呈现错误的页面。
所以对于首页离线缓存渲染的效果,保存页面的时机很重要,我们提供让开发者可以配置的时机,配置的时机有两个:渲染完成和离开首页前。对于渲染完成就是首页渲染完成,用户还未执行任何的操作前把页面保存下来作为离线缓存的页面。离开首页前就是指用户在首页执行了一系列的操作后,跳转到其他页面前用户看到的页面保存下来作为离线缓存的页面。
针对离开首页前保存页面的问题,我们设计了一个事件的队列,小程序生命周期中可能对首页改动的事件都会被捕捉,同时放入到一个队列里面,异步线程会定时的从队列里面拿事件,然后延迟执行保存首页的操作,由于经常对浏览器内核执行保存操作,对性能是有影响的,所以会对这些事件进行合并处理,最终会以最后一个正确保存的首页为准。
2. 离线缓存首页和动态渲染首页替换时的闪屏
对于闪屏问题发生的场景是因为缓存页面和真实渲染的页面是分离的,是两个独立的页面,缓存页面是静态的页面,真实的页面是通过 js 动态创建的页面,所以常规的做法就是当真实页面创建完成后替换缓存的页面,这样的情况下就会发生闪屏。
针对这个问题,我们是采用虚拟 dom 来解决,在加载缓存页面的时候把缓存页面放入初始的虚拟 dom 里面,真实页面创建后产生的虚拟 dom 跟缓存页面的虚拟 dom 进行 dom diff,把变化的内容通过 patch 传给浏览器内核,渲染对应的页面,这样就可以只更新局部有变化的页面内容,避免了整个页面的更新,也保证内容的准确性和实时性。
通过实测数据显示,这个优化可以将小程序的冷启动实现秒开。
虚拟 dom 优化
小程序的页面渲染采用的也是业界普遍在使用的虚拟 dom 技术,该技术可以保障在更新页面时只更新变动的部分,提升了更新的效率。不足的地方就是虚拟 dom 也是用 js 来实现,在运算时会大量消耗 cpu,执行的效率不高。
JavaScript 是一种弱动态类型的语言,不同于静态类型的 C 和 Java 语言,相较而言 JS 的运行性能会差一些,因为类型的不确定性限制了 JIT 优化编译器生成代码的质量。
针对这种情况,我们选择 WebAssembly 作为虚拟 dom 的实现方向,WebAssembly 是一个新的 Web 标准,它定义了网页中的可执行代码的二进制格式和相应的类似汇编语言格式。他的目标是使执行代码几乎与本地机器代码一样快,它被用来作为 JavaScript 的补充,以加速 Web 应用程序的性能关键部分,所以我们使用 WebAssembly 技术重新实现了虚拟 dom 这块的核心代码,提升了小程序的页面渲染。
在做这个优化的时候,我们面临 js 代码桥接到 WebAssembly 的性能较差的挑战,因为 js 引擎和 WebAssembly 是两个独立的引擎,他们之间的交互比 js 到 js 的性能要差了不少,针对这个问题,我们参考了业界的一些实现,对 V8 的代码进行了优化,解决 js < -> WebAssembly 交互性能差的问题。
在做这个优化前,我们需要先了解下到底是什么原因导致了 js 和 WebAssembly 交互性能差。由于 JS 和 WebAssembly 是两种不同类型的语言,所以引擎执行过程中遇到语言切换的时候,需要做一些“翻译”工作。而这些翻译工作需要考虑各种情况,需要跳转到一个专门的 trampoline stub 处理。
由于在小程序前端框架的实现代码是 TypeScript 来开发的,所以框架在调用虚拟 dom 的 WebAssembly 的函数时是可以传入具体的参数类型,并且参数的顺序也是固定的,但是这些参数类型和参数顺序在到 js 引擎的时候就丢失了,所以需要做一些额外的“翻译”工作,降低了交互的性能。
我们的思路就是精简这些翻译的工作,在开发层面把框架和 WebAssembly 的交互代码的参数类型和顺序都固定下来,不让其变动。同时我们让 js 引擎支持了参数类型和参数顺序的传入,在编译期把代码的参数类型和参数顺序保存下来,运行期把 js 代码和类型文件一起传给 js 引擎,让 js 引擎可以直接识别该函数的参数类型,这样就可以直接进行参数转化的工作然后调用 WebAssembly 的方法,避免跳转到一个通用的参数转换的 trampoline stub 上。
通过实测数据表明,相比于以前的实现,新的实现代码执行效率有 50% 的提升。
3、开发者工具
支付宝小程序的目标就是为用户提供高品质的服务,这些服务是靠我们的开发者来实现的,所以如何帮助开发者提供提供高品质的小程序,如何保障线上小程序的质量,就是我们一直努力在做的事情。支付宝小程序提供从开发、调试、发布到运维整个链路的工具,这些工具也在不断的完善和增强,让开发者可以更高效的开发出高品质的小程序。
开发者工具 IDE 支持 mac 和 windows 两个平台的运行,通过打通接入研发平台、数据监控、日志收集等系统,进一步为桌面客户端的稳定性提供保障。提供多端开发能力,通过整合通用能力,适配各端差异,帮助开发者实现代码的多端调试运行,同时可以一键发布到多端。
对于开发新手来说搭建一套完整的后端应用过于复杂,涉及到服务器的购买,域名购买,环境配置等等一系列问题,每一个问题都可能阻碍开发者进行下一步操作。为此我们提供了以下两套一站式云服务方案让开发者能够快速高效搭建一套完整的后端服务:
- 云函数,将服务器购买,配置,发布,运维等完全解决,让开发者只用关心自己的代码逻辑部分的编写,并且开发语言是 js,对于前端开发者非常友好。相比云应用,更适合编写轻量级的小程序,但是每个云函数只能在绑定的小程序中调用。
- 云应用,将服务器购买,配置,发布的问题解决,相比云函数,云应用更加灵活,适合编写较复杂的后端应用,并且一个云应用可以支撑多个小程序同时调用。我们提供了两种后端语言 nodeJs 和 java,用户可以自行选择。
小程序云测试服务,可以帮助开发者更全面的检测小程序缺陷,评估产品质量,提高审核通过率。我们提供了一套完整的小程序云真机自动化检测方案,在 IDE 申请云测试服务,执行完成后自动生成测试报告。
云测服务提供“快速检测”、“深度检测” 两种检测模式,满足多纬度测试需求,并且提供性能检测及优化建议,开发者可根据优化建议优化小程序代码,提供更好的用户体验。
线上巡检
目前支付宝小程序拥有几十万生态合作伙伴,随着小程序生态的不断壮大,合作伙伴的数量也在急剧增加,如何对生态伙伴提供的服务形成有效的管控,如何对小程序的质量进行保障,这是我们面临的新挑战。面对这个问题,我们在制定相应技术标准和运营规范的同时,对小程序从入驻到运营,从质量、体验、安全、合规、效能等维度建设了平台化的质量与风险管控能力。
巡检是开发者生态质量与风险保障重要的一环,是识别问题的重要手段。小程序为开发者提供的服务场景非常丰富而且复杂,为解决这一系列问题,我们通过自建识别引擎,并整合蚂蚁、阿里云等多项基础检测单元的服务能力,以“技术 + 一体化 + 平台化”的方式,建设主动巡检(稽查)的能力,即巡检平台。
在平台建设过程中,我们面临的挑战有:
1. 开发者提供的服务场景非常丰富且复杂,如:缴费、医疗、保险、旅行等服务,产品呈现多样化;
2. 小程序提供的是一套前端框架,服务内容是由服务端动态呈现,随时变化,甚至并且千人千面;
3. 小程序技术的灵活性因素,比如允许内嵌 webview,Js 动态加载等;
4. 小程序体量庞大,百万应用,数千万 page 并且不断增长。
巡检平台具有以下特点:
- 功能全面:可用性、内容合规、信息泄露、图片识别、资源流耗问题的稽查;
- 主动检测:主动访问,非被动监控;先于用户发现,尽可能提前将问题暴露;
- 动态渲染:支动态加载和页面渲染;
- 高频巡检:分钟级高频巡检,快速发现问题;
- 多重保障机制:双引擎检测、智能复查、智能恢复;
- 多渠道灵活的预警决策:多渠道、多阶梯预警,工单决策、故障熔断、事后处罚等完备的业务闭环能力;
- 实时数据大屏:巡检、故障、预警决策实时监控;
- 多维数据度量:多视角、多维护数据大盘;
- 智能高效:预警决策环节,加入大数据 + 算法运用,更智能和高效。
小程序巡检平台从上线以来,实现智能化提效 94%,将小程序审核平均时长从 70.59 小时下降到 4.27 小时并实现 0 积压。根据业务诉求进行不同频率的巡检,目前已累计发现和处理了上万个有问题的小程序,提升了小程序线上服务的品质。
4、多端 inside
在支付宝小程序发展的过程中,集团内的 BU 也有很强的诉求需要在他们的 app 端运行小程序,扩展他们的商业场景,增加用户的活跃度。为了避免重复造轮子,大家共享小程序生态,也就需要我们从业务和技术上打通小程序的技术栈,输出支付宝小程序技术,帮助集团内的 BU 具备小程序的运行能力。
目前支付宝小程序正逐步打通阿里生态,开发者可一次开发,阿里各大 app 多端运行,通过小程序连接阿里经济体。小程序对外输出的技术主要包含两个部分,一个是小程序运行时的 SDK,这个需要集成到接入的客户端里面,另一个是小程序的互通,这块需要接入的平台和小程序平台打通,大家共享同一个小程序生态。
小程序 SDK
小程序输出的 SDK 包含两个部分,基础引擎和能力插件,基础引擎是必须的,不可替换的,它承载了小程序的基础能力,包括前端框架和容器的核心能力,以及提供渲染的内核。它提供了小程序核心的运行时和基础的核心组件和 JSAPI,同时提供了能力插件的插件容器,插件容器有良好的隔离性,不会因为插件的 crash 导致容器的 crash,保障了小程序核心运行时的稳定性。
小程序互通
小程序的技术栈除了前端的框架和客户端的运行时,还包括开发者的入驻,小程序的创建,开发和上线,以及后续的运维和运营等管理,为了给用户和开发者较好的体验,小程序的互通是小程序技术输出的必须环节。
平台互通:开发者可以在入驻的开放平台管理投放到所有端的小程序,包括小程序的开发、调试、测试、发布、运维和管理等一系列的工作。
研发平台互通基于支付宝的开放平台能力,统一开发和发布流程,通过门户接入互通、开发者体系接入互通、审核能力接入互通、小程序研发链路接入互通、小程序运行链路接入互通,实现开发者一次开发、多端投放的能力。
运营管理平台通过统一埋点 SDK 提供多端小程序自动化埋点能力,输出标准化行为、异常与性能数据模型,通过数据分析平台,提供小程序在各端实时数据分析能力,并进一步提供用户特征分析、页面分析、用户留存分析支持小程序研发可视化数据自运营的能力。同时也支持小程序研发自定义数据采集点配置,并开放分析管理支持小程序内的用户行为做精细化跟踪、分析,满足除页面访问等标准统计以外的个性化分析需求。
工具平台提供给开发者统一的开发者工具,帮助开发者更好的开发和测试小程序,同时接入的端可以扩展开发者工具的模拟器和特色的 jsapi 接口,方便开发者做端内的特色能力调试。
能力互通:支付宝特色能力支付、会员、卡券、信用等能力可以通过扩展 jsapi 或者插件的方式输出到接入的客户端里面,同样的,接入的端也可以把自己的特色能力输出到小程序联盟的其他端里面,为更多的用户服务。基础能力所有端保持一致,客户端特色能力可以通过扩展 jsapi 的方式集成到小程序 api 里面,也可以通过插件的形式发布到插件市场,用户在使用的时候动态下载插件,屏蔽端上的差异。
用户互通:投放到多端的小程序,需要账号绑定,用户无需登录,给用户提供一致的用户体验。账户通 SDK 通过提供一套完整的注册、登录、授权、账号绑定管理等基础功能来完成多个 APP 间账户互通的功能,并保障整个过程安全可控。通过账户通可以拓展小程序服务覆盖边界,将支付能力、X 服务能力覆盖更多的客户,让服务通行便利、多端权益打通、多端体验统一。
小程序的 inside 技术栈不只是针对阿里集团内输出,也可以输出到外部的 app 商户,帮助 app 商户丰富业务场景,给用户提供更多有价值的服务。欢迎加入支付宝小程序联盟,通过小程序连接到阿里经济体,共同壮大小程序生态。