携程无线离线包增量更新方案实践

2,747 阅读10分钟
作者简介  

赵辛贵,携程技术中心基础业务研发部无线研发总监。 2013年加入携程,主要负责App基础框架研发相关工作,主要关注App开发框架、性能、质量、效率和新技术。

先后负责和参与携程Native、Hybrid和React Native框架设计、工程模块化拆分解耦、Android插件化动态加载、无线持续交付平台等项目。目前重心主要在React Native框架在公司的推广和研发支持、以及公司内部其它独立App的框架和工程架构升级。

携程旅行App中近半数业务页面使用H5 Hybrid和RN技术开发,为了提高页面加载速度和成功率,我们在开发Hybird技术之初就采用了离线包方式,即将RH5 Hybrid或者RN开发的业务代码打包到App中,直接通过应用商店分发到用户终端。如果有业务功能有变更,就通过我们的无线发布系统,将新的业务离线包更新到App中,从而做到随时发布,动态更新。

当然如果都是全量发布,App在启动时就需要下载更大的离线包,增加用户流量的同时加大了下载失败的概率,因此需要考虑好增量更新的方案。

离线包增量更新方案

下面这张简图,介绍了我们是如何设计离线包增量更新方案的:

从客户端的角度,整个流程分为2部分,离线包下载列表获取和离线包文件下载。

现在以一个新的业务模块上线为例,说明下整个流程:

1、创建业务模块

在离线包管理系统里面,新增业务模块,并配置生效的App、版本和环境(开发/测试/生产)。

2、发布业务模块

在离线包发布系统,选择业务代码仓库分支,然后Build,发布。

3、App打包,获取最新基准包

打包系统中,嵌入下载最新离线包功能的脚本,确保每次打包都能将最新的离线包打包到App中,并将每个包的版本信息,一并打包到App中。

4、App启动,获取最新离线包列表

App启动之后,发送本地App中离线包的版本号,以及App的ID到服务端,服务端返回最新离线包的下载路径。

5、App根据离线包列表下载离线包

获取到离线包列表之后,在后台线程中按顺序逐个下载。

6、离线包安装

下载完成,解压,合并,安装;

其中2个系统的功能简单介绍下。

1、离线包管理系统主要负责以下功能:

a、离线包元数据信息管理

元数据包括唯一包名、适用的平台、优先级、负责人、以及业务频道描述。

b、离线包对应的App关系维护

所有的离线包,最终都要打包到App中,考虑到灵活性和扩展性,需要维护离线包和App+环境的关系。

c、离线包的启停用控制

离线包在某个App版本中不在使用之后,可以修改相关配置。

d、App+版本+环境的最新离线包查询列表

打包App的时候,需要将最新的离线包打包进去,这个时候,需要离线包管理系统提供查询最新离线包列表的API。

2、离线包发布系统包含以下功能:

a、拉取选择仓库分支的代码,然后Build

b、发布Build完成的包 (修改数据库中该离线包版本,修改离线包管理系统中最新包的版本)

c、灰度发布、回滚、停用支持

灰度切分流量下发离线包,发现有问题及时回滚,可以随时停止发布,避免影响更多用户。

d、发布数据查询与监控

发布效果监控,可以查看升级百分比,也支持特定用户的对某个发布的结果查询。

工程实践中的问题和解决方案

上面介绍了离线包增量更新方案,但在实际工程实践中还是会遇到了诸多问题,接下来逐个分析。

包依赖管理

携程旅行App有超过100个离线包,每个离线包,都是一个独立的功能或者业务模块,这么多的业务之间必定有相互依赖的问题,要严谨的解决该问题,需要引入类似node的包管理机制,但这样的解决方案对我们来说太重。

因此我们设计了一套简单的依赖管理规则,来解决这个问题:

1、使用数字标识离线包的优先级,数字越小,优先级越高;

2、优先级越高的包,先下载安装;

3、优先级相同的离线包,下载顺序和发布顺序一致;

实际使用过程中,我们只定义了2个优先级0和100。0为框架类,公共业务类的离线包,100为业务功能的离线包。业务依赖框架正常,极少有业务之间的强依赖,偶尔有的时候,业务之间协调好发布顺序即可。

动态差分

为了让用户能够尽快下载离线包,我们需要尽可能的减小每个离线包的大小。

这个时候,就需要采取差分算法,计算最新发布的包和原始打包到app中的基准包之间的差量,然后下发给App。我们的方案是使用bsdiff做差分:

1、服务端拿最新包和打包到App里面的基准包计算差量,生成patch;

2、客户端下载到该patch文件后,和打包到app里面的原始文件merge,生成最新包;

看起来很完美的方案,并且业内大多做离线包的差分都是采取这种成熟的方案。但是实际效果并不完美,我们发现偶尔会出现300多KB大小的离线包在差分之后,生成的包有100多KB。

经过反复测试,我们发现zip文件解压之后比较里面的变化文件,生成diff文件,然后将diff文件生成一个zip包,比直接bsdiff计算2个zip包生成的diff,会小很多。基于这个测试,我们对bspatch做了一些改进:

上图可以看到,生成patch包的时候,只zip进去变更过和新增的文件,同时,对每个变化过的文件,生成了一个hash文件,这样可以确保客户端将diff文件patch到原始文件之后,能验证文件是否完整, 这一点非常重要,因为bspatch执行的时候,不会因为文件内容合并不正确而返回patch失败。

下图是某个版本中发布的4个差分包,传统bsdiff方案和我们的优化方案使用后,最终实际下载包的大小对比,可以看出优化效果非常明显,

另外,因为打包到不同App定制/渠道包里面的各个离线包版本不同,因此差分包需要动态生成。客户端获取到最新离线包列表之后,会先通到CDN下载,如果CDN没有,再回源到源站服务器,这个时候触发动态差分包生成,生成完成之后,再推到CDN上。

App端离线包下载

主要由以下机制进行保障:

1、重试机制

离线包下载在网络状态不好时,会有下载失败的情况。为了减少网络因素导致的失败,需要增加重试策略,比如最多下载3次,第一次失败,隔15s重试一次,第二次2次失败,隔30s再试一次,3次失败则会终止继续下载。

2、签名校验

文件下载完成之后,需要检查文件是否被篡改过,因为离线包里面都是代码,必须保证代码的正确性,建议在下发离线包列表的时候,下发该文件的签名,下载完成之后,校验签名是否正确。

3、定时轮循

最初我们的离线包列表是在App启动之后,会获取一次,然后下载,当时经常会有反馈离线包下载不及时,不重启就下载不到最新版本。为了解决这个问题,也考虑过使用服务器推送的方案,但是成本较高。因此简单为第一次离线包下载完成之后,每隔10分钟再去服务器查询一次,是否有最新离线包列表,如果有,继续下载,这样保证了发布之后,用户网络正常情况下,最多间隔10分钟左右,离线包就可以被更新。

离线包的使用、安装和加载

离线包是打包到App中,发布到应用市场,用户下载安装的,因此在本地使用之前,需要先解压安装。从服务器端下载到的新离线包版本,也需要解压安装才能使用。下图是进入某个离线包业务的流程:

以业务A为例,简单说明离线包的下载安装过程:

1、业务A的离线包下载成功之后开启子线程合并下发的离线包Anew.7z和App包中的原始包Abase.7z,合并成功之后保存在离线包的工作目录,例如A的工作目录为Awork,将成功合并的目录命名为Abak,不可直接覆盖该离线包的工作目录A_work,因为A业务可能正在被使用;

2、进入A业务,如果发现有合并成功的离线包文件Abak,先使用该文件覆盖Awork, 覆盖成功之后,加载页面时候,需要清空缓存,reload页面,加载失败回滚A_work目录。

发布控制

灰度发布:离线包发布直接到达用户终端,为了确认发布的功能对用户带来的影响线,需要先观察一部分用户行为和数据。这样发布系统就需提供灰度发布功能。我们采用的默认设定规则是10分钟10%,30分钟50% 1小时后达到100%。

发布回滚:发布的包如果有问题,可以回滚到先前版本的包。

停止发布:发布的包如果没有生效,也没有副作用,为了尽可能的减少影响,可以直接停止这个包的发布。

端到端监控

离线包的下载、安装是一个复杂的过程,并且都是在App运行后台进行,用户并无感知,为了能了解发布的状态和结果,需要完整的数据采集和监控。

对于生产环境:可以以一次下载为起点,安装完成为终点,当做一个事务。每次事务完成,都记录一条日志。这些日志上报后,后端就可以根据这些日志进行监控,实现对端到端的离线包更新效果进行监控或告警。

对于测试环境:可以在测试包中保持完善的文件日志,记录离线包下载更新的每一步。另外,可以提供Debug工具,查看每个离线包业务的版本号,例如下图:

小结

通过持续的迭代优化,这样一套无线离线包更新方案,已经稳定的运行在携程旅行主App和集团其它子App中。从目前生产统计数据看,我们的离线包在iOS和Android平台分别达到99.8%和99.5%的下载安装成功率,希望我们的方案可以对读者的类似业务有所参考。

【推荐阅读】