外卖客户端容器化架构的演进
好的架构要不断演变,进而去适应业务的发展。美团在移动端上的架构,也经历了组件化、平台化、RN混合化,到现在开始向容器化变迁。容器化架构充分地利用了现在的跨端技术,将动态化的能力最大化地赋予了业务。
作为美团最为重要的业务之一,美团外卖移动端的架构演进是怎样的呢?本文将为你揭开背后的思考、技术细节以及实践。
中秋国庆双节喜临,美团技术团队祝大家节日快乐~~
1. 背景
1.1 移动端跨平台技术的介绍
移动端的跨平台技术不是一个新话题,早在几年前,WebView容器、React Native、Weex、Flutter、小程序等移动端跨平台框架就风起云涌。为什么跨平台这么有吸引力呢?我们设想一下如果可以做到一次开发,多端复用,那么对于公司来说,就可以降低用人成本。对于开发来说,只需要学习一个框架,就可以在Android和iOS双平台上开发。节约下来的成本,可以投入到产品快速验证、快速上线。这对所有人来说都有着极大的吸引力。本节先针对部分移动端跨平台技术进行一些简要的介绍,以便读者能够更好地理解后面的内容。
1.1.1 WebView容器
WebView容器的工作原理是基于Web技术来实现界面和功能,通过将原生的接口封装、暴露给JavaScript调用,JavaScript编写的页面可以运行在系统自带的WebView中。这样做的优势是,对于前端开发者比较友好,可以很快地实现页面跨端,同时保留调用原生的能力,通过搭建桥接层和原生能力打通。但这种设计,跨端的能力受限于桥接层,当调用之前没有的原生能力时,就需要增加桥。另外,浏览器内核的渲染独立于系统组件,无法保证原生体验,渲染的效果会差不少。
1.1.2 React Native
2015年,Facebook推出了React Native,一经推出就备受关注。它的思路是最大化地复用前端的生态和Native的生态,和WebView容器的最大区别在于View的渲染体系。React Native抛弃了低效的浏览器内核渲染,转而使用自己的DSL生成中间格式,然后映射到对应的平台,渲染成平台的组件。相对WebView容器,体验会有一定的提升。不过,渲染时需要JavaScript和原生之间通信,在有些场景可能会导致卡顿。另外就是,渲染还是在Native层,要求开发人员对Native有一定的熟悉度。
1.1.3 Flutter
2018年Google推出Flutter,通过Dart语言构建一套跨平台的开发组件,所有组件基于Skia引擎自绘,在性能上可以和Native平台的View相媲美。Flutter站在前人的肩膀上,参考了React的状态管理、Web的自绘制UI、React Native的HotReload等特点,同时考虑了与Native通信的Channel机制、自渲染、完备的开发工具链。Flutter与上述Recat Native、WebView容器本质上都是不同的,它没有使用WebView、JavaScript解释器或者系统平台自带的原生控件,而是有一套自己专属的Widget,底层渲染使用自身的高性能C/C++ 引擎自绘。但大部分移动端发展到今天,都已经形成了自己的架构,在现有基础上加上Flutter,会形成原有架构和Flutter双平台共存的问题。目前,对新的App来说,是最被看好的跨端方案。
1.2 美团外卖业务介绍
作为中国领先的生活服务电子商务平台,美团致力于用科技连接消费者和商家,提供服务以满足人们日常“吃”的需求,并进一步扩展至多种生活和旅游服务。而作为公司最为重要的业务之一,美团外卖从2013年创建以来,已经从单一的品类扩展到附近美食、水果、蔬菜、超市、鲜花、蛋糕等多品类,从早午晚餐,发展到下午茶、宵夜,中餐、西餐、家常菜、小吃、快餐、海鲜、火锅、川菜、蛋糕、烤肉、水果、饮料、甜点等多种类餐饮。美团外卖可以说是当前电商领域,最为复杂的业务之一。
业务的复杂,给系统架构也带来了不小的挑战。美团外卖业务之所以说是当前电商领域最为复杂的业务,主要源于以下几点特征:
- 美团外卖业务承载在三个App上,美团外卖App、美团App外卖频道、点评App外卖频道。
- 美团外卖作为美团公司重要的用户入口,还承担着流量平台的作用,提供平台能力支撑频道业务的发展,如闪购、跑腿、金融等。
- 美团外卖作为已经成熟的业务,需提供可复用的平台能力,支撑新业务的发展,例如菜大全App的发展。
- 美团外卖作为一个超级业务方,业务内又运营多个方向业务,如流量业务、交易业务、商家业务、商品业务、营销业务、广告业务等。
综上所述,可以发现美团外卖不仅仅自身业务比较复杂,而且对外的角色也很复杂。在美团内部,外卖不仅仅是美团平台的一个频道业务,而且自己本身也是一个平台业务,同时美团外卖还承担着新业务发展的平台角色。这意味着想要支持好美团外卖业务的发展是一件非常有挑战的事情。
1.3 美团外卖移动端历史架构概述
好的架构源于不停地衍变而非设计。美团外卖的架构,历史上也是经历了很多次迭代。由于外卖业务形态不断地发生变化,原有的设计也需要不断地跟随业务形态进行演进。在不断探索和实践过程中,我们经历了若干个大的架构变迁。从考虑如何高效地复用代码支持外卖App,逐渐地衍变成如何去解决多端代码复用问题,再从多端的代码复用到支持其他频道业务的平台架构上。在平台化架构建设完成后,我们又开始尝试利用动态化技术去支持业务快速上线的诉求。如今,我们面临着多端复用、平台能力、平台支撑、单页面多业务团队、业务动态诉求强等多个业务场景问题。下文我们针对美团外卖移动端架构的变迁史,做一些简单的概述,以便读者阅读本文时能有更好的延续性。
1.3.1 组件化架构
早期阶段,美团外卖作为公司的一个孵化业务,在2013年底完成了美团外卖App的1.0版本。随着外卖业务的验证成功和跑通,订单量也快速增长,在2014年底突破了日订单量100万。
随后在2015年2月,外卖以Native的形式接入美团App,成为美团App的一个业务频道。在接入过程中,我们从美团外卖App拷贝了大量的代码到美团App的外卖频道,两个App上的外卖业务代码也分别由两个独立的团队维护。早期外卖业务变化快,App迭代频繁,写代码的方式也比较粗放,同时美团App也处在一个平台化转变的时期,代码的稳定性和质量都在变化和提升当中。这些因素导致了外卖代码内各子系统之间耦合严重,边界模糊,“你中有我,我中有你”的情况随处可见。这对代码质量、功能扩展以及开发效率都造成很大的影响。
此时,我们架构重构的目的,就是希望将各个子系统划分为相对独立的组件,建设组件可以直接复用,架构如下图所示:
1.3.2 平台化架构
如上文所述,大家可以知道美团外卖和美团外卖频道是由不同的团队在维护发展。2015年,公司考虑到业务发展的一致性,将美团外卖频道团队正式归于美团外卖。从组织架构上来说,美团外卖和美团外卖频道,逐渐融合成一个团队,但是两端的差异性,导致我们不得不仍然阶段性地维持原有的两班人马,各自去维护独立外卖App和美团外卖频道。如何解决这个问题?两端代码复用看起来是唯一的途径。另外,随着业务的快速发展,外卖App所承载的业务模块越来越多,产品功能越来越复杂,团队规模也越来越大,如闪购、跑腿等业务想以独立的Native包的形态接入外卖App,还有外卖的异地研发团队的建立,都带来了挑战。这使得我们在2017年开始了第二次架构重构——平台化架构,目标是希望能够支持多端复用和支持不同团队的业务发展。通过抽象出平台能力层、业务解耦、建立壳容器,最终实现了平台化架构,架构如下图所示:
1.3.3 RN混合架构
在平台化架构之后,美团外卖功能持续增加,美团外卖客户端安装包的体积也在持续增加。回顾2017年和2018年,每年几乎都增长100%。如果没有一个有效的手段,安装包将变得越发臃肿。另外,由于原生应用需要依托于应用市场进行更新,每次产品的更新,必须依赖用户的主动更新,使得版本的迭代周期很长。业务上的这些痛点,不断地督促我们去反思到底有没有一种框架可以解决这些问题。
在2015年的时候,Facebook发布了非常具有颠覆性的React Native框架,简称RN。从名字上看,就可以清楚的明白,这是混合式开发模式,RN使用Native来渲染,JS来编码,从而实现了跨平台开发、快速编译、快速发布、高效渲染和布局。RN作为一种跨平台的移动应用开发框架,它的特性非常符合我们的诉求。美团也积极地探索RN技术。在RN的基础上,美团在脚手架、组件库、预加载、分包构建、发布运维等方面进行了全面的定制及优化,大幅提升RN的开发及发布运维效率,形成了MRN(Meituan React Native)技术体系。
从2018年开始,美团外卖客户端团队开始尝试使用MRN框架来解决业务上的问题。使用RN的另一方面的好处是,能逐渐的抹平Android和iOS开发技术栈带来的问题,使用一套代码,两个平台上线,理论上人效可以提升一倍,支持的业务需求也可以提升一倍,架构如下图所示:
2. 美团外卖容器化架构全景图
2.1 什么是容器化架构
上文说到,外卖业务已经发展到多App复用、单页面多业务团队开发的业务阶段。要满足这样的业务场景下,寻求一个可持续发展的业务架构是件不容易的事情。经过我们之前架构演进,我们获得了宝贵的经验:在平台化架构的时候,我们将App和业务进行解耦,将App做成壳容器,业务形成独立的业务库,集成到壳容器里面,从而屏蔽了多App的问题,提高了业务的复用度。在RN混合式架构里面,我们引入了RN容器,通过这个容器,使得业务屏蔽了Android和iOS的平台差异。借助这些成功的经验,我们进一步思考,如果我们尝试进一步的细分外卖的业务场景,将不同场景下的基础能力建设成壳容器,业务集成到容器内,是否可以更好的支撑我们多App复用、单页面多业务团队的当前现状呢?
容器化架构的愿景是:
- 希望将前端呈现业务的环境抽象出来,将能力进行标准化,形成统一的容器,通过容器去屏蔽平台和端的差异。容器提供上层标准统一的能力接口,使得业务开发人员专注于容器内的业务逻辑的实现,最大复用已有的能力,而不用关注现在的环境是Android还是iOS,现在的端是美团App还是大众点评App。
- 容器和远端达成呈现协议,使得端上的内容具备随时可变化的能力。容器化架构的实现是存在一定前提的,如果业务的发展本身处在一个探索阶段,还有较多可变的因素,是无法形成稳定的能力层的,这时候建设容器化架构反而使得架构偏向复杂。但对于外卖业务场景来说,经过多年的沉淀固定,外卖业务逐渐形成了一套稳定的业务形态,已经进入到场景细分和快速迭代业务模块的阶段。在这样的阶段下,容器化架构才有可实施的前提。
2.2 容器化架构的优势
当我们把承载外卖业务的环境进行了抽象和标准化后,就可以获得以下若干点好处。首先动态化属性提升,我们可以把原有必须在客户端上写的业务放到了远端,业务的动态性得到很大的提升,具备随时上线业务的可能。对于开发过程而言,编译部署的速度也得到了极大提升。如果涉及到客户端的代码改动,那客户端的编译打包,即使是增量的编译,也至少是秒级的编译速度。而容器化后,我们只打包必要的业务,把业务动态下发到容器呈现,客户端代码本身不会有变化,这样就可以从秒级的编译减少到毫秒级的编译。同样,业务动态下发,对减少客户端的包大小也有很大的帮助。
然后,容器位于应用之内,我们向应用中引入相同的容器SDK,容器屏蔽了应用之间的差异,对于Android和iOS平台,在设计上,通过容器这一层去尽可能屏蔽平台之间的差异,使业务开发人员只需要认识容器,不需要花费大量的精力去关注应用和平台之间的差异,从而使得开发效率得到了极大的提升。
其次,容器化后,容器对承载的内容是有接口协议要求的,承载的内容只有满足容器定义的协议才能得到容器带来的好处,这促使业务得到了更细粒度的细分,业务开发时候,对模块化的意识得到了保障。另外,容器这一层提供的接口在Android和iOS上是标准化的,业务的开发也因为依赖的标准化,而趋向标准化,双端的业务一致性得到了提升。这些潜在的架构好处,对未来的业务维护和扩展都打下了比较好的地基。
2.3 外卖容器化架构全景图
整个外卖容器化架构可以按照从下到上,从左到右的视角进行解读:
最底层是系统服务,因为我们采用了H5和RN这样跨端的技术栈,使得iOS系统和Android系统成为了最底层。
系统服务之上是集团基于Native建设的基建,全公司通用,覆盖了研发工程中方方面面的基础服务。
在基建之上是我们定义的容器层。我们尝试用单一技术栈解决所有问题。但经过我们的探索,觉得不太可能实现。好的架构要匹配业务形态,业务的诉求决定了我们不能选择唯一的技术栈去解决所有问题,细分外卖的业务场景可得到以下3个方向的页面分类:
- 高PV主流程页面,例如首页、金刚页、商家页等,这类页面的PV远高于其他页面。这类页面的特点是,交互强、曝光度高、多团队参与建设和维护。针对这类页面,为了给用户提供极致的体验,是无法采用H5或RN实现的,即使性能上能够满足,但是这些页面是多团队共同参与建设,如果用H5或RN实现,那么单一团队改动,都会造成全页面受到无法预料的影响,其他未改动的业务方肯定是无法接受的。所以这类页面,我们采用的是局部动态化+页面模块化的方案,我们针对页面进行容器化改造,将页面变成容器,容器承载模块。每个模块归不同的业务方进行维护,从模块的维度进行解耦,每个模块都可以动态配置和下发。
- 低PV辅助页面,例如帮助、足迹、收藏等,这类页面的PV低,但胜在数量多,都用原生实现成本比较高,如果都用H5来实现,不少页面和原生的关联还是比较近,不是非常适合。针对这类页面,我们采用集团提供的MRN基建去承载,MRN作为跨端的技术栈,我们已经在之前的RN混合架构的时候,建设了较为丰富的组件,针对自定义的MRN容器做了比较丰富的建设。同时,MRN具备动态下发的能力,满足业务的诉求。
- 向外投放的运营活动页面,例如圣诞节活动,时效性比较强。这类页面由H5技术栈去完成,一方面可以满足时效性,另一方面H5的动态下发能力也是最强的,这样的特性能够充分的满足业务的诉求。我们使用集团提供的Titans基建,通过建设业务自定义的Titans容器,支撑业务的发展。
再往上,就是垂直的业务,外卖目前有流量业务、交易业务、商家业务、商品业务、广告业务、营销业务、闪购业务等。业务都是垂直向下依赖,直接可见容器,可见基建,能够很好地获取到各种已经建设的能力去完成业务的需求。
最上面是承载的App端,目前有四端,包括外卖、点评、美团、闪购等等。
右侧是测试发布和线上监控,相对于常规的移动端App架构而言,容器化架构的测试发布和监控是更为精细化的。不仅仅要关注端本身的可用性,还需要关注容器、容器承载的模块、模块展示的模板,模板里面的样式这些的可用性。
2.4 容器化的挑战
容器化架构相对常规的移动端架构而言,它从管理移动端的代码转变成管理移动端的容器建设代码和业务远端开发代码,多出了容器和业务远端下发。这不仅仅是对技术上的挑战,对长期做客户端开发同学,也需要一个思维转变的跳转。
一致性的挑战:容器需要在多个宿主应用之中运行,宿主应用的环境一致性直接影响了容器的一致性。我们的策略是两手准备,一方面利用外卖业务的优势推动宿主应用的环境对齐;另一方面将容器建设成SDK,通过SDK将长期保持容器的一致性,也通过SDK内部的设计屏蔽应用之间的差异;对于Android和iOS平台,我们通过分层的设计,尽可能屏蔽平台的差异。综上所述,一致性的挑战在于(1)容器运行的宿主应用的环境一致性;(2)不同应用不同版本容器的一致性;(3)Android和iOS平台容器的对业务的一致性。
动态发布的挑战:长期以来,客户端同学的开发概念里面只有App版本的概念,而当我们逐渐把业务代码做成远端下发时,将会新增一个线上动态发版的概念。当我们在发布业务的时候,相对以往的工作,多出需要去考虑这个业务的版本,可以运行的容器对应的App上下界版本。另外,发版的周期也会新增业务的发版周期,不仅仅是App的发版周期。这两者在一起将会产生新的火花,业务的版本和App的版本如何适配的问题,业务动态发版的周期和App的发版周期如何适配的问题。外卖这边的解决方式是建设主版本迭代+周迭代的模型。
监控运维的挑战:以往的移动端架构,我们更加关注的是端本身的可用性,然而当我们演进到容器化架构的时候,仅仅关注端的可用性已经远远不能确定业务是可用的了。我们需要从端的可用性延伸出下载链路、加载链路,使用链路上的可用性,针对每个重要的环境,都做好监控运维。
3. 外卖跨端容器建设
3.1 MRN容器
3.1.1 MRN容器简介
React Native框架本身只是一个运行时环境中的渲染引擎,可以将同一套JS代码分别在Android和iOS系统上最终以Native的方式渲染页面,从而为App提供了基础的跨端能力。但从工程化的角度来看,如果想在App中大规模地应用RN技术,除了RN框架本身外,还需要在开发、构建、测试、部署、运维等诸多方面的配合。
MRN(Meituan React Native)是美团基于React Native框架改造并完善而成的一套动态化方案,在RN的基础上提供了容器化能力、动态化能力、多端复用能力和工程化保障。MRN在开发效率、稳定性、性能体验、动态化和监控运维等多方面进行了能力升级和扩展,满足了美团RN开发工程化的需要。目前,MRN已接入美团40多个App,核心框架及生态工具有超过100位内部代码贡献者,总PV超过4亿。
3.1.2 Roo组件库
下面再介绍一下外卖建设的两个UI相关的技术项目,Roo组件库和组件样式动态配置。
- Roo组件库:外卖在RN及MRN框架提供的UI组件基础之上,又扩展了适用于外卖业务的标准化UI组件库。UI组件库一方面统一了我们的设计规范和开发规范,提高了UI一致性;另一方面,组件封装也提升了RN页面的开发效率和质量。
- 组件样式动态配置:有了标准的Roo组件,我们进一步给标准组件的动态添加了样式动态配置能力。在使用组件时,很多样式是支持动态下发的,例如字体、圆角、背景色等,方便我们进行UI的适配和改版。
外卖在2018年底开始试验MRN容器在外卖业务上的应用,并在2019年上半年进行了大面积的页面落地。目前,外卖已有近60个RN页面上线,占外卖页面比例超80%,其中包括Tab页面“我的”、提单选择红包页、订单评价页等高PV页面。MRN容器的接入,给外卖App的容器化、动态化、人效提升、包大小瘦身等方面都做出了不小的贡献。
3.2 Titans容器
3.2.1 Titans容器简介
Titans容器是美团系App统一的Web容器组件,基于苹果提供的WebView组件,将WebView容器化,统一了WebView的UI展示和交互方式,规范了桥协议的使用范式,同时预置了诸多基础能力和业务能力。Titans容器大大提高了Web页面的开发效率和用户体验上的一致性。
- Web容器:Titans容器提供了统一的UI展示和自定义样式,例如导航栏样式、页面Loading状态、进度条样式等;还有统一的交互方式,例如页面跳转、Scheme协议的解析等。
- KNB统一桥服务:Web容器虽然在动态化和Android、iOS双端复用上很好地弥补了Native的不足,但在很多地方体验上又难以达到Native的标准。因此,KNB桥应运而生,KNB定义了Native和JS通信的标准方式,方便开发时进行桥协议扩展,同时KNB也内置众多的Native基础能力,极大地提高了Titans容器的用户体验和开发效率。
- Web业务增强能力:Titans容器中预置了丰富的基础业务页面,例如登录页面、分享弹窗等。
- 提供链路增强:提供了长连接、离线化等方式来提高网络请求的速度和成功率。
- WebView预加载:在App启动之后,用户点击网页入口之前,提前加载好HTML主文档和基础库,这样当用户点击页面入口时,App直接使用已准备好的WebView,仅需加载少量的业务代码。从而达到白屏时间短、加载页面迅速的效果。
Titans容器在外卖业务中的使用场景非常丰富,其中最重要的使用场景是各种运营页和活动页,例如点击首页顶部Banner的广告落地页、为你优选、限时秒杀等活动运营页等;还有客服页、帮助反馈页、商家入驻页、美团公益页等功能性页面;作为一级入口页面的美团会员页面,也是一个基于Enlight的Titans容器。
4. 外卖页面容器建设
外卖容器化建设,首先需要要区分的是核心页面和非核心页面。外卖业务中对核心页面的定义是页面DAU>美团DAU的5%或者是下单关键路径。为什么要先按照是否为核心页面进行拆分呢?重点就在于改造的成本。核心页面的业务复杂度决定了它不容易做全页面的动态化,它比较适合做局部的动态化方案。核心页面的复杂度在于业务本身复杂,最重要的是核心页面往往会有多个垂直业务团队共同的开发维护,大家各自有重点关注的模块,做全页面的动态化,无法做到有效的物理隔离。
而对于非核心页面,业务功能和交互相对简单,组织关系也较为确定,更适合做标准的MRN和Titans容器化。所以我们的策略是核心页面做到支撑页面模块级别的业务动态和复用,非核心页面可以做到页面级别的动态化和复用。页面容器化的核心含义就是把一个页面划分为若干个模块,每个模块成为一个业务容器,每个容器的填充既可以用Native的方式实现,也可以用Mach实现(Mach是外卖自研的页面局部动态化技术),可以支持iOS/Android/小程序三端跨平台运行。页面本身则化身为容器的管理者,负责子容器的编排和布局,并支持其动态化。
4.1 页面容器化设计思路
页面容器化设计中主要分为三个阶段,模块有序化、模块编排化、渐进式业务落地。
- 模块有序化:将耦合的外卖业务代码按模块维度进行拆分,建立标准化的模块间组合和交互方案,降低模块内改动对其他模块的影响。这个阶段我们同时完成了Native原生模块和局部动态化模块的标准化改造。
- 模块编排化:页面容器化的一个特点是页面具备编排模块的能力,在这个阶段我们在客户端增加了对业务模块结构编排能力的支持,同时我们跟后端的同学共建了配置平台,通过配置化的方式打通了AB实验平台、统一数据服务等多个平台。在标准化的数据协议的支撑下,页面支持了AB实验动态上线,大大降低了客户端在AB实验方面的开发成本,做到了客户端零成本,后端低成本,高效地支撑了外卖首页六周年的大改版。
- 渐进式业务落地:页面容器化后最明显的收益是支持业务需求的动态上线,只有页面容器中的业务模块具备动态能力才能实现这个目标,这会涉及Native模块迁移为Mach模块的过程。在这个方面,我们的思路是跟随业务需求渐进式落地。在模块编排阶段,我们设计了Native模块向Mach模块迁移的能力,同时设计了覆盖模板维度和API接口维度的三重降级方案,来保障模块动态化迁移的稳定性。
4.2 业务构建模块标准化
从App页面开发的角度看,一个完整的页面可以按照不同的功能及不同业务属性划分出多个不同的模块。
业务构建泛指由多个业务模块组合拼装为一个业务页面的过程,涉及页面本身(UIViewController/Activity)以及各个业务模块的构造过程,前后端业务数据以及页面和业务模块之间的数据交互过程,业务模块内部的数据处理以及视图刷新流程。
模块标准化指的将业务构建涉及到的多个过程通过规范化的方式确定下来,形成唯一的标准。模块标准化一方面能够在解决业务共性问题的基础上提供业务难点专项解决方案,另一方面能够在框架基础上形成能力约束,减少重复建设、低质量建设的问题。
业务构建模块标准化中我们抽象了四层,下面将分别进行解读。
- 最底层是框架能力层,是外卖业务团队自研的符合外卖业务特点的双端模块化框架。框架解决了不同页面场景下的共性问题,对典型的业务痛点也进行了支持。它是一种页面框架设计在iOS、Android双端对齐的实现方案,这种双端对齐的能力为页面容器化设计的双端一致性提供了保障。
- 统一接口层是对框架能力层的标准化抽象,它可以保证任一模块调用的能力在各个业务场景下的实现都是一致的,有了这一层抽象任一模块都可以直接在各个场景下复用。
- 在往上就是App全局的模块复用层,标准化后的模块可以通过唯一标示向模块复用池注册模块,这种中心化的注册方式可以让业务模块在跨业务库的场景下可以灵活地复用。
- 最上层就是外卖的核心业务场景层,每一个场景都对应了一个标准化的页面容器,页面容器通过实现容器化接口来完成页面容器的构建。
通过业务构建模块标准化的建设,业务模块已经是标准化的了,可以在跨页面间自由组合,这为页面容器化打下了基础。
在页面容器化中最基础的能力有以下几点:页面中业务模块可编排能力,动态上线前端AB实验的能力,增量上线动态模块的能力。实现这些能力最重要的就是进行前后端数据协议标准化建设。客户端根据数据协议中的模块唯一标识匹配并构造业务模块,在完成模块数据的填充后会根据数据协议中的模块布局信息完成模块的布局。针对Mach动态模块,我们创建了基于模板ID的模块匹配和数据填充流程,可以支持Mach动态模板的增量上线。在数据协议中针对前端AB实验我们预留了AB实验和通参字段,在数据填充阶段通过容器化接口传入动态模块中,用于支持AB实验的动态上线。
在容器化上线的过程中属于接口的大版本升级,为了保证容器的高可用性,客户端从模块级别和API级别实现了两套降级容灾方案。
模块级别的降级方案主要针对Mach动态模块,与Native模块不同,Mach动态模块需要预先下载动态模板才能正常地完成模块的载入和渲染。为了保证动态模块的加载成功率,我们一方面在接口上线前利用Eva(美团内部系统)对Mach模板的下载进行预热。另一方面,我们设计了动态模块的主动降级方案,针对动态模块的动态上线使用Native模块进行兜底降级,对于跟版动态模块使用App内置模板的方案进行兜底降级。
API级别的容灾方案主要为了保障客户端在新接口不稳定的情况下可以自行降级到旧接口。针对这个问题,我们对线上老接口设计了数据结构映射方案,在客户端通过配置化的方式可以把老接口的数据结构映射为新接口的数据结构。这样在上层业务无感知的情况下,可以做到容灾方案的上下线。
4.3 小结
通过页面容器化,使得页面只需要关心页面级的构造方式,而无需关心某一模块内部如何实现动态化的。把页面与页面的模块分离,也符合目前外卖客户端的组织结构,有利于业务组间的协作。同时,页面容器化使得外卖核心页面具备了符合外卖业务场景下的动态能力,渐进式把Native静态模块过渡到具备动态能力的模块,从模块的维度使整个页面具备了动态能力。这种渐进式的迁移方案把容器迁移跟业务模块的迁移分隔开,大大降低了页面容器化改造的风险。
5. 外卖容器化架构的衡量指标
5.1 容器化架构衡量指标的特点
质量和性能指标是衡量我们App开发质量和用户体验的重要依据,是我们一直都非常关注的重点数据。在非容器化时代,我们大多数的指标都和App的使用环节紧密相关,因为在非容器化时代,逻辑链路相对简单,例如我们打开一个新页面时,我们首先创建页面实例,然后发起网络请求,同时页面会经历一系列生命周期方法,最后渲染。这时我们可能会关注网络请求的成功率和请求时间,页面的渲染时间,和过成功是否发生Crash,这个过程相对更短暂,指标更少,所以监控起来也更容易。
外卖的容器化大大提升了外卖业务的复用能力、动态能力、模块化和开发效率,但同时也带来了更长的逻辑链路,链路从时间维度上划分是:下载链路、加载链路、使用链路。例如我们在使用MRN容器的时候,会涉及到bundle的启动下载或预热下载,bundle解压缩,MRN容器引擎初始化,bundle加载,JS的加载、执行,页面渲染等步骤,其中的每个步骤都可能存在性能问题和质量风险。因此,我们需要升级我们的衡量指标系统来应对容器化带来的新的挑战。
5.2 链路指标
- 下载链路:在下载链路中下载容器所需的各种资源,在MRN和Mach中主要是bundle的下载任务,只有bundle下载成功,才能进行后面的各项操作。所以bundle下载的成功率是下载链路中最重要的指标,同时bundle下载的时机也很重要。外卖业务中有各种bundel上百个,如果在启动时拉取所有bundle,对冷启动时间会造成极大的影响。我们采用了bundle预热的方法,发布bundle是给bundle打上相应的Tag,在适当的时机去下载,避免集中下载。
- 加载链路:在加载链路中重要工作是对下载链路中下载的资源的解压和解析。例如在用PGA加载页面时,会进行模块的解析、模块匹配、模块降级、数据模型生成等步骤。在MRN中会进行bundle解压、引擎初始化、bundle加载等步骤。加载链路往往是比较消耗计算资源的步骤,对页面打开和加载时间影响较大,所以我们会比较关注加载链路的性能指标。
- 使用链路:使用链路和非容器化的使用阶段基本相同,会主要关注页面的加载时间、Crash率、页面页面FPS、页面卡顿等指标。除此之外,还会关注和容器本身特性相关的一些指标,例如在MRN容器中,我们还会关注JS错误率、JS渲染时间、白屏率等指标。
5.3 关键指标
因为容器化的使用形成了一个串行的链路,所以如果某个关键节点失败,会导致容器功能不可使用,关键指标的任务就是从上述众多的指标当中筛选出这些关键节点。例如在下载链路中bundle下载的成功率和API的成功率,加载链路中bundle加载的成功率和模块匹配的成功率,下载或加载失败都无法再进行链路中的后续步骤,针对上面的成功率指标,我们会添加分钟级别的实时监控告警,做到及时发现,快速响应和紧急修复。
在使用链路中模块渲染的成功率、Native Crash率、JS错误率也属于关键指标,这些任务的失败也会导致容器的不可用,针对这些指标我们也会采用实时监控措施,并且添加降级手段,例如回滚bundle版本,或者把MRN和Mach容器降级为Native容器。
6. 外卖容器化架构的监控运维
上面讲到了容器化架构的各项衡量指标,那么把这些指标具体落到实处的工作就是线上的运维监控工作。工欲善其事,必先利其器,对于监控运维工作,一定要有合适的监控工具辅助配合才能事半功倍,公司内有很多优秀的监控统计工具可供使用,这里的难点就是如何根据监控的需要判断选择合适的工具。还有就是合理的划分监控维度和数据指标的优先级,例如对于能够影响到链路稳定性的关键指标,我们需要做到分钟级的监控,一旦出现问题就能及时收到告警,对于非关键指标,则通过生成日报的方式,方便开发者的统计和分析。
工具的使用上主要分为大盘工具、具体异常工具、灰度降级工具、告警工具等(以下是美团内部使用的工具)。
- 大盘工具:主要使用CAT、天网、Crash平台等工具。这些工具收集、统计大盘数据,然后生成可视化的图标和曲线。方便开发者了解大盘的整体情况和变化趋势。
- 具体异常工具:使用Sniffer、Logan等工具。这些工具可以用来获取发生异常时的上下文和设备信息,回捞用户行为日志,方便定位排查具体问题。
- 灰度降级工具:使用ABTest平台、Horn等工具。用于下发配置,以进行灰度控制或开关控制。
- 告警工具:告警小助手。将告警通过IM软件及时发送到个人或群组,做到及时发现及时处理。
业务覆盖维度监控可以分为全局监控和局部(单业务)监控。
- 全局监控:监控各项容器化质量指标、性能指标,生成每日报表,方便跟踪监控容器化的整体质量。
- 局部(单业务)监控:实时监控每个页面、每个容器的线上数据,做到有问题及时发现,及时定位,及时处理。
时间维度监控:可以按天、小时、分钟的时间维度。天级别的监控主要是一些非关键路径指标,例如一些性能指标,页面加载时间、页面FPS、JS渲染时间等,我们可以按天维度的生成数据报表,已邮件的数据发送日报。当App灰度上线时,我们会开始小时级别的监控,每过半小时通过IM软件向广播一些关键指标,方便开发者跟踪线上数据的稳定性。分钟级别的监控则是针对关键指标,观察分钟维度上的变化,如果关键指标超过阈值,或者波动过大,就会及时产生告警。
下面我们以一个开发者的视角去看一下外卖容器化架构的监控运维系统。从获取信息的方式上可以分为主动查询和被动推送,开发者可以通过监控工具监控全局和局部数据的变化趋势,也可以分析具体异常Case;也可以从IM工具,邮件等收到相关的推送数据,以便及时响应。在控制运维上,开发者可以通过Eva、Horn等美团内部的灰度系统进行灰度发布,当灰度期发现问题的时候,可以及时地通过停止灰度,版本回滚,关闭入口的方式进行降级容灾处理。
7. 外卖容器化架构的发布能力
7.1 容器化架构发布体系
容器化使外卖业务具备了强大的动态化能力,但动态化能力又和需要相应的发布能力来支持,发布能力是我们业务开发质量和效率的重要保障,也是我们容器化建设工作过程中的重点环节,这一节主要介绍一下外卖容器化的发布能力。
从发布能力类型的角度看主要可以分为三种类型:(1)容器内容的发布,包括发布整个页面或者发布页面中的局部模块;(2)配置下发,通过API或其他配置平台,下发布局协议、AB测试、样式配置、功能配置、模板配置、容器配置等,大大提高了业务的灵活度和线上验证能力;(3)灰度、降级下发,通过UUID,用户画像等信息做到灰度发布,降级回滚等控制能力。
从发布资源的的角度看主要分为两种:一种是普通的资源,例如发布一个Web页面,或者通过发布新版API来控制页面局部容器的展示与否和展示的位置,同时我们也可以进行一些AB Test操作;另一种是bundle资源,主要是针对MRN容器和Mach容器,每个MRN容器和Mach容器的资源都会先被打包成一个bundle,然后通过发布系统下发到终端,然后终端解析bundle中的代码和资源,最终渲染页面。
从发布阶段的角度看,可以分为测试阶段、上线阶段、灰度阶段和全量阶段,其中上线阶段是最终的环节,我们增加了很多校验和保护手段来尽量保证上线操作的正确性。
7.2 跟版本发布流程
虽然我们具随时备动态发布能力,但正常的版本迭代还是会存在中,所以外卖这边的节奏是周动态迭代+双周版本迭代,这保证了我们的开发工作有个一清晰的周期。在动态发布阶段中最关键的阶段操作上线阶段。以MRN为例,目前外卖业务中应有70多个bundle,再算上测试环境的bundle就有接近150个bundle,只是管理这些bundle就是一个复杂的工作,况且在进行上线操作时还是涉及发布的目标App、App版本的上下界、MRN版本的上下界等,一不小心就会造成操作失误,所以进行上线操作时需要非常谨慎。
我们针对操作上线阶段进行了事务流水线,通过流水线建立保护措施,一个bundle的上线要经历一个流水线的若干操作。首先,操作人根据上线SOP手册进行若干检查操作,同时编写标准格式的发布说明,然后周知相关核心人员后在操作系统上发起上线申请,Leader和QA收到申请后会进行检查并审批,审批通过后还要避开App使用的高峰期或节假日上线,上线后通过灰度发布观察各项数据指标,指标正常后全量发布。
7.3 bundle资源发布
bundle是我们最常发布的资源类型,这里再结合发布工具讲解一下bundle的发布过程。MRN和Mach都是以bundle的形式下发到设备终端的,我们在发布bundle的时候主要会用到两个工具,打包工具Talos和发布工具Eva(美团内部工具)。一个bundle的工程文件主要由三个部分组成:配置文件、源代码和资源文件,其中配置文件用于指导Talos对工程文件进行打包,多个bundle可以共享一份配置文件。当我们准备发布一个bundle时,先找到该bundle在Talos的发布模板,选择发布环境(测试或线上),然后进行一键打包,然后Talos会进行一系列流水线操作,包括Clone代码、配置环境、进行Lint检查、构建和上传等。Talos打包完毕后将bundle上传到Eva系统,然后Eva负责bundle的分包、上线、下线、灰度等操作,最终下发到终端设备上。
未来,我们还将引入美团住宿的MRN-DevOps来进一步的屏蔽当前多系统的问题,降低整个周期管理的成本,特别是发布前的人工检查成本,逐渐实现RD在一个平台上操作从研发到发布运维的所有实现。尽可能地减少人工成本,提升自动化。
7.4 多种发布能力综合使用
上面介绍的是以bundle资源形式的发布流程,过程较为清晰简单。下面再结合外卖首页,介绍一下局部容器化的发布方式。外卖首页是典型的流式列表,在局部容器化的架构下,首页就是由一个个矩形容器以ListView方式布局的,容器分两种,Native容器和Mach容器,Mach容器是一个通用容器,我们可以编写不同的样式模板,下发到终端后交由通用Mach容器来渲染,以此达到只使用通用容器展示不同UI样式的目的,这里涉及了Mach的发布系统。
首页各子容器相当于一块块积木,它们的位置排布、展示与否、模板的选择等最终交由API控制,API具备了控制首页布局,样式展示的能力,而不再是单纯的数据源。同时,首页也涉及了AB能力、灰度降级策略等其实配置项下发系统。可以看到外卖首页的容器化是由多种发布能力配合支撑的,是外卖发布能力体系的“集大成者”。
8. 总结
好的架构是要随着业务的发展,不断演变去适应业务的发展。美团外卖从一个很小规模,每日单量只有几千的业务,逐渐地走到今天,每日单量峰值超过4000万,组织架构也从一个十几个人的团队,逐渐发展到现在多角色、多垂直业务方向,上千人共同协作的团队。移动端上的架构,为了适应业务的发展要求,也经历了组件化、平台化、RN混合化,再到现在向容器化的变迁。
容器化架构相对于传统的移动端架构而言,充分地利用了现在的跨端技术,将动态化的能力最大化的赋予业务。通过动态化,带来业务迭代周期缩短、编译的加速、开发效率的提升等好处。同时,也解决了我们面临着的多端复用、平台能力、平台支撑、单页面多业务团队、业务动态诉求强等业务问题。
当然,容器化架构带来好处的同时,对线上的可用性、容器的可用性、支撑业务的线上发布上提出了更加严格的要求。我们通过监控下载、加载、使用链路上的可用性,来保障线上动态业务的可用性。针对容器,我们利用成熟的测试基建,建设容器的自动化测试来保障容器的可用性。针对发布,我们建设迭代流程,配合发布流水线,将线上的发布变得更为可控。
截止到目前为止,外卖业务经过了几十个动态化业务上线窗口,累积共发版百次以上。未来半年,我们还将进一步从业务需求入手,将业务需求细分归类,让产品侧逐渐建立容器和动态化需求的概念,能够从源头上,逐渐的将业务进行划分,最终使得每个业务需求,都可以归类抽象成可以动态下发的业务和容器能力建设,从而进一步的完善容器化架构的能力和支持更多的的业务场景。
9. 参考资料
- React Native在美团外卖客户端的实践
- 美团外卖iOS多端复用的推动、支撑与思考
- 美团外卖前端容器化演进实践
- UC浏览器客户端容器化架构演进
- 你知道支付宝容器化架构是怎么搭建的吗?
- 讲一讲移动端跨平台技术的演进之路
- 盘点主流移动端跨平台UI技术:实现原理、技术优劣、横向对比等
10. 作者简介
郭赛,同同,徐宏,均为美团外卖iOS工程师。
下一章:Flutter包大小治理上的探索与实践
Flutter作为一种全新的响应式、跨平台、高性能的移动开发框架,在性能、稳定性和多端体验一致上都有着较好的表现,自开源以来,已经受到越来越多开发者的喜爱。 但是,Flutter的引入往往带来包 ...