一、背景
满帮团体挪动团队2018年初开始尝试React Native,通过近三年的倒退,目前曾经承载了大部分的外围业务场景,波及16+的业务模块、200+页面,日均PV数据在千万级。外围业务也应用React Native开发后,咱们脱离了APP发版的限度,对立应用动静发版。相比于APP发版,动静发版频率进步了很多,一周最低两版,有时一周甚至会发5个版本。
2018年上线React Native时,用的是过后比拟新的0.51版本。在后续的版本中,Facebook官网引入了诸多新的个性,比方Hooks、Hermes引擎等等。咱们持续应用0.51版本,这些新个性都无奈应用,而且社区中很多基于更新版本ReactNative的第三方库业务也无奈应用。因而,在应用0.51版本3年之后,咱们决定降级到目前较新的0.62版本。
二、0.62版本改良
在之前,咱们始终应用的是0.51版本,不过,在通过近两年的迭代后,React Native公布了0.62版本,并且0.60以上版本相比之前的版本,性能有了大幅的进步,次要体现如下。
2.1 性能晋升
相比于0.51版本,0.62最大的改良是,android上应用了Hermes作为JS执行引擎,在启动速度、内存占用、JS运行效率上都有十分大的晋升。
2.2 稳定性晋升
从0.51版本到0.62版本,修复了大量功能性和稳定性bug,比方Native 局部的SDK的健壮性失去了很大的增强,例如Android中 ReactHostView,在 show() 和 hide() 的安全性都进行了增强。又如ViewManager 局部, 不非法时间接进行了异样解决。
2.3 社区生态
ReactNative生态次要分为两局部:
React 自身语言个性。
0.51 应用的是 React 16.0, 0.6x 应用的是 16.11.+, 两头新增了很多令人兴奋的新个性,例如 16.2.0 的 Context 、 16.8.0 的 Hooks , 这些无疑是开发的利器!
React Native、React第三方库
社区的第三方库往往两年一个周期进步 React 的依赖版本,例如比拟有名到导航库 :
React-Navigation , 并且减少很多实用的新个性, 例如 ReactNative 外部路由栈开始反对页面间的激活、回到后盾等个性。这在咱们日常开发中十分实用。
2.4 Android端性能
0.6x开始,Android端引入了Hermes引擎,带来了很大的性能晋升。相比于JSC,Hermes最大的改良是反对间接运行JS代码预编译的产物,因而冷启动性能上有很大晋升,同时内存占用也有肯定降落,然而包体积增大了一些。
为了理解分明性能晋升数据,咱们在Android端对JSC和Hermes进行了性能比照测试,测试设施:VIVO X21 RAM:6G。
2.4.1 冷启动耗时数据
从上图能够看出,Hermes+HBC的冷启动耗时相较于JSC+JS降落了50%以上,因而咱们决定应用Hermes+HBC的计划。
2.4.2 包体大小数据
从上图能够看出,HBC二进制包的压缩比显著不如Jsbundle, 体积简直是后者的两倍, 然而这一点能够通过后续的拆包, 端上转化HBC 等伎俩躲避。
2.4.3 代码指令处理速度
面对大量运算以及解析的时候, JSC 性能消退特地重大,而Hermes则绝对安稳, Hermes 和 JSC 耗时比根本在 1/6 左右, 杰出的处理速度对帧率、 动画晦涩度都有很大的晋升。
2.4.4 内存占用
从上图测算进去的数据能够得出两条论断:
1、ReactNative 0.62 内存体现显著优于 ReactNative 0.51,这得益于Hermes的加载机制,不会把整个文件一次性load进内存解析。
2、ReactNative 0.62 的内存抖动较平缓, 这得益于 Hermes 执行的产物是二进制, 而非JS代码,不须要二次转码。
整体操作流程,波及 4 个 ReactInstanceManager 和 5个页面, 节俭了 56 M 内存空间,收益的确可观。
2.5 iOS端性能
从0.51降级到 0.62后,iOS端的JS引擎仍然只有JSC。然而在Jsbundle之外,反对了RAM格局, 采纳RAM 以及 inline计划,冷启动速度和内存都能够失去很大的改善。然而思考到咱们后续要做基座拆分,因而没有应用RAM格局,iOS端仍然应用JSC+Jsbundle的计划。因而,iOS端在内存、冷启动、指令执行速度上并没有太大晋升。不过最近公布的React Native 0.64 版本,官网在iOS上也开始反对 Hemers。
从性能数据上看,Android端性能有十分大的晋升。降级后也能应用hooks等React的最新个性,晋升开发效率,因而咱们决定降级到0.62版本。
三、执行一次无感知降级
3.1 挑战和危险
3.1.1 多部门单干合作
如前所述,ReactNative承载了满帮大部分的外围业务场景,波及16+业务模块,200+页面,50+开发人员。满帮团体的业务在高速发展期,各种业务经营流动的发展都以天为单位计时。业务多、人员多、迭代节奏快、稳定性要求高。须要兼顾多个测试、开发团队以及发版团队等多条线的工作。
3.1.2 SDK降级与高频率发版并行
为了满足快节奏的业务迭代,咱们每周最低公布最低两次动静版本(最高一周能公布5次)。咱们要求技术改造不能影响业务迭代(包含APP版本迭代和动静版本迭代),任何业务需要不能因为技术改造而延期。因而咱们须要 0.51 发版工作和 0.62 降级工作同步进行, 而且要互不烦扰。
3.1.3 升高降级老本
快节奏高频率的发版下,SDK降级不能给业务需要的开发、测试带来过多的累赘,须要把对业务开发、测试的影响尽可能的升高。本次降级作为逾越3年的大版本升级,波及到十分多的 Release Note,咱们须要尽力从底层兼容这些差异性,从而尽可能得升高开发人员批改面以及降级测试人员的回归力度,近而升高各方面的老本。
3.1.4 保障线上稳固
满帮团体外围的两款APP日均UV在500万级别,对APP体验的要求又十分严苛,异样率回升万分之一,都会导致客户投诉率晋升,稳定性保障是降级计划的重中之重。然而,无论咱们计划做得多完满,谁也不能保障不会有意外情况产生。因而咱们须要在第一工夫感知到线上异样,升高影响并及时修复。
本次React Native SDK降级,就像是给一辆以120码高速行驶的重型卡车更换轮胎,稍有不慎就会翻车。
3.2 降级计划准则
3.2.1 低危险
次要次要包含两点:
1.业务危险低:不影响业务需要的迭代。
2.稳定性危险低:不影响线上的稳定性,异样率要管制在极低的程度。
3.2.1.1 公布方案设计
为了满足下面两个条件,咱们决定采纳分批次、灰度的形式公布上线。
分批就是把线上用户分成多个批次,一个批次上线实现后再进行其余批次的。满帮有四款APP:运满满司机端、货车帮司机端、运满满货主端、货车帮货主端,咱们联合业务个性剖析后,采纳的计划是两个司机端第一批上,两个货主端第二批上。
灰度放量当初业内用的十分广泛了,这里不再解释含意,下文中会具体阐明灰度计划的细节。
3.2.1.2 告警和回退方案设计
真正做到低危险,咱们还须要把线上问题扼杀在摇篮, 咱们须要告警机制。降级前的满帮ReactNative曾经有告警机制,因而咱们只须要把 0.62 拆分出一个统计维度独自计算, 因为后期灰度的量少,如果和原先的告警机制复用就很难触发告警条件。
遇到线上顽疾短时间内无奈解决的咱们还须要有降级计划,可能短时间内把线上的 0.62 切换到 0.51, 待问题解决后再切回 0.62.。
3.2.2 低成本:
这里的低成本是指对业务开发、测试的影响尽可能的升高。升高代码批改量、批改难度,从而升高开发投入的人力老本;升高影响范畴,从而放大测试回归范畴、升高回归力度,从而节俭测试投入的人力老本。
3.2.2.1 一套代码
为了升高危险,咱们采纳的是多批次灰度放量的模式公布上线,整个上线周期会继续很长一段时间,在上线期间,各个业务模块都在一直的迭代开发新的需要。也就是说,存量的业务代码和新需要的业务代码,都要兼容两个版本的SDK。要兼容两个版本的SDK,最简略的计划是保护两套代码,别离适配两个版本的SDK,然而这样须要写两遍代码,对业务开发来说是十分惨重的累赘。为了防止这种累赘,咱们提出了一套代码适配两个版本SDK的计划。
3.2.2.2 开发环境切换
一套代码适配两个版本SDK,代码当然要放在一个分支上。在开发业务需要时须要别离在两个版本SDK环境上运行代码,咱们提供了环境切换脚本,能够应用一行命令切换到不同的ReactNative环境上。比方:司机端在线上曾经进行灰度放量了,货主端还没有开始放量,对于须要同时在司机&货主两端运行的代码,开发人员能够通过脚本切换到不同的环境进行开发, 如下图。
3.2.2.3 代码批改扫描
为了进一步升高开发人员的适配老本,咱们开发了专门的脚本工具,能够扫描出所有须要批改的中央,并给出具体的批改办法。
采纳如上的计划,咱们做到了降级危险齐全可控(通过多批次灰度降级管制稳定性),又把开发人员的适配老本将到了最低(通过一套代码适配两个版本的SDK和脚本扫描批改内容)。
四、后期筹备
4.1 API changes梳理
降级之前,须要先梳理两个版本SDK之间的API差别,对0.51到0.62的所有批改有全面的意识。API change分为两种:
- breaking change
- 非breaking change
咱们的办法是暴力浏览了0.51~0.62所有版本的Release Note,整顿出了所有breaking changes,并给每个breaking change制订专门的适配计划。例如AsyncStorage,0.51版本的AsyncStorage用法是xxx,0.62版本的用法是yyy,所以0.51版本的代码和0.62版本的代码相互不兼容。咱们的适配计划是对立采纳咱们本人封装的Bridge[MBBridge.app.storage]。
//npm install --save @react-native-community/asyncstorage不倡议应用// import AsyncStorage from '@react-native-async-storage/async-storage';// 倡议批改为Bridge模式// 依据KEY获取VALUEMBBridge.app.storage.getItem({ key: BootPageModalKey.KEY_IS_SHOW_BOOTPAGEMODAL }).then(res => { if (this.isGuidanceSwitch(res?.data?.text)) { retuReactNative null }})// 存储<KEY,VALUE>MBBridge.app.storage.setItem({ key: Constant.StorageKey.Common.RefeReactNativeame, text: commonStore.refeReactNativeame })
4.2 代码适配计划
公司业务迭代节奏是一周三版甚至更多, 且次要应用的是ReactNative技术栈, 因而如果在如此快节奏的开发节奏下还要同步两套代码(0.51 && 0.62) , 那老本就太高了。因而咱们感觉采纳一套代码可能同时适配 0.51 和 0.62的计划:对于所有不兼容的API,封装一层适配层,屏蔽底层差别。如下图所示:
例如,导航库的适配思路如下:
批改前如下
import { StackNavigator } from "native-navigation"const RootStack = StackNavigator(...)export default class xxxx extends Component<any, any> { render() { retuReactNative ( <RootStack screenProps={this.props} /> ) }}
批改后,ReactNative-lib-protocal 就是咱们的协定层
import { createStackNavigatorCompat, createAppContainerCompat } from "@ymm/ReactNative-lib-protocal"const RootStack = createStackNavigatorCompat(...)export default class StickerPageRouter extends Component<any, any> { render() { const App = createAppContainerCompat(RootStack) retuReactNative ( <App screenProps={this.props} /> ) }}
而后,协定实现层代码如下。
import { NavigationActions } from 'react-navigation';export default class StackActionsCompat {static reset(resetAction: any){retuReactNative NavigationActions.reset(resetAction)}static push(pushAction: any) {retuReactNative NavigationActions.push(pushAction) }static pop(popAction: any) {retuReactNative NavigationActions.pop(popAction) }static popToPop() {retuReactNative NavigationActions.popToTop() }}
这样业务开发同学就能够实现一套代码跑在两个React Native版本上, 节俭保护两套代码的老本。
4.3 脚本工具
这里的工具包含三个:
1、 API查看工具(反对本地 && CI/CD);
2、代码工程环境切换工具;
3、运行环境查看工具。
4.3.1 API查看工具
API查看工具是为了查看那些在 0.51 环境能运行而在 0.62 上不再兼容的API,为了解决这个问题, 咱们针对两个版本泛滥变动点形象出API检测的规定。查看工具应用 Python 脚本编写,开发同学既能够在本地查看(间接运行python脚本或者运行npm命令) 也能够在Jekins打包时启用该查看, 查看成果如下:
4.3.2 环境切换工具
工程环境切换工具则是为了不便开发同学可能不便得切换 0.51 和 0.62 的协定实现层 和 配置文件(package.json、metro.config.js 等) ,这块能够用 Shell 或者 Python 实现。
这个工具保障了业务开发同学可能在一个分支上进行开发,而不必把关注点放在 0.51 和 0.62 的API差别和配置差别上。
4.3.3 环境查看工具
运行环境查看工具则是为在测试环境查看 ReactNative SDK 环境和 Bundle 产物不匹配的状况,例如 0.51 原生SDK加载了 0.62 的 Bundle/HBC, 或者 0.62 原生SDK加载了 0.51 的Bundle 包,从而防止不必要的麻烦以及沟通老本:
五、落地计划
下图是咱们降级打算的一个简图, 整个流程以角色为纬度,划分为四条主线: 开发人员、测试人员、APP版本、 动静版本。每一条主线对应的时间轴在要害工夫点都有具体的Action。
例如:对于业务开发人员(第一条线)来说, 须要在 2020-12-18 日把适配的业务代码合入 dynamic-1231 主公布分支,随后 0.51、0.62 专用一套代码直至整个降级流程完结。
5.1、分批降级
如前所述,咱们采纳的是分批降级的计划,司机端APP第一批上线,货主端APP第二批上线。
Android端为了这次降级,对 React Native 的环境进行了插件化。为了尽可能的管制危险,第一批次Android司机端上线是通过插件动静公布的模式进行的:0.62版本的SDK和HBC的产物同时通过动静降级的形式下发到端上。动静公布的形式能够非常灵活的管制灰度节奏:为了确保稳定性,咱们能够把灰度工夫拉的足够长。而且,咱们的动静降级平台反对线上实时回滚。
咱们基于稳定性的角度登程,决定Android端通过动静降级的形式上线。然而保障稳定性的同时,也不能影响业务需要上线,0.62版本的SDK和HBC产物在线上灰度公布的同时,也会基于0.51版本的SDK和jsbundle产物同步做业务需要的公布。也就是说:0.51 和 0.62 环境须要在较长时间的在线上并行存在。
整个灰度过程中比拟重要的一点是线上环境的性能同步问题:0.51 和 0.62 的产物会别离以各自的节奏公布到线上,不能互相烦扰,然而同时又都必须蕴含所有的业务需要。
例如:0.51 每 2 天一个版本, 而 0.62 灰度周期是 10 天, 因而须要保障用户无论应用的是 0.62 还是 0.51 都须要蕴含最新的性能, 咱们的策略如下:
如上图所示,0.51 和 0.62 的公布是两条平行的线, 0.62 的版本号在设计上要大于 0.51的版本号(保障了0.62 的产物永远不会被 0.51 的产物笼罩),每一次 0.51 业务包的公布都会同步公布一个 0.62 的业务包,因而能够保障以下两点:
1、线上用户应用的性能始终是最新的;
2、0.62产物始终依照本人的节奏灰度, 不会被0.51的产物笼罩。
待0.62灰度全量之后, 线上不会再公布 0.51 的业务包,线上降级切换实现即可。
5.2、CI/CD
因为0.51 和 0.62 的业务 Bundle 须要较长时间的在线上并行存在, 以及两个版本的环境、产物也会存在不兼容的状况。因而除了有测试阶段的环境检测伎俩之外, 还须要在CI/CD阶段插入咱们的一系列校验流程:
1.环境切换;
2.把查看兼容API的python脚本集成到构建过程中;
3.依据产物类型来生成版本号规定:
- 0.51版本号规定 :5.91.xxx.yy
- 0.62版本号规定:5.91.1xxx.yyyy
4.针对Android的hbc产物生成额定的map并上传ftp。
5.3、数据筹备
这里次要是埋点,用来和 0.51 版本的数据辨别开,咱们冀望线上 0.62 版本 产生的数据能精确得反馈降级的真实情况(拜访占比、稳定性), 同时咱们也为 0.62 的数据独自配置了告警策略。
六、线上验证
我的项目上线当前,咱们所要做的就是及时跟进线上数据, 验证后期实验室数据, 以及关注监控数据, 及时调整计划。
6.1 每日报表输入
在62升级包灰度期间,每日会有报表输入, 包含每个模块的DAU、PV、JS异样用户数、JS异样率、SDK异样用户数以及SDK异样率等, 开发和测试同学能够从整体理解线上运行的情况。咱们也提前制订了预案,异样率达到肯定阀值时就进行灰度。
6.2 性能数据输入
以Android端的性能数据为例,最终咱们从线上采集到的性能数据如下,和咱们线下测量的数据根本吻合:
1、包大小
Android端采纳的是Hermes+HBC的计划,打包输入的产物从字符串格局的.jsbundle变为二进制包 .hbc, 包体积因而增长了 45% 以上。这是以空间换工夫的一个优化(JIT 变为 AOT)。
2、冷启动
采纳Hermes+HBC的计划后,指令运行速度大大晋升,冷启动时长时缩小了约64% ,启动速度晋升了近三倍,根本合乎咱们之前的测试和预期。
3、热启动
咱们做了引擎复用机制,引擎创立一次后,会驻留内存,因而第二次启动就是热启动了。热启动的过程相较于冷启动,没有JS代码加载、初始化执行等耗时操作。因而、热启动时长简直没有改善,这也根本合乎咱们之前的测试和预期。
4、内存占用
Hermes引擎执行HBC,省掉了JS代码解释的过程,因而冷启动单页面运行时内存缩小了 30%以上。
6.3 后续批次
第一批次的降级工作根本告一段落, 在这两头会积淀不少最佳实际:上线打算、容错计划、测试计划、性能剖析 等,第二批次货主端的降级工作仅须要在这根底上做微调整, 上线危险和整体打算会晦涩很多。
司机端第一批次对稳定性做了根本的验证,咱们能够确定危险整体可控。因而,货主端第二批次上线时,间接采纳了追随APP发版的形式上线。
iOS端没有插件化机制,因而两个批次都是采纳追随APP发版的形式上线,应用AppStore默认的7天灰度。