本文介绍了百度糯米移动 App,在面临多业务和多渠道时架构的演进,以及对接入层和 Hybrid 框架的优化。
背景
随着糯米在生活服务 O2O 平台化战略上的转型,大量的自营/第三方垂类业务需要快速接入糯米,对糯米移动 App 端的架构提出了挑战。在这样的背景下,我们在 2015 年初开始对糯米 App 端架构进行改造。
架构变迁
早期糯米 App 架构
早期糯米移动 App 使用了传统的 3 层架构进行设计,核心基础服务层、业务抽象层、业务层。
在业务需求较少的时候,三层框架能够满足需求,但随着平台化发展,内外部的垂类业务快速增加,问题就开始突显出来:
- 一个研发团队对接所有垂类需求,研发效率成为瓶颈
- 业务需求希望能够快速上线,但受限于 App 固定的发版周期
- 系统复杂度不断提升,模块耦合越来越严重,维护成本也越来越高
组件化架构
为了解决这些问题,我们在原有三层水平架构的基础上进行纵向切分,将垂类业务抽象成为组件,组件与组件之间没有直接依赖,通过框架层进行数据通信降低耦合度。同时配套了一系列开发、调试、测试、发布工具,来实现垂类团队开发组件的自助化和并行化,通过多团队协同作战提升研发效率。
至于组件的 hot deploy,我们是采用了 Hybrid 的方案 (web 开发方式+桥接 native 能力) 来搭建组件运行时,这样也就天然的支持了组件动态更新。
组件服务 SDK 化,多渠道接入
每个垂类业务除了需要接入糯米外,往往还会有需求接入百度系的其他产品线 (手百、地图…)。但不幸的是,每个产品线一般都会有自己的技术标准 (手百 APS 插件等),意味着垂类业务每做一次接入,需要重新做一次开发来适应新的标准。
因此我们做了第三次技术改造,将我们的组件技术 SDK 化,和百度系内部其他产品线的技术标准打通。严格来说这是一次技术融合,而不是架构改造,但收益很大,业务组件开发一次可以无缝运行在手百、地图、糯米、度秘、钱包之内,大大降低了业务方的开发成本。
优化
组件化使得不同团队并行开发各业务,极大加快了迭代效率,但从性能优化角度来看,也带来了一些挑战,如:
- 组件业务代码不由糯米客户端团队控制,前端优化需要由业务团队(包括公司内外)进行,优化推进落地的时间点不容易控制;
- 各业务拥有自己的服务端,服务端域名与 CDN 域名分散,另外服务端相关优化也需各业务 Server 分别改造,同样有推进时间不容易控制的问题;
基于这样的挑战,我们主要从以下两个基本思路出发考虑优化:
- 服务入口收敛、接入层统一优化
- Hybrid 框架优化,最小化运行时开销
服务入口收敛、接入层统一优化
随着组件化高速并行迭代的优势逐渐发力,组件业务已经横跨数十个团队,其中不仅有内部团队,也有第三方团队。除了各业务团队自行开展的性能优化外,糯米组件化框架还通过统一服务的方式进行了一系列优化,框架团队进行的统一优化与业务团队进行的特定优化进度互不影响,使得优化收益尽早的最大化。
1. DNSProxy
DNSProxy 是糯米客户端在 Native 阶段即使用的类 HttpDNS 域名解析服务,相对于传统的 DNS lookup,存在以下优点:
- 策略可控,如预解析、缓存控制、流量调度等
- 批量域名解析
- 防域名劫持
糯米组件化 Hybrid 方案中,组件使用组件化框架提供的 Native 网络请求能力,因此所有组件业务均透明地继承了 DNSProxy 能力。
2. 图片资源代理服务
在前端开发中,围绕图片相关的优化是比较常见的一个课题。糯米组件化框架在这个点上,主要实现了统一图片延时加载和资源代理,资源代理的流程大致如下:
当业务组件发起图片请求时,组件化框架的资源代理会根据运行平台以及系统版本拦截请求进行优化处理,例如转发到图片代理服务,由图片代理服务进行 webp 转换等,这样也有注于域名收敛。
通过资源代理的方式,组件化框架将图片优化任务收敛至一处,并且使得开发者可以更关注业务本身。
3. 统一接入服务
随着业务组件的不断接入,每个业务都会有自己的 ApiServer。这样往往会带来以下几点问题:
- 客户端基于网络服务的升级优化,依赖业务组件 server,成本具大且进度无法对齐。
- 外部团队的 server 的稳定性层次不齐,App 的整体稳定性保障是难点。
- 缺少统一监控,线上问题预警、排查较难收口。
因此糯米组件化方案在组件 Server 和业务组件之间加入了统一接入服务作为解决方案:
在引入统一服务接入层后,糯米在网络侧做的优化策略,业务组件接入糯米后直接使用,透明输出。如协议改造(https、http2.0、长连接)、连接复用、payload 优化;同时也可以为内外部的业务团队提供一些平台能力,提升服务稳定性 (协议卸载、限流、负载均衡、流量调度、实时监控、静态化预案)。
Hybrid 框架优化
1. 离线组件包
传统 H5 页面的静态资源常常需要从远程服务器下载,造成移动环境下最昂贵的网络开销,不同于 H5,组件化 Hybrid 方案引入了组件包的概念,将业务所需的所有静态资源打包成一个组件包文件,并进行离线化版本管理。在用户访问组件页面之前,组件框架通常已将业务组件包下载至客户端,用户访问组件页面时,WebView 加载的实际上均是本地页面和本地资源,大幅提升了页面加载性能。
不仅如此,关键业务组件在糯米客户端版本发布时会预置到 App 中,减少首次下载安装的时间。
2. 容器预热
容器方面,目前百度有手机浏览器内核可以提供支持,业内也存在如 Crosswalk 等方案。但考虑到安装包大小等各因素,糯米组件化目前没有替换内核,仍然使用系统 WebView 作为运行容器,并在其上作自主优化。
在与 Hybrid 容器相关方面,糯米组件框架主要从 WebCore 预热、容器预建、页面预载等角度进行了性能优化。
在一个进程中首次创建 WebView 时,系统会进行一系列 WebCore 相关的初始化,而这段开销长达数百毫秒,避免这段开销能够有效提高感知性能。
一般情况下,组件框架加载页面的时间线大致如下:
可以看到 Hybrid 容器在 Activity 或 Fragment 之后才被创建,并且 WebCore 需要在 loadUrl 调用之后才开始进行静态资源的加载,造成了前面一段时间浪费。
为了节省开销,糯米会在 App 空闲时预先生成一个备用 Hybrid 容器,这个容器主要有两个作用:
- 容器创建开销转移为闲时开销;
- 使 Hybrid 容器脱离 Activity/Fragment 束缚,可提前预载组件;
采用容器预载方案之后,加载页面的时间线大致如下:
初期构思容器预载方案时,我们很自然地使用了 ApplicationContext 来帮助 Hybrid 容器脱离 Activity/Fragment 限制。但在应用中,如页面需要弹出对话框等情况下,使用 ApplicationContext 实际会造成应用崩溃。最终方案中,糯米组件框架依然通过 ApplicationContext 提前加载页面,但在 Activity Context 准备完毕时,Hybrid 容器的 Context 则被替换掉为 Activity Context。
3. 网络接口预载
组件化方案中,静态资源均存在于离线包中,主要的网络请求则是业务接口请求和后续的图片请求。通常情况下,在用户点击进入组件页面和页面发起业务接口请求之间还存在一定的时间开销,例如有 Activity/Fragment 创建、静态资源加载等,以时间线的形式表示则大致如下所示:
可以看到最长路径是由时间片 t1、t2、t3、t4 构成,网络接口与其他本地任务之间并不能达到最大并行化。组件化采用了和糯米 Native 同样的思路,即将网络接口预载,从而大幅缩小网络开销在最长路径上的占比,期望达到如下时间线所示的效果:
然而在用户点击的时间点,组件容器还未加载任何页面相关的静态资源,组件化框架并不了解页面将会发起的具体业务请求。组件化框架在这里建立了一套请求配置规则,开发中业务方在配置文件中根据定义的规则配置请求,在运行时,糯米组件化框架则会解析配置,完成请求参数动态替换,生成请求发出,从而达到未加载页面即可预载接口的效果,整个流程在预载请求快速返回时如下所示:
实际情况的实现比上面的示意图复杂一些,例如上图中当组件 JS 发起请求时,组件框架直接拦截请求,返回了预请求 X 的响应,然而可能会存在此时预请求 X 还未收到响应的情况,这种情况下,组件框架会在预请求 X 的响应收到后再将数据传递给组件页面,依然能够减少整体耗时。
后续规划
架构优化、技术探索我们还在路上。未来我们还会在以下几个方向上继续摸索:
- 运行时 (Hybrid) 的持续优化
- 生态的进一步完善,IDE、调试工具、模版工具
- ReactNative 等技术与糯米组件架构的融合
- 面向外部开发者开放 SDK,支持运行自己开发的组件,糯米现有的垂类组件也能够无缝运行。
感谢徐川对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们。
评论