极致的 Hybrid:航旅离线包再加速!

极致的 Hybrid:航旅离线包再加速!

去年(2015)四月份,在 QCon 北京大会上分享了阿里旅行 Hybrid 实战经验,作为航旅在 Hybrid 方向探索的一个收尾。当下集团内的重量级 App(手淘、钱包等)在 H5 容器建设上成长迅速,形成了宏大的技术体系,到去年双十一,H5 容器所承载的流量已经远远超过了有限的 Native Page。就航旅来说,H5 承载的流量是 App 的至少四倍。无疑,处在应用层的 Web 技术栈,以其独有的运行时环境(WebKit)、普适的技术标准(W3C & ES 5、6)以及极具优势的研发灵活性,成为面向 UI 和交互无线研发的不二解决方案。而 H5 离线技术体系的逐渐成熟,让 H5 和 Native 的融合达到前所未有的深度。

资源离线的思路简单、场景复杂,最复杂的就是 H5 活动页面的离线化。今天跟大家分享的就是航旅去年在 H5 活动页推包体系建设的一些实践。

就加载性能来说,资源离线和发版频度是一对矛盾。活动页结构简单,不具备复杂的业务逻辑,但变更极其频繁,对时效性要求很高,这种更新频繁程度在双十一期间表现最甚。我们在去年 4 月份立项的独角兽项目,集中精力建设活动页推包体系,试图克服这一对矛盾。

资源离线是加载性能优化的“二向箔”

Mobile Web 在弱网提速的唯一的办法就是坚决杜绝不必要的(运行时)网络请求,即除了 Json 格式的动态数据和其携带的商品配图之外,不应再有其他网络请求(埋点请求除外)。所以,HTML 和业务数据之间必须解耦。在此基础上,航旅的信鸽平台力争完成这四项任务:

  1. 定时程序:紧随页面结构(HTML)变更进行推包
  2. 增量包:增量包(保性能)和全量包(保安全)必须同时提供
  3. 服务器推:基于长连接的消息推送 & 客户端静默更新
  4. 自动化:推包过程必须自动化,解放人工,搭好页面点击“发布”即完成推包

航旅的页面也会通过 Zcache 在手淘中做离线,但由于缺少对线上页面变更的监控,所以双十一会场页面仍然无法在 Zcache 中缓存 HTML,只能缓存 JS、CSS 和 Img。我们知道,HTML 是否缓存对弱网加载速度影响很大,下图是航旅双十一主会场页面在两个端里 2G 网络下的加载瀑布:

极致的 Hybrid:航旅离线包再加速!

可以看到,手淘 App 里的渲染时机被 HTML 网络请求推后了,而且有一个更新后的 CSS 文件请求,不适时宜的阻塞了渲染。

定时程序动态推包

所以,针对去啊 H5 容器,我们写了两个程序来弥补 Zcache 在缓存动态页面方面的不足,

  1. o2o 在线资源抓取程序:基于 phantomjs 解析资源
  2. grunt-inc-offline 增量包计算器:基于 git-diff 的增量包运算

当然离线包生成器也是必不可少的,我们用信鸽平台将它们这样整合起来:

极致的 Hybrid:航旅离线包再加速!

  1. o2o 定时程序监听线上页面变更,将其所携带的资源(HTML、CSS、JS 和部分图片)抓取下来
  2. 增量包计算器会计算好与之前若干版本之间的增量文件,配合包生成器将增量包逐一构建打包,同时生成好每个增量包的 Diff Json
  3. 调用 Clam 命令通过 Gitlab 将资源包部署至 CDN,以备手机端更新。
  4. Gitlab 仓库 的更新会触发一个 Hook 脚本,调用 tSync 服务器的接口,来通知资源变更
  5. tSync 服务器沙箱完成消息封装,包括了第二步生成了的 Diff Json 文本
  6. tSync 长连接将消息指令下发给手机终端
  7. 手机终端拼好资源文件链接,从 CDN 将增量包更新下来,随后执行 Diff Json中的指令,完成包的更新。

其中,获取增量包时,手机会将本地离线仓库版本带上,和远端 tSync 消息中的更新包版本一起拼成资源包的 URL(一个真实的增量包 URL),格式形如:

http://g.alicdn.com/trip/h5-op2op/{$线上最新版本}/{$本地包版本}.zip

另外,Diff Json也很干净,包含了“新增”、“删除”、“更改”,这样可以让客户端来删除旧文件,减少新包覆盖后的冗余。平台的易用性上,信鸽平台的操作界面里很贴心的加上了“快速下线”功能,即一旦发现离线包更新到达率不够,可以立即做离线包下线,端的虚拟域会自动切换到线上页面。

极致的 Hybrid:航旅离线包再加速!

可以看到,整个系统关键在两个平台的衔接,即信鸽平台和 tSync Server。两者的分工很明确:

  1. 信鸽平台面向在线的 URL 完成资源抓取、构建和部署。
  2. tSync Server 完成消息推送和动态更新。

只要思路捋顺,整个推包的逻辑设计并不难,难的是这些模块的实现是否可靠健壮,其中较为核心的模块就是 o2o 定时抓取程序,o2o 定时程序是@弘树 开发的独立的命令行脚本,将资源离线的过程中,需要根据文件内容来决定文件路径的哈希值,即只要文件内容不变,离线后的引用路径就不变,这样就比较容易由 git-diff 程序来计算增量文件,毕竟文件是否属于“新增”应当看内容是否有变化,而不应该根据文件名(或者版本号)的不同来判断。

由于 o2o 是基于 phantomjs 来抓资源,所以,以 o2o 为原型我们衍生出了 o2o-capture 项目,将线上页面完全静态化到本地,用来做 TMS 系统挂掉的容灾备份方案,也是棒棒的。

有了完整的外围设施,手机端就可以聚焦在文件 IO 的性能优化上了,之前也介绍过,航旅 H5 容器用多层保障来加速 Local File 的文件读写,一方面避免不必要的 IO、另一方面将资源池管理和运行时的缓存管理隔离开,确保各自程序任务聚焦、高效,下图是手机端的两个重要进程:

  1. 资源预加载进程:在实际访问页面之前,将资源预加载到缓存池并更新 Cache Map
  2. 创建 WebView 进程:只聚焦本地资源读写,别的什么也不干

所以手机端 touch 到网络的环节收敛到了两处,第一,Package Update Controller,第二,WebView 本身必要的网络请求:

极致的 Hybrid:航旅离线包再加速!

最终,在定时程序的帮助下,我们可以放心的将 HTML 也离线到端,而不必担心更新不及时的问题,配合高性能的 H5 容器,做到秒出就是自然而然的事情了。我们来看 2G 下去啊 App 和 手淘加载航旅会场页的速度对比,显然,去啊 App 的离线更干净彻底,不管首次加载还是二次加载,速度都是很可观的:

极致的 Hybrid:航旅离线包再加速!

从全网的性能数据看亦是如此,下图是航旅双十一预售阶段的数据,10月28日主会场页在去啊、钱包、手淘里各种网络情况下的 DomReady 时间统计。在 2G 网络下,支付宝和手淘基本都卡在 HTML 请求阻塞上。

三端在三个网络下的 DomReady 时间对比(单位秒),结果是显而易见的:

极致的 Hybrid:航旅离线包再加速!

应当说明的是,这是在手淘和钱包没有条件缓存活动页 HTML 资源的情况下拉的数据,依照上面的思路,我们也写了一个外围工具 zcache pusher,来半自动化推送动态更新的页面,理论上手淘也是可以做到 2G 离线加速的

此外,Zcache 较早前就有计划和新版 TMS 尝试打通,让离线操作自动化起来,期待能尽快看到进展,和我们一样,Zcache 也会面临这个问题:“发版频度和离线包更新到达率”的问题。那么,影响包的到达率的因素都是什么呢?

发版频度 vs 离线包更新到达率

信鸽的整个体系对性能的考验不是来自架构设计,而来自硬件瓶颈,尤其是当定时程序检测到线上页面频繁部署(比如双十一期间,航旅会场页面每天更新频率峰值多达四十多次每天),频繁推包会带来两个问题:

  1. 手机终端是否能及时更新到最新版的离线包
  2. 如果要保证更新足够及时,频繁静默更新又会大量占用手机的流量

显然,这两点是相互矛盾的。我们既希望用户尽可能及时的更新到最新版的离线包,又不希望这种频繁推送过多占用手机流量。这种情况下,增量包只能确保用户及时更新的情况下,尽可能少的占用流量,而无法完全杜绝。也就是说,不管是手淘、钱包还是航旅 App,目前离线包的推送策略仍然过于“全量”,稍显粗暴,用户“无条件”接受所有更新。即目前所有端都做不到对用户行为的精准判断,做不到用户需要(订阅)A,我就给他推包 A。所以,信鸽平台和 tSync Server 都存在很大升级空间。

So,在这次双十一执行过程中,在航旅 App 里,这一对矛盾究竟如何表现?下图是10月28日会场页面最新一次推包后的客户端更新比例,左图是推包 1 小时后,右图是推包 2 小时后。

极致的 Hybrid:航旅离线包再加速!

天哪,看看今天执行了多少次推包!

可以看到,相比于过去传统的线上页面部署,离线包的部署时效性显然慢一些,不够 100% 所见即所得。这也是为了换取加载速度不得不做的牺牲。但基本上每次推包 3 个小时候可以完成 90% 以上的更新。受用户所在网络和打开时机等因素影响,散落在手机端里的离线包旧版本的碎片化依然非常严重。不管用了多少优化手段,频繁修改页面、频繁推包都会对页面体验带来负面影响。

所以,对于时效性很强的页面,比如凌晨零点的发布需要切会场的场景,需要页面即时部署即时生效。若要走离线有两个方案可以选择:

  1. 提前(至少四个小时)推离线包,需要在页面中写好定时切换的逻辑
  2. 提前推消息指令,先将离线页面从端删除,在切换时同时执行线上部署和离线推包,去啊客户端支持虚拟域, 在线离线页面 URL 在容器中保持一致,在保证线上页面可用的情况下,逐步扩大离线包在端的覆盖率。

但不论哪个方案,我们都不可能像过那样部署线上资源那样轻松了。如果需要更高的时效性 + 更快的加载速度,则必须适度减少琐碎需求变更,降低推包次数。从这个角度看,决定性能的因素这里已经主要不在技术上、而在工程上了。

今年手淘(天猫)双十一主会场页面也遇到同样的状况,本来 Weapp 可以非常优雅的将 H5 Page 转义成 Native Page,理论上是可大大提速的,但还是为了满足页面动态性更新和个性化配置,不得不引入一些额外的网络开销,这些网络开销不合时宜的阻塞了布局的渲染,在弱网里的影响更大。我们来看去啊 App 里首次进航旅主会场(H5)和手淘中首次进天猫主会场在移动 3G 下的加载速度:

极致的 Hybrid:航旅离线包再加速!

可以看到,H5 页面是逐级加载的,Native Page 是等待请求完成后瞬时渲染的。所以,不管是 H5 还是 Native,只要是应对这种频繁修改部署变更的活动页面,都会遇到加载上的瓶颈。信鸽平台是解决这对矛盾的一个缓冲,但根本上还是要从控制页面灵活性角度来求解。

但是,这种离散性未必都是坏事,它能适度缓解安装包体积膨胀的压力。

安装包体积极限

大家相信吗,手淘和猫客的安装包都过百兆了。这已经到了一款电商 App 包体积的极限。在上一篇文章中也提到,目前客户端技术架构在面对新功能的井喷时显得力不从心。H5 是一个方案,将资源和内容置于远端,但又会极大稀释客户端的体验。离线包技术就很合事宜的弥合这对矛盾。

但这显然不够,和钱包 App 的早期一样,航旅 App 在构建安装包时就会“预装”一部分重要 H5 资源,但面临高速迭代的产品需求,这个缓冲区是远远不够用的。目前,钱包和手淘显然已经将这个缓冲挤占完全挤占:

极致的 Hybrid:航旅离线包再加速!

以去啊 App v6.0 为例7 M 的 H5 离线包承载了将近 40% 的功能性页面 和 100% 的活动页面。所以只要你的 H5 页面质量够高,H5 离线包的体积消耗是非常划算的。这也是在航旅 BU 正在发生的事情,即便是在交互极其华丽的“去啊 App 6.0 行程”项目中,PM 还是不断从前端团队抽调同学参与一些关键页面的研发,一方面大家已经不担心 H5 体验的瓶颈,另一方面,“前端同学搭界面真是快!!!”

所以,高性能 H5 容器配合高效的离线包推送体系,再加上高质量的 H5 代码(通过 Clam 工具来保证),做出全网络“无缝秒出”的体验是完全没有问题的(体验视频):

极致的 Hybrid:航旅离线包再加速!

大家还可以感受下航旅这次双十一的无线狂欢城和跑步游戏在3G网络中的表现

经过上面这些折腾,最终就达成了我们希望的结果:

  1. 快速的页面研发
  2. 灵活的部署、持续交付
  3. 无缝秒出
  4. 划算的功能体积比
  5. 时效性强的活动页也能做到高效离线化!

这就是航旅无线技术团队正在做的事情。好像很爽的样子,下面我们来说但是吧。

另一种混合

由于 WebView 对 App 进程来说是一个沙盒,所以 H5 页面的内存分配和 CPU 分片都是 WebView 独立完成,前端代码因为普遍缺少细致的内存管理,所以内存泄露时有发生,以至于H5 容器一定程度会影响 Crash 率。比如手淘 Android 就会限制打开的 WebView 堆栈的个数来减少内存压力。

ReactNative 是一个解法,就像我跟@小马 开玩笑时讲的,“前端同学用 React 搭了一个 App,就好像 Java 开发同学用 Bootstrap 搭了一个后台界面一样兴奋”。和 Bootstrap 一样,ReactNative 是业余 Native 开发同学的脚手架,是无法做出面向 C 端的产品的,只能做一做“阿里内外”这种量级的应用。

还有一个方向就是重新设计动态的 Native 页面布局,集团内代表性的就是鸟巢、 Dynative和今年双十一手淘在尝试的基于 Web Component 在三个端同构渲染的 Weapp,尽管对于复杂列表还是偶有性能问题,但至少整个 UI 的渲染已经脱离了 WebView,内存更加可控。我们也必须承认,在运行时性能上,不管是 H5 翻译 Native 还是原生 Native,都要强过 H5 容器的,尤其是在超长复杂列表的渲染上。相对于鸟巢和 Dynative 的全 UI 渲染,航旅也在规划 H5 和 Native 的交叉渲染技术的尝试,总体思想是借助 JSCore 和一个删减版的 Webkit 内核来渲染翻译好的 HTML 片段,页面交由 Native 来拼装:

极致的 Hybrid:航旅离线包再加速!

这个方向我们也是刚刚起步,希望能和兄弟 BU 们一同共建。

小结

航旅在 Hybrid 方向上做的事情,并非要证明航旅 H5 比其他端快,毕竟应用场景不同(比如手淘就有选择的忽略 3G 和 2G 网络)。但从航旅和集团各 BU 的混合开发实践来看,其实大家都在朝着一个方向努力,Native 期望获得 H5 快速开发和部署能力、H5 期望获得更快的速度和更高的硬件调用权限,两个思路:

  1. H5 容器技术(得益于 Clam 工具的保障,手淘、钱包、去啊 各自的容器,已经做到90%相互兼容):
    1. 优势:独立的 WebView,对前端开发友好,天然获得多端兼容的特性
    2. 劣势:外围体系化建设难度大
    3. 需要克服:一,硬件调用能力,通过桥协议解决;二,秒出保证,通过“离线包体系” + “精心编程的 H5 页面”解决
  2. H5 代码 Native 化(鸟巢Dynative、ReactNative、Weapp,互不兼容):
    1. 优势:H5 代码编译成二进制代码直接运行,天然的秒出体验
    2. 劣势:对前端开发极其不友好
    3. 需要克服:一,阉割版的布局能力,通过增加对 CSS3 标签的支持来解决;二,无法做到多端兼容,通过限制 H5 语法来解决

可见,两个思路都各有取舍、各有克服,都很难完美,从实践效果来看,H5 容器更加适合哪些对多端部署有要求的 BU,航旅就是一个典型。而 H5 的 Native 化方案更加适合独立客户端的一些私有场景,比如支付宝和天猫。所以要根据自身需求来选择技术路线。

我作为一名传统的前端工程师,从 PC 时代转战到无线,从去年航旅开始无线研发模式的探索以来,我也很幸运的尝鲜各种 Native 技术,从开始的好奇,到现在一大堆体系和工具的落地,一系列探索让我自己脑洞大开,也打破了之前固有的编程理念,这两年前端技术被颠覆的如此剧烈,让人有点不敢相信自己的眼睛。我想一方面,PC Web 开发的迅速衰落为无线前端技术快速崛起带来契机,另一方面,无线技术快速崛起,带来的不仅仅是技术体系的混合(H5 和 Native),更多的呼唤人的混合。我们很欣慰的看到一大堆前端同学在研究 ReactNative,另外一大堆 Native 同学在研究 W3C 和 HTML5。打破技术边界、拥抱无线技术的 All In,充满好奇,用怀疑一切的眼睛去看待自己固有的技术理念,投身并享受新一轮的无线技术变革,我想,或许正是因为此,使得阿里无线端技术体系伴随业务的增长,不断走向百花齐放、走向多元的吧。

To Be Continue...



一些 Q & A:

Q:现在 Wifi 是最多的用户网络,为什么要纠结于弱网用户?

A:首先,我们希望在最严苛的环境中考练技术,再者,广大(地级)市、县乡这些 Wifi 覆盖率低的地区,我们都亲身体验过那里 4G 和 3G 是什么网速。

Q:这种强混合的研发模式,对前端技术框架有什么影响?

A:影响是颠覆式的。前端的模块化编程过于依赖 Loader 和上层的模块规范,而 Mobile Web 里是不是还强依赖 Loader 要打一个问号,一方面,Loader 本身太重,另一方面,Loader 是在运行时去组织资源依赖加载,显然会抢占宝贵的运行时资源,资源的组织和加载应当下沉交给更底层的 H5 容器或者工具去解决。

Q:H5 容器即是浏览器,怎么去平衡 Web 的加载性能和运行时性能?

A:这篇文章主要说的就是加载性能的解决方案。运行时性能我们也有一揽子的最佳实践,另开一篇介绍。

Q:基于 H5 容器毕竟不像浏览器,怎么快速打离线包调试?

A:这方面手淘、钱包 都有最佳实践。航旅通过工具可以实时构建 zip 离线包,直接拷贝到手机即可。目前是最土,也是最有效的办法。

Q:航旅怎么做到 7 M 的离线包完成 App 里40%的功能页面?

A:我们化了一整年来做体积瘦身的优化,另开篇介绍吧。

Q:上面提到的离线数据从哪里来的?

A:航旅自己提供了一个非常全面的打点方案 tracker,目前部署上抽离的还不是很够,但基本上我们想要的关键数据可以比较精确的取到了。大部分数据可以通过鱼眼来查看。

该文章来自阿里巴巴技术协会(ATA)精选集

上一篇:制作一个禁止修改文件名的Excel文档


下一篇:ruby批量修改文件创建时间