大前端从入门到跑路ReactNative

React Native的热更新服务系统设计

2020-04-09  本文已影响0人  brandonxiang

前言

之所以选择RN没有选择Flutter,很大的原因是在于它的热更新能力。随着Flutter在IOS的热更新不明朗,React Native在实战当中还是占据着主导权。因此,React Native的热更新系统设计成为业务系统中的关键,它的作用在于赋能客户端APP,使得APP拥有活力,增加了代码可修复性。相比于H5,页面的显示效率也得到很好的保证。React Native和Typescript的项目开发入门一文已经阐述了如何入门RN项目,这里主要讨论下如何设计一个RN热更新服务发布系统。

热更新系统设计

首先热更新系统需要去存储热更新的RN版本,该版本是自增的,客户端App需要做的事情很简单,在每次启动App的时候,以当前版本为参数拉取并下载最新的RN bundle。客户端有沙盒环境处理,优先读内置版本,带热更新bundle下载完毕才切换到最新RN Bundle。客户端App的逻辑越简单越安全,同样也给服务端的设计带来一些麻烦。首先客户端RN的目录结构ios和android是不一致的,因为历史原因,我们没法统一文件结构,热更新服务需要满足两个平台的RN包文件目录的差异性。与此同时,热更新服务发布系统需要支持ios和android同时热更新的发布,而且支持单独发ios或安卓,目标是为了在多个客户端版本满足这两个平台,减少人为出错的可能性。

客户端版本控制

每个React Native应用都是与APP提供的API强相关的,这一点与Hybrid H5不同,H5与App提供的api相对来说是弱相关性,H5可以采用兼容写法避免旧版本App的一些缺陷问题,但是React Native的JS SDK的版本就是和客户端的SDK版本是需要一一对应的。例如APP的RN SDK是60版本,理论上,JS端RN SDK也要升级至60版本,保证所有功能是对齐的,否则,有些bug是不可控的。同理,RN依赖APP提供的定制API也是随着客户端版本提供的,当然有Hybrid App经验的同学肯定懂得可以用caniuse判断方法可用性或者版本判断等方法来实现“代码层面”的兼容,但是这种“兼容方法”一方面不太“优雅”,另一方面有些情况是没法兼容的,例如一些大变动。

因此,我们需要对客户端进行多版本控制。例如表格,1.0.2到1.0.4采用的是最新版本为3的bundle。而1.0.5和1.0.6采用的是最新版本为6的bundle,1.1.0和1.1.1采用的是8的bundle,这种根据客户端版本号差异化下发RN版本的功能被称之为“断层”(break change),即可避免RN因客户端API差异所导致的潜在问题。

客户端版本 RN Bundle 版本
1.0.2,1.0.3,1.0.4 1,2,3
1.0.5,1.0.6 4,5,6
1.1.0,1.1.1 7,8

公共包与业务包

随着业务线的不断增加和多国家业务的差异化发展,面临着一个问题,就是多个业务包以及公共包抽离的功能。公共包里面应该是一些成熟不变的公共依赖,而业务包则是繁琐的业务逻辑。如果这些功能用webpack来实现非常简单,但是React Native打包是依赖于metro.js, 官方没有提供分包策略。

参考一下各种分包方案,携程的CRN应该是属于比较早期的分包方案,在metro.js没有从React Native分离时候的方案,相对来说比较繁琐。另一种方案则是smallnew/react-native-multibundler,是基于metro.js的分包策略,它的原理很简单,详情查考下图。

公共包的入口是以common.js为入口。打包的同时利用metro.js的API(createModuleIdFactory)记录下公共包的依赖,例如React和React Native等。业务包是以每一个业务包作为入口,在打包的同时,读取依赖并利用API(processModuleFilter)过滤掉公共包的依赖,得到了业务包特定的内容,如果分包得当,业务包的体积不会太大,因为公共依赖已经分离。将静态资源按协商好的位置存放打包即可,这里需要计算每个文件的md5得到manifest,这是为了保证资源没有在传输过程中被改动。客户端加载逻辑是优先加载公共包,在同一个JS上下文内,业务包即可读取公共包的内容(或依赖)。加载完成后90,客户端需要校验静态资源的md5值。

多bundle打包流程

公共包与业务包的加载采用的是预加载,在沙盒中加载一份新的代码。如果在加载页面前已经完成预加载,则会采用新的RN页面,否则采用旧版页面。预加载的优点在于弱网情况下页面加载性能表现。

公共包(也称为Common Bundle),包含了公共JS(或者其差分包)以及图片(或别的静态资源)。将静态资源集中处理的好处在于减少业务包之间的依赖关系,只保留单个业务包和公共包之间的依赖。业务包则是每个独立的。

公共包与业务包

多业务包网络是指根据特定条件(例如国际化中的国家)过滤并组合多个业务包得到业务场景适用的业务包。通过构建的配置文件,进行国家级别(或别的指标)的过滤划分控制。其中存在着公共业务包的概念,公共业务包并非公共包,它属于业务包,不过它主要是公共业务,与其他业务包结合得到适用于特定场景的业务组合。如图,common_biz则是公共业务包,它可以和别的业务包集合成为某个业务场景的RN Bundle,满足不同个条件下的业务场景。

多业务包网络

差分包与静态替换

由于客户端采用的是预加载的策略,差分包是非常好的流量节约的方法,但是部分文件才可以实现差分计算,某些静态资源,例如翻译文件等,能采用静态替换的方法。而且业务包的逻辑差异较大,差分的意义不大。所以,这里更多可以针对公共包进行差分计算,其他资源则会采用静态替换,总体采用混合式下发方式,在保证逻辑清晰的同时,也保证了流量节约的要求。

image.png

灰度与回滚

灰度和回滚是RN的热更新服务系统的两把“救命稻草”,有效避免问题,立即修复问题。

热更新的灰度功能则是利用客户端传递的参数,在特定范围内提供新版本号,也就是最简单的灰度功能。热更新服务会优先去找该用户手机是否符合灰度标准,如果符合灰度标准,则返回灰度范围内最新版本,否则采用普通版本内最新版本。这样,可以满足测试同事提前灰度测试新版本的RN功能灰度版本,如果出现问题,可以有效停止灰度。灰度版本可以加入多种指标(deviceid,uid或者其他用户特征)来划分用户进行灰度,灰度范围可以修改并调整范围。灰度版本也可以一键转换成为正常版本,但是正常版本没法转为灰度版本。该过程是一个不可逆的过程。

灰度与回滚

快速回滚的目标是为了减少生产问题带来的风险问题,需要保证代码回滚的速度,有必要减少打包构建的速度。回滚的逻辑非常简单,创建新bundle版本并指向旧版本bundle,客户端用较旧的版本号请求到新bundle,覆盖掉之前RN包,实现了快速平稳的代码回滚。这里,只有正常版本能够进行灰度,灰度包是不能进行回滚的。

总结

以上的客户端版本控制公共包与业务包差分包与静态替换灰度与回滚等功能,基本上满足热更新发布服务系统的要求,该功能已经在shopee公司使用,有效成功的迭代。以上功能也可以类推到别的发布服务当中,希望能给大家带来一些启发。

题外话

shopee,又称虾皮,是一家腾讯投资的跨境电商平台。这里加班少,技术氛围好。如果想和我并肩作战一起学习,可以找我内推。邮箱weiping.xiang@shopee.com,非诚勿扰。

上一篇 下一篇

猜你喜欢

热点阅读