MRN 简介
MRN(Meituan React Native) 是基于开源的React Native框架改造并完善而成的一套动态化方案,在开发体验上基本能与原生 RN 保持一致,同时从业务需求的角度满足从开发、构建、测试、部署、运维的工程化需要。解决了一系列痛点问题:客户端版本审核及更新效率低、Android/iOS/Web 三端开发技术方案不一致、公共需求重复劳动、需求排期不敏捷、集成成本高等。目前 MRN 已接入美团数十个 App,在核心框架及生态工具上有超过百位代码贡献者,(每天)的总 PV 超过 1 亿次。
在项目成立之初,MRN 使用当时最新的 React Native 0.54.3 作为基础版本,然后进行了一系列的改造。React Native 官方稳定版已经升至 0.60.5,对 MRN 页面的质量性能、开发者体验都有了巨大的提升,包括 JSI 替换桥进行 JS 和 Native 通信、JS 引擎替换、React Hooks 等功能。最近,MRN 也做了一些升级适配和深度优化,在相关基础建设、融合过程、优化手段等方面,我们进行了很多的探索和思考,后续这些内容会陆续放出,希望能给大家一些启发。本文主要分享美团外卖 App 在业务实践和技术探索过程中的经验。
背景
美团外卖自 2013 年创建以来,一直处于高速发展期。美团外卖所承载的业务,也从单一的餐饮业务,发展到餐饮、超市、生鲜、果蔬、药品、鲜花、蛋糕、跑腿等十多个大品类业务。伴随着业务的快速发展,我们深切地感受到 3 个痛点:
(1)业务要求快速发版试错和原生迭代周期长
美团外卖业务长期使用 H5+Native 的技术栈。由于原生应用需要依托于应用市场进行更新,这样的话,每次产品的更新必须依赖用户的主动更新,使得版本的迭代周期变得很长,无法实现快速发版快速试错的诉求。H5 虽然具备随时发布的能力,但受限于内核的影响,平台兼容性并不好,性能也较差,而且调用 Native 的能力也受限,往往只能满足一定范围内的产品需求。
(2)有限的客户端研发资源无法满足日益增长的业务
业务的快速发展对客户端的开发效率不断提出挑战。如何通过技术手段,在有限的客户端人力资源下,支持更多的业务需求,解决有限的研发资源跟不断变大的业务需求量之间的矛盾呢?试想,如果能逐渐地磨平 Android 和 iOS 开发技术栈带来的问题,支持一套代码在 2 个平台上线,理论上人效可以提升一倍,支持的业务需求也可以提升一倍。
(3)业务持续增长带来的安装包的大幅增长
业务的快速迭代,功能的持续增加,美团外卖客户端安装包也在持续增加,从 2018 年到 2019 年,已经累计增长 140%。如果没有切实有效的技术手段,安装包将变得越发臃肿。
业务层面的这些痛点,也在不断地督促我们去反思:到底有没有一种框架可以解决这些问题。2015 年,Facebook 发布了非常具有颠覆性的 React Native(简称 RN)框架。从名字上就可以看出,这属于一种混合式开发的模式。RN 使用 Native 来渲染,JS 来编码,从而实现了跨平台开发、快速编译、快速发布、高效渲染和布局。作为一种跨平台的移动应用开发框架,RN 的特性非常符合我们的诉求。我们也在一直积极地探索 RN 相关的技术,并且基于 RN 在脚手架、组件库、预加载、分包构建、发布运维等多个维度进行了全面的定制及优化,大幅提升了 RN 的开发及发布运维效率,还打造了更适应于美团的 MRN 技术体系。从 2018 年开始,美团外卖客户端团队开始尝试使用 MRN 框架来解决业务层面的一系列问题。经过一年多的实践,我们积累了一些经验和结论,希望相关的经验和结论能够帮助到更多的个人或技术团队。
外卖混合式架构
上图是外卖 App 引入 MRN 后的架构全景图,接下来我们会从下到上、从左到右逐步介绍:
最下层是 Android/iOS 系统服务层,因为 MRN 是跨端的,所以需要引入这一层。相对单一平台来说,由于 MRN 的引入,整个 App 的架构不可避免地需要考虑 Android 和 iOS 平台本身的差异性。
倒数第二层是平台服务层,这一层相对与单一平台来说,并没有太大区别。
再往上一层是 MRN 基建层,这一层的工作主要是:(1)尽可能地屏蔽 Android 和 iOS 系统的差异性;(2)打通已有的平台基建能力,让上层业务不能感知到差异。
再上一层是业务组件层,这一层相对于单一平台来说,区别不大,主要是增加了 Android 和 iOS 的 RN 容器,同时业务组件是可以被 RN 调用的。
继续往上是 MRN 接口层,该层的主要任务是尽可能地屏蔽 Android 和 iOS 组件之间的差异,让上层页面使用的 RN 接口保持一致。
最后是业务层,这一层是用户可直接接触到的页面,页面的实现可以是 Android/iOS/RN。
左上角是研发支撑,主要包括代码规范、代码检查工具、Debug 插件、准入规范、准入检查工具、代码模板插件等。这块相对于单一平台来说,主要的差异体现在:由于编译器和语言不同,使用的工具有所区别,但工具要做的事情基本是一致的。
左下角是测试支撑,主要包括 UI 自动化测试、自测覆盖率检查、AppMock 工具、业务自测小助手、性能测试、云测平台等。这块相对于单一平台来说,基本也是一致的,主要的差异同研发支撑,主要是语言不同,使用的工具有所区别。
右上角是发布支撑,主要包括打包 Bundle 和 APK、打包检查、发布检查、发布 Bundle 和 APK 等。这块相对于单一平台来说,保持了打包发布平台的一致性,区别在于:需在原有的基础上,增加 MRN 的打包发布环节。
右下角是运维支撑,主要包括基建成功率监控、业务成功率监控、线上问题追踪、网络降级等。这块相对于单一平台来说,保持了一致性,区别在于:需在原有的基础上,增加 MRN 的监控运维。
研发测试支撑
外卖业务 MRN 组件架构
RN 官方对双端只提供了 30 多个常用组件,与成熟的 Native 开发相比,天壤之别。所以我们在开发的过程中面临的一个很重要问题就是组件的缺失。于是,MRN 团队基于 RN 组件进行了丰富,引入了一些优秀的开源组件,但是源于外卖业务的特殊性,一方面需要业务定制,另一方面部分组件依然缺失。所以为了减少重复代码,提升外卖客户端 MRN 的研发效率,建设外卖组件库就变得非常有必要。
上图是我们外卖组件库的架构图,最底层依赖 Android 和 iOS 的原生服务;然后是 MRN 基建层,用于抹平 Android 和 iOS 系统之间的差异;再上一层则是外卖组件库及其依赖,如平台组件库和打包服务,组件库分为两类:纯 JS 组件和包含 JS 和 Native 的复合组件。再上一层则是 Android 和 iOS 的 MRN 容器,它提供了上层 Bundle 的运行环境。整个组件的架构思路,是利用中间层来屏蔽平台的差异,尽可能地使用 JS 组件,减少对原生组件的依赖。这样可以有效地减少上层业务开发时对平台的理解。接下来,我们主要讲一下 WM-RN 组件库:
如上图所示,WM-RN 组件库主要包含三部分:RN interface、RN Native 组件、外卖 RN JS 组件。RN Interface 主要包括 Native 组件的 Bridge 部分和 Native 组件在 JS 侧的封装,封装一层的好处是方便调用 Native 暴露出的接口,也可以用来抹平 Android 和 iOS 系统间的差异;RN Native 组件分为 Android 和 iOS 两端,依赖各自的业务模块,为 RN 提供外卖 Native 的业务能力,如购物车服务、广告服务;外卖 RN JS 组件则是纯 JS 实现,内部兼容外卖 App 与美团外卖频道间的差异、Android 和 iOS 平台间的差异,依赖现有的 MRN 组件库和外卖开源Beeshell组件库,减少组件的开发成本;从工程的物理结构来看,建议将 Native 组件、RN Interface 放在一个仓库进行管理,主要是因为 Native 与 JS 侧的很多通信都是通过字符串来匹配的,放在一起方便双端与 JS 侧的接口统一对齐,发布时也会更加方便。目前,外卖组件库已经扩展了几十个业务组件,支持了线上近百个 MRN 页面。
Native/MRN/H5 选型标准
目前,美团外卖 App 存在三种技术栈:Native、MRN、H5,面对业务持续增长和安装包不断变大的压力,选择合适的技术栈显得尤为重要。H5 在性能和用户体验方面相比 Native 和基于 Native 渲染的 RN 相对弱一些,所以目前大部分 H5 页面只是用来承载需求变更频繁、需要即时上线的活动页面。那么 MRN 和 Native 的界限是什么呢?当有一个新的页面产生时,我们应该如何做取舍?通过实践,我们逐渐摸索了一套选型规则,如下:
Native 选型规则,强交互(同时存在 2 种及以上手势操作),无法用二元函数描述的复杂动效,对用户体验要求极致的页面,类似首页、点菜页、提单页等。
对于强交互或强动画,MRN 技术栈支持效果不理想,不建议使用。其他情况下,建议使用 MRN。
H5 适用于需要外链展示的轻展示页面,比如向外投放活动的运营页面等等。
具体选型细节可参考下表:
发布运维支撑
发布运维是一个成熟的软件项目中非常核心的部分,它保证了整个项目能够高效且稳定地运转。建立一个稳定可靠的发布运维体系是我们建设整个外卖 MRN 技术体系的重要目标。但发布运维的建设上下游牵扯了众多基建:拥有一个合理的工程结构对发布运维来说至关重要。如果工程结构臃肿且混乱,将会引起的一系列的权限问题、管理维护问题,这样会严重制约整个发布运维体系的效率。所以 MRN 的工程架构演进优化也是发布运维体系建设的重要组成部分。
MRN 分库 & 工程结构演进
业务分库
任何一个大型、长期的前端技术项目,良好的工程结构都是研发发布支撑中非常核心的部分。从 2018 年 10 月份,外卖正式启动 MRN 项目以来,面临涉及近百个 MRN 和几十人参与的大规模 MRN 应用计划。从项目初期,我们就开始寻找一个非常适合开发维护的工程结构。
在最开始的时候,我们的目标是快速验证及落地,使用了一个 Git 库与一个 Talos 项目(美团自研发布系统)去承接所有页面的开发及发布工作,同时对权限进行了收缩,保证初期阶段的安全发布。然而随着页面的增多,每个版本的发布压力逐渐增大。发布 SOP 上的三大关键节点权限:Git 库操作权限、Talos 的发布权限、美团自研的线上降级系统 Horn 权限,互不相关,负责人也各异,导致发布时常因各个节点的权限审批问题,严重阻塞效率。
随着项目的大规模铺开,我们的页面数量、合并上线次数与初期已不可同日而语。为了解决逐渐臃肿的代码仓库问题及发布效率问题,我们将庞大而臃肿的 RN 库根据业务维度和维护团队拆分成了 4 个业务库,分别是订单业务、流量业务、商家业务、营销业务,并确认各库的主 R,建立对应的 Talos 项目,而主 R 也是对应 Talos 项目的负责人。同时所有的主 R 都有 MRN 灰度脚本的管控权限。这样一来,MRN 的工程结构和 Native 的工程结构完全对齐,每个责任人都非常明确自己的职责,不会来回地穿插在不同的业务之间,同时业务库任意页面的发布权限都进行了集中,RD 只需要了解业务的负责人,即可找到对应的主 R 完成这个业务的所有相关工作。
工程结构
在项目初期,对于每个库的工程结构,美团内部比较流行的工程结构有两种:一个是适合小型业务开发的单工程多 Bundle 方案,另一个是相对更适合中大型业务开发的多工程多 Bundle 方案。
单工程单 Bundle 方案
顾名思义,单工程单 Bundle 方案的意思就是一个前端工程承载所有的业务代码,最终的产物也只有一个 RN Bundle。通过入参决定具体加载哪个页面。
对于业务不多,参与人不多的团队,使用单工程单 Bundle 的方式即可快速完成开发、发布。因为通过一次发布就可以完成整个发布的工作,但是带来的弊端也是不可接受的:因为所有业务都耦合在一起,每次更新都会“牵一发而动全身”,增大了问题的隐患。如果多个业务需求同时提测的时候,在团队配合上也是一个极大的挑战,因为新版本号会覆盖旧版本号,导致两个需求提测时会出现相互覆盖的情况。所以我们在立项之初就排除了这种方案。
多工程多 Bundle 方案
多工程多 Bundle 方案的意思就是一个 Git 库中存放了多个页面文件夹,各个文件夹是完全独立的关系,各自是一个完整的前端工程。拥有自己独立的 MRN 配置信息、package.json、组件、Lint 配置等(如下图所示)。每个页面文件夹都输出一个独立的 RN Bundle。
相比于单工程单 Bundle 方案,多工程多 Bundle 方案将页面进行解耦,使之基本可以满足中大型 MRN 项目的需求。在外卖 MRN 项目初期,一直都使用着这样的工程结构进行开发。但是我们也为之付出了相应的代价,即每个页面的依赖都需要对应 RD 去维护升级,依赖碎片化的问题日趋严重。同时在工程级别的管控,如统一 Lint 规则、Git Hook 等也变得更加复杂。
多工程多 Bundle 方案 => 单工程多 Bundle 方案
随着外卖 MRN 页面规模以及参与人规模的进一步增大,多工程多 Bundle 方案的缺点日益凸显。特别对于那些前端技术底子相对薄弱的团队来说,依赖管理问题会变得很头疼。在这种情况下,单工程多 Bundle 的方案就应运而生了。
核心思路也很简单:观察一下单工程单 Bundle 方案和多工程多 Bundle 方案的优缺点可知,单工程单 Bundle 依赖管理方便的优点主要来自于“单工程”,而多工程多 Bundle 的业务解耦的优点主要来自于“多 Bundle”。所以结合这两种工程方案的核心优点,就可以设计一种新方案:单工程多 Bundle。即用一个工程去承接所有的页面代码,但是又可以让每个页面输出独立的 RN Bundle 来保证互不影响。其实,这种方式类似于 Native 一个静态库的管理,如下图所示:
通过分析 MRN 的打包原理可知,MRN 通过一个配置文件配置了一个 Bundle 的所有业务信息以及 mrn-pack2 的打包入口。所以我们只需要让配置文件支持多份 Bundle 信息的配置,通过打包命令与参数选择正确的 mrn-pack2 打包入口,即可打出我们最终所需要的业务 Bundle。如下图所示:
核心优势:
整个工程采用一个 package.json,管理业务库中所有的依赖。这样可以有效地解决各自页面去管理自己依赖时,必然产生的依赖版本碎片化问题,避免同一依赖库因为版本不一样,而导致页面表现不一样的问题。
从依赖角度去规范各自页面的使用工具规范,如 A 页面使用某一种三方库来实现某种功能,B 页面使用另一种三方库也实现了同一种功能,单一依赖管理就可以从库依赖的角度强制做技术选型,减少各个页面的实现差异,从而降低维护成本。
让业务同学可以更加专心地开发业务代码,不用关心复杂的依赖问题,大大提升了开发效率。
实现了工程级别的管控,如 Pre-Commit,脚手架方案管理将变得更加便捷。
这种工程组织形式也成为了 MRN 工程结构的最佳实践,而且美团内部也有多个团队采用了这种解决方案。目前已支撑超过几百个页面的开发和维护工作。
外卖发布运维体系
下图展示了我们的发布运维全景,共覆盖了开发交付、线上发布、线上监控、有效应对、复盘改进等五大模块。接下来我们会逐一进行介绍。
(1)开发交付
开发阶段,需求 RD 完成开发,提交到 Git 库的发布分支。对应的业务库主 R 角色(通常由 RN 经验较丰富的工程师来承担)进行 CodeReview,确认无误之后会执行代码的合并操作。顺便说一下,这也是外卖 RN 质量保障长征路的第一步。
(2)线上发布
合入发布分支之后,就可以正式启动一次 RN Bundle 发布。这里我们借助了美团内部的 Talos 完成整个发布过程,Talos 的发布模板与插件流水线规范了一次发布需要的所有操作,核心步骤包括发布准备(Git 拉代码、环境参数确认、本次发布说明填写)、发布自检(依赖问题检查、Lint、单元测试)、正式打包(Build、版本号自更新)、产物上传测试环境(测试/线上环境隔离、测试环境进行测试),双重确认(QA、Leader 确认发布)、产物上传线上环境等等。
产物上传线上环境,实际上是上传到了美团内部的 CD 平台–Eva。在 Eva 上,我们可以借助 RN Bundle 的发布配置去约束发布 App 的版本号、SDK 版本等,以及具体的发布比例及地区,去满足我们不同的发布需求。最终执行发布操作,将 RN Bundle 上传到 CDN 服务器,供用户下载,完成整个发布流程。
(3)运维监控
发布之后,运维是重中之重。首先我们的运维难点在于我们的业务横跨两个平台——美团 App 与外卖 App。由于它们在基建、扩展、网络部分都存在差异,所以我们选取指标的维度不仅要从业务出发,还要增加全局的维度,来确保外卖平台 MRN 的正常运转。基于这个层面的思考,我们选取了一系列 RN 核心指标(在下面的章节会详细列举),进行了全方位的监控。目前外卖客户端,已经做到分钟级监控、小时级监控和日级别监控等三档监控。
在监控手段上,首先我们使用了美团开源的Cat告警平台(这部分已经通过 Talos 插件完全自动化配置),确保当核心指标在线上出现波动、异常的时候,相关 RD、QA 以及业务负责人可以及时接受到报警,并由对应的 RD 主 R 负责,快速进入到“有效应对”的环节。同时为了能够分阶段、更好地处理问题,我们将核心指标报警分为【P1】与【P0】两个级别,分别代表“提高警觉,确认问题”与“大事不好,马上处理”。保障了一个问题出现之后能够及时被发现并快速进行处理。
除了监控报警手段之外,我们还会借鉴客户端高可用性保障的经验。用一些日常运维的手段去发现问题。比如使用灰度小助手、数据日报等手段从宏观角度主动去发现存在隐患的指标,及时治理,避免问题。
(4)有效应对
根据“墨菲定律”:如果事情有变坏的可能,不管这种可能性有多小,它总会发生。即便我们在发布管控和线上监控上做的再充分,线上问题最终还是无法避免的。所以当通过线上告、客诉等手段发现线上问题之后,我们需要及时的应对问题、解决问题,把问题带来的影响降低到最小,并以最快的速度恢复对用户的服务。
在有效应对的方面,我们主要靠两种手段。第一种是存在 B 方案兜底的情况,使用 Horn 灰度配置,关掉 MRN 开关,短时间内恢复成 Native 页面或者 H5 页面继续为用户提供服务,同时通知相关 RD 和 QA 快速定位问题,及时修复,验证并上线。第二种是无兜底方案的情况,CDN 服务器(Eva)上撤掉问题 Bundle,实现版本回滚,接下来的问题定位过程跟手段保持一致。
这两种备案保障了外卖 MRN 业务的整体高可用性。
(5)复盘改进
在以上四个大环节中,问题可能会出现在任意一个环节。除了及时发现问题与解决问题,我们还需要尽力避免问题。这一点主要是靠我们内部的例会、复盘会,对典型问题进行 Review,将问题进行归类,包括复盘流程规范问题、操作失误问题、框架 Bug 等,并力图通过规范流程、系统优化来尽力地避免问题。
在外卖 MRN 项目实施过程中,我们共推动了二十多项规范流程、系统优化等措施,大大保障了整体服务的稳定性。
最后,我们用一张图对外卖监控运维体系做一个总结,帮助大家有一个全局的认知。
混合式架构流程
针对混合式架构的流程,目前外卖技术团队采用的是正常双周版本迭代流程+周迭代上线流程。MRN 页面既可以跟版迭代,也可以不跟版迭代,这样可以有效地减少流程的复杂度和降低 QA 的测试成本,而周迭代流程可以有效地利用 MRN 动态发版的灵活性。混合式开发和原生开发应尽量保持时间节点和已有流程的一致。这种设计的好处在于,一方面随着动态化的比例越来越高,版本迭代将可以无限拉长,另一方面从双周迭代逐渐演变成周迭代的切换成本也得到大幅的降低。详细可分为下面几个阶段:
评审阶段
业务评审阶段在原有的流程上,增加了技术选型阶段。在技术选型时,明确是否会存在需要使用 MRN 页面的情况,如果页面可以完全不涉及到 Native 部分即可完成,就可以进入周迭代的发版流程。如果需求用 MRN 实现,但是又涉及到 Native 部分,仍然走周迭代的上线流程。除正常开发需求的时间外,RD 需综合考虑到双端上的适配成本。
开发阶段
客户端以周维度进行开发,每周确定下周可提测的内容,根据提测内容是否为动态化的业务、下周是否在版本迭代周期内,决定跟版发布或周发布。
提测阶段
提测前,为了保证 MRN 页面的提测质量,RD 首先需要按照 QA 提供的测试用例提前发现适配问题。提测时需要在提测邮件中注明:(1)提测的 Bundle 名称和对应的版本号 ;(2)标明哪些组件涉及 Native 模块 ;(3)依赖变更情况,如是否升级了基础库,升级后的影响范围;(4) 重点测试点的建议。
上线阶段
MRN 由于其可动态发布的特性可以跟版发布,也可不跟版发布,但上线时间和灰度时间节点都保持了一致。不过版本还是动态发版,都默认周二上线,周四全量。
跟版发布:默认只对当前版本生效,需在双周迭代三轮提测节点,周二当天将 Bundle 上线服务器,MRN 的灰度开关全量打开。通过周四 App 的发版灰度比例来控制 MRN 的灰度比例,上线时需配置报警和灰度助手监控,实时掌握 MRN 的线上数据。
不跟版发布:也同样以周四作为全量发布窗口,Bundle 需在周二时上线指定线上版本,指定 QA 白名单。测试通过后,在周三按照比例逐步灰度,周四正式全量,和跟版发布一样,上线时需要配置报警和监控。
架构总结
引入 MRN 后,相对单平台而言,架构层级上,我们增加了 2 个 MRN 中间层去屏蔽 Android 和 iOS 平台、原生组件之间的差异。这样做的目的是为了让上层业务开发者可以很快地使用框架进行业务开发,完全不用关心平台和组件间的差异。通过引入 MRN 技术栈,带来的好处很明显:
(1)使用 MRN 实现的页面理论上可以实现一套代码,部署到不同平台上,开发效率得到大幅度提升。
(2)采用 MRN 框架,无论是加载性能还是页面滑动性的用户体验上,都会比原来 H5 的方式要好。
(3)部分页面具备了快速编译、快速发布的能力。
但一个硬币总有两面,混合式架构增加了架构的复杂度,使得原本只要考虑一个平台的事情,逐渐转变成需要考虑三个平台,另外 Android 本身具备碎片化的问题,这使得混合式架构的适配问题较为突出。当出现问题时,我们的第一反应由“这是什么问题”变成“它是否存在于两个平台,还是只在一个平台上?”、“如果仅在一个平台上,是在原生代码还是 React Native 代码出了问题?”、“历史版本的 MRN 是否存在问题,是否需要修复”、“修复的效果在 Android 和 iOS 上的表现是否一样”,这些问题增加了定位和修复工作的复杂性。另外,MRN 的适应场景也是有限的,并非所有的业务和页面都适合改造成 MRN,如何做选择也需要进行有效的判断,从而增加了决策成本。
针对上述问题,我们的建议是:
(1)减少分歧:
在研发、测试、发布和运维环节,MRN 的页面尽可能对齐 Native 原有的环节,减少团队理解的成本。
在 Debug 开发环境下,利用页面浮层提示技术栈使用情况;Release 环境下,利用工具、MRN 自动化报表,及时的让开发同学明确知道是 Native 页面还是 MRN 页面,减少确认。
MRN 页面尽可能地避免原生组件的使用,而使用纯 JS 代码实现,供 MRN 页面使用的原生组件的需要高质量的提供,减少下层组件的问题。
默认只修复当前的版本,出现严重问题时才考虑修复历史版本,减少多版本带来的复杂度提升。
(2)技术栈明确边界
做好 Native 和 MRN 技术栈使用的边界,尽可能用简单的选型标准,让合适的场景选用合适的技术栈,从而保证业务整体的可用性,让用户体验依然如初。
(3)单技术栈转向多技术栈团队
培养全栈工程师,当团队的同学都具备 iOS、Android 和 MRN 多个技术栈能力时,将会有效地提升开发的效率,短期内可选择 iOS、Android 和 MRN 工程师结伴编程的策略。
可用性体系
正如在“监控运维”章节中所讲到的那样,线上运维是我们工作的重中之重。这个章节我们就讲一下我们对于监控指标的选取。鉴于外卖业务的特殊性,除了美团的外卖频道之外,外卖业务还需要运行在独立的外卖 App 上。如下图所示:
外卖 App 经过多年的发展,目前已逐渐成为一个平台级应用,承接了 C 端、闪购、跑腿等多个业务。与美团 App 相比,它们之间在很多基础建设、扩展、网络部分都存在差异。所以在监控核心指标的选取上,我们除了保证 C 端 MRN 业务在美团以及外卖两端的高可用性,还需要保证外卖 App 平台本身基建的稳定性,从而保证运转在外卖 App 上所有 MRN 业务的高可用性。
而从监控的大分类上来讲,我们分为了【可用性指标】以及【性能指标】,它们分别关注业务本身的可用性,以及页面的性能与用户体验。接下来,我们就依次进行讲解。
MRN 可用性指标
可用性指标也是我们关注的关键指标,它直接决定了我们的 MRN 页面是否能够正确、稳定地为用户提供服务。通过 MRN Bundle 加载全景,我们可以确定整个包加载的几个关键节点。可以说,MRN 业务的可用性就是取决于这些关键节点的成功率。
下载链路
MRN 是一个动态化的框架,所有的 MRN Bundle 都是从 CDN 节点上远程下载。所以下载成功是 MRN 业务可用的先决条件。有些普通的业务方是不需要关注这个指标的,而外卖 App 可能会因为网络库基建,出现启动下载线程拥堵、DNS 劫持等问题,所以我们把下载成功率作为外卖 App 监控的全局指标。目前,外卖 App 的下载成功率长期稳定在 99.9%左右。
加载链路
加载链路可以细分为初始化引擎部分以及业务 Bundle 加载部分。前者跟基建有关,代表从引擎创建到加载完 Common 包加载成功这段的成功率。这部分主要依赖 MRN SDK 的稳定性,从我们的日报上看,稳定性基本保持在 99.99%以上。而业务 Bundle 加载成功率(MRN PageLoad Success),是 MRN 页面创建到业务视图内容渲染过程中,没有发生错误的比例。它与跟拉包时网络情况、MRN 框架稳定性和业务 JS 代码都有关系。这也是我们关注的核心指标,因为它直接决定了我们某个页面是否可以渲染成功,所以我们把这个指标同时列为了外卖 App 监控告警的全局指标与单 Bundle 告警的指标。目前,整个外卖业务的 Bundle 加载成功率稳定在 99.9%以上。
使用链路
Bundle 加载成功之后,页面成功被渲染。但是在使用的过程中,可能会因为 JS 代码,Native 代码的 Bug 出现 JS Error、Native Crash 等问题,这样给用户带来的直观反馈就是应用闪退、页面白屏等,造成了服务的不可用。所以在使用链路上出现问题率,基本也可以直观反映出一个 RN 页面的质量以及它当前的运行状况。
在使用链路上,我们主要关注的是 JS Error 率、JS Error 个数以及页面退出成功率(MRN PageExit Success)等。
JS Error 很好理解,由于 RN 是由 JS 驱动的框架,所以一个页面的 JS Error 率基本上可以综合反映出一个页面的可用性、稳定性或者基建的稳定性,故我们同样把这个指标同时列为了外卖 App 监控告警的全局指标与单 Bundle 告警的指标。我们用上报上来的 JS Error 数量做分子,该页面的 PV 做分母,计算一个页面的 JS 错误率,当 JS Error 个数短时间内极速升高或者 JS Error 率有大幅上升时,就会触发我们的 JS Error 告警。目前外卖大盘的 JS Error 率保持在万分之一左右,略低于 Native Crash 率。
页面退出成功率(MRN PageExit Success),理解起来不如前面的指标那么简单,因为它表示的是用户在退出 MRN 页面时,业务视图内容已成功渲染的比例。它会包含所有已知和未知的异常,但是用户进入页面后快速退出的场景,也会被错误的统计在其中,因为用户退出时可能页面尚在加载中。相比于 JS Error,它是一个更加综合的指标,基本上涵盖了加载失败、渲染白屏、使用时出现错误等多个异常场景,基本上可以反映出一次 MRN 业务的单次可用性,相比于之前的指标会更加严格。我们把这个指标同时列为了外卖 App 监控告警的全局指标与单 Bundle 告警的指标。我们希望它永远能保持在 99.9%以上,否则就会触发告警。目前外卖大盘的 MRN PageExit Success 基本稳定在万分之三左右,我们最终的目标是希望稳定在万分之一左右。
最后,我们希望通过两个“脑图”快速回顾一下外卖全局监控与单业务监控关注的核心指标。
MRN 性能指标
除了可用性指标,性能指标也是我们重点关注的内容。如果加载时间过长,就会大大增加用户离开页面的概率。而页面卡顿,也会影响用户在使用层面的体验,从而引发客诉或者业务损失。
根据 Bundle 加载全链路图,我们也可以把性能指标分为两个大类,一个是加载时耗时与使用时性能指标。前者主要关注 Bundle 从 Load 到渲染整个链路的耗时,后者主要关注使用时的性能指标,在这里主要是指页面的 FPS。
加载链路耗时
如上述所说,整个加载链路分为引擎初始化的时间以及 Bundle 本身加载及渲染的时间的时间。
引擎初始化的时间在整条链路上占比是最长的,因为初始化的时候会加载比一般业务代码大得多的 CommonJS。经过观察,这部分的时间总体表现较差,在 iOS 上 50 分位和 90 分位分别是 0.3s 和 0.7s。在 Android 上表现更差,50 分位和 90 分位分别是 1.3s 和 1.8s。不过目前 MRN 已经使用了预加载方案,即在 App 刚启动时就初始化一个 JS 引擎,等实际使用时,直接复用该引擎即可,大大缩短了首次 Bundle 的整体加载时间。
页面加载时间和页面渲染时间是我们关注的第二类指标,从加载链路图也可以发现,页面加载时间代表从开始加载 Bundle 到 RN 内容渲染成功的整条时间,而页面渲染时间则是它的子集,代表 Bundle 解析完毕,从 JS StartApplication 开始加载组件到渲染出第一帧的时间(iOS 和 Android 的统计口径不同)。区分这两项指标也可以更好地分析整个加载链路上的瓶颈在哪,有助于针对性的做性能优化。
以外卖 iOS 50 分位为例,我们发现页面整体的加载时间在 400ms 左右,JS 渲染时间只需要 100ms 左右,主要的性能瓶颈在 Bundle 加载以及 JS Bundle 的解析部分,这也是我们接下来需要重点研究课题。
使用时 FPS
衡量用户使用体验比较直观的一个指标就是 FPS,较高的 FPS 会让用户更加顺畅地体验功能,完成操作。
目前,MRN 在外卖侧业务总体落地页面复杂度适中,遇到复杂动画也使用了 BindingX 来提升性能。通过监控,外卖侧的页面总体表现良好,在 iOS 上几近满帧,在 Android 上表现稍差,平均在 55 帧左右,较深的视图层级与较低的 JS-Native 的通信效率都是 MRN FPS 的杀手。如何提升 MRN 特别是在 Android 上的页面性能也是我们下一阶段研究的课题。
目前,外卖性能指标 50 分位的性能指标基本满足线上需求,但是 90 分位的表现不尽如人意,特别是较低的 FPS 以及过长的页面加载时间。革命尚未成功,同志仍需努力。
效率衡量
引入 MRN,提升了本地的开发效率,但同时也增加了工程的复杂度,所以总体来说真的能提升实际开发效率吗?在完成几十个 RN 页面的开发后,总结了一些公式,希望可以给其他团队一些结论性的参考。首先设定三个方面去考量:人效提升、代码复用、维护成本衡量,将外卖的所有 MRN 页面加在一起,取平均值,可以得出较为准确的结论:
人效提升计算公式:∑(Android Native 总人日+iOS Native 总人日-RN 总人日)/ ∑(Android Native 总人日+iOS Native 总人日)
代码复用率计算公式: ∑(RN 行数-平台分支判断代码块)/ ∑(RN 行数+Android native+iOS Native)
维护成本计算公式:∑(Android Native 原生总行数+iOS Native 页面总行数-RN 页面总行数)/ ∑(Android Native 页面总行数+iOS Native 页面总行数)
根据页面的交互程度去进一步的划分,得到如下的表格:
如表所示:人效提升的方面,主要取决于页面是否存在复杂的交互,如果页面存在复杂交互,就会不可避免的导致涉及到 Native 的双端原生开发,如部分交互需要 Native Module 实现,最终的人效提升将大打折扣。而对于涉及较少的 Native Module 和展示型的页面,MRN 存在较大优势。但大家会很奇怪这种结果,为什么人效提升会大于 50%?逻辑上 Android 和 iOS 双端复用后,提升的效率理论上最大应该是 50%。这是由于 RN bundle 的热加载极大地节省了 Native 的编译时间,这一部分相对原生开发效率大概能提升 20%以上,使得最终的人效提升大于 50%。双端复用率方面,对于纯展示型的页面,大概率可以完全由 JS 实现,双端复用率可以达到 100%,后续双端只需维护一份 JS 代码即可,极大的降低了维护成本。对于一些交互复杂的页面,需双端各自封装对应的 Native Module 实现,复用率下降,维护成本变高。
总结
随着业务的快速发展,工程复杂度的不断提升,在没有外力的情况下,开发效率必然会持续下降。如何在资源有限的情况下不断提升开发效率是一个永恒的话题。美团外卖客户端通过借助美团基建 MRN,推动混合式架构来提升效率。截至目前,美团外卖业务已经有 60 多个 RN 页面上线,每天的 PV 高达上千万,为用户提供了稳定可靠的服务。
混合式开发带来的不仅仅是技术层面的挑战,更是对团队成员、团队组织能力的挑战。MRN 虽然能够做到跨端,但是有时候仍然需要针对特定平台单独编写代码来解决问题,这就间接要求工程师必须熟悉三个平台,团队也必须有效组织各技术栈人才共同协作,才能真正用好 MRN。
参考文献
作者介绍:
晓飞、唐笛、维康,均为美团外卖前端团队研发工程师。
本文转载自公众号美团技术团队(ID:meituantech)。
原文链接:
评论 1 条评论