React Native在美团外卖客户端的实践
MRN簡介
MRN(Meituan React Native) 是基于開源的React Native框架改造并完善而成的一套動態(tài)化方案,在開發(fā)體驗上基本能與原生RN保持一致,同時從業(yè)務(wù)需求的角度滿足從開發(fā)、構(gòu)建、測試、部署、運維的工程化需要。解決了一系列痛點問題:客戶端版本審核及更新效率低、Android/iOS/Web三端開發(fā)技術(shù)方案不一致、公共需求重復(fù)勞動、需求排期不敏捷、集成成本高等。目前MRN已接入美團數(shù)十個App,在核心框架及生態(tài)工具上有超過百位代碼貢獻者,(每天)的總 PV 超過1億次。
在項目成立之初,MRN使用當時最新的React Native 0.54.3作為基礎(chǔ)版本,然后進行了一系列的改造。React Native官方穩(wěn)定版已經(jīng)升至0.60.5,對MRN頁面的質(zhì)量性能、開發(fā)者體驗都有了巨大的提升,包括JSI替換橋進行JS和Native通信、JS引擎替換、React Hooks等功能。最近,MRN也做了一些升級適配和深度優(yōu)化,在相關(guān)基礎(chǔ)建設(shè)、融合過程、優(yōu)化手段等方面,我們進行了很多的探索和思考,后續(xù)這些內(nèi)容會陸續(xù)放出,希望能給大家一些啟發(fā)。本文主要分享美團外賣App在業(yè)務(wù)實踐和技術(shù)探索過程中的經(jīng)驗。
背景
美團外賣自2013年創(chuàng)建以來,一直處于高速發(fā)展期。美團外賣所承載的業(yè)務(wù),也從單一的餐飲業(yè)務(wù),發(fā)展到餐飲、超市、生鮮、果蔬、藥品、鮮花、蛋糕、跑腿等十多個大品類業(yè)務(wù)。伴隨著業(yè)務(wù)的快速發(fā)展,我們深切地感受到3個痛點:
(1)業(yè)務(wù)要求快速發(fā)版試錯和原生迭代周期長
美團外賣業(yè)務(wù)長期使用H5+Native的技術(shù)棧。由于原生應(yīng)用需要依托于應(yīng)用市場進行更新,這樣的話,每次產(chǎn)品的更新必須依賴用戶的主動更新,使得版本的迭代周期變得很長,無法實現(xiàn)快速發(fā)版快速試錯的訴求。H5雖然具備隨時發(fā)布的能力,但受限于內(nèi)核的影響,平臺兼容性并不好,性能也較差,而且調(diào)用Native的能力也受限,往往只能滿足一定范圍內(nèi)的產(chǎn)品需求。
(2)有限的客戶端研發(fā)資源無法滿足日益增長的業(yè)務(wù)
業(yè)務(wù)的快速發(fā)展對客戶端的開發(fā)效率不斷提出挑戰(zhàn)。如何通過技術(shù)手段,在有限的客戶端人力資源下,支持更多的業(yè)務(wù)需求,解決有限的研發(fā)資源跟不斷變大的業(yè)務(wù)需求量之間的矛盾呢?試想,如果能逐漸地磨平Android和iOS開發(fā)技術(shù)棧帶來的問題,支持一套代碼在2個平臺上線,理論上人效可以提升一倍,支持的業(yè)務(wù)需求也可以提升一倍。
(3)業(yè)務(wù)持續(xù)增長帶來的安裝包的大幅增長
業(yè)務(wù)的快速迭代,功能的持續(xù)增加,美團外賣客戶端安裝包也在持續(xù)增加,從2018年到2019年,已經(jīng)累計增長140%。如果沒有切實有效的技術(shù)手段,安裝包將變得越發(fā)臃腫。 業(yè)務(wù)層面的這些痛點,也在不斷地督促我們?nèi)シ此?#xff1a;到底有沒有一種框架可以解決這些問題。2015年,Facebook發(fā)布了非常具有顛覆性的React Native(簡稱RN)框架。從名字上就可以看出,這屬于一種混合式開發(fā)的模式。RN使用Native來渲染,JS來編碼,從而實現(xiàn)了跨平臺開發(fā)、快速編譯、快速發(fā)布、高效渲染和布局。作為一種跨平臺的移動應(yīng)用開發(fā)框架,RN的特性非常符合我們的訴求。我們也在一直積極地探索RN相關(guān)的技術(shù),并且基于RN在腳手架、組件庫、預(yù)加載、分包構(gòu)建、發(fā)布運維等多個維度進行了全面的定制及優(yōu)化,大幅提升了RN的開發(fā)及發(fā)布運維效率,還打造了更適應(yīng)于美團的MRN技術(shù)體系。從2018年開始,美團外賣客戶端團隊開始嘗試使用MRN框架來解決業(yè)務(wù)層面的一系列問題。經(jīng)過一年多的實踐,我們積累了一些經(jīng)驗和結(jié)論,希望相關(guān)的經(jīng)驗和結(jié)論能夠幫助到更多的個人或技術(shù)團隊。
外賣混合式架構(gòu)
上圖是外賣App引入MRN后的架構(gòu)全景圖,接下來我們會從下到上、從左到右逐步介紹:
- 最下層是Android/iOS系統(tǒng)服務(wù)層,因為MRN是跨端的,所以需要引入這一層。相對單一平臺來說,由于MRN的引入,整個App的架構(gòu)不可避免地需要考慮Android和iOS平臺本身的差異性。
- 倒數(shù)第二層是平臺服務(wù)層,這一層相對與單一平臺來說,并沒有太大區(qū)別。
- 再往上一層是MRN基建層,這一層的工作主要是:(1)盡可能地屏蔽Android和iOS系統(tǒng)的差異性;(2)打通已有的平臺基建能力,讓上層業(yè)務(wù)不能感知到差異。
- 再上一層是業(yè)務(wù)組件層,這一層相對于單一平臺來說,區(qū)別不大,主要是增加了Android和iOS的RN容器,同時業(yè)務(wù)組件是可以被RN調(diào)用的。
- 繼續(xù)往上是MRN接口層,該層的主要任務(wù)是盡可能地屏蔽Android和iOS組件之間的差異,讓上層頁面使用的RN接口保持一致。
- 最后是業(yè)務(wù)層,這一層是用戶可直接接觸到的頁面,頁面的實現(xiàn)可以是Android/iOS/RN。
- 左上角是研發(fā)支撐,主要包括代碼規(guī)范、代碼檢查工具、Debug插件、準入規(guī)范、準入檢查工具、代碼模板插件等。這塊相對于單一平臺來說,主要的差異體現(xiàn)在:由于編譯器和語言不同,使用的工具有所區(qū)別,但工具要做的事情基本是一致的。
- 左下角是測試支撐,主要包括UI自動化測試、自測覆蓋率檢查、AppMock工具、業(yè)務(wù)自測小助手、性能測試、云測平臺等。這塊相對于單一平臺來說,基本也是一致的,主要的差異同研發(fā)支撐,主要是語言不同,使用的工具有所區(qū)別。
- 右上角是發(fā)布支撐,主要包括打包Bundle和APK、打包檢查、發(fā)布檢查、發(fā)布Bundle和APK等。這塊相對于單一平臺來說,保持了打包發(fā)布平臺的一致性,區(qū)別在于:需在原有的基礎(chǔ)上,增加MRN的打包發(fā)布環(huán)節(jié)。
- 右下角是運維支撐,主要包括基建成功率監(jiān)控、業(yè)務(wù)成功率監(jiān)控、線上問題追蹤、網(wǎng)絡(luò)降級等。這塊相對于單一平臺來說,保持了一致性,區(qū)別在于:需在原有的基礎(chǔ)上,增加MRN的監(jiān)控運維。
研發(fā)測試支撐
外賣業(yè)務(wù)MRN組件架構(gòu)
RN官方對雙端只提供了30多個常用組件,與成熟的Native開發(fā)相比,天壤之別。所以我們在開發(fā)的過程中面臨的一個很重要問題就是組件的缺失。于是,MRN團隊基于RN組件進行了豐富,引入了一些優(yōu)秀的開源組件,但是源于外賣業(yè)務(wù)的特殊性,一方面需要業(yè)務(wù)定制,另一方面部分組件依然缺失。所以為了減少重復(fù)代碼,提升外賣客戶端MRN的研發(fā)效率,建設(shè)外賣組件庫就變得非常有必要。
上圖是我們外賣組件庫的架構(gòu)圖,最底層依賴Android和iOS的原生服務(wù);然后是MRN基建層,用于抹平Android和iOS系統(tǒng)之間的差異;再上一層則是外賣組件庫及其依賴,如平臺組件庫和打包服務(wù),組件庫分為兩類:純JS組件和包含JS和Native的復(fù)合組件。再上一層則是Android和iOS的MRN容器,它提供了上層Bundle的運行環(huán)境。整個組件的架構(gòu)思路,是利用中間層來屏蔽平臺的差異,盡可能地使用JS組件,減少對原生組件的依賴。這樣可以有效地減少上層業(yè)務(wù)開發(fā)時對平臺的理解。接下來,我們主要講一下WM-RN組件庫:
如上圖所示,WM-RN組件庫主要包含三部分:RN interface、RN Native組件、外賣RN JS組件。RN Interface主要包括Native組件的Bridge部分和Native組件在JS側(cè)的封裝,封裝一層的好處是方便調(diào)用Native暴露出的接口,也可以用來抹平Android和iOS系統(tǒng)間的差異;RN Native組件分為Android和iOS兩端,依賴各自的業(yè)務(wù)模塊,為RN提供外賣Native的業(yè)務(wù)能力,如購物車服務(wù)、廣告服務(wù);外賣RN JS組件則是純JS實現(xiàn),內(nèi)部兼容外賣App與美團外賣頻道間的差異、Android和iOS平臺間的差異,依賴現(xiàn)有的MRN組件庫和外賣開源Beeshell組件庫,減少組件的開發(fā)成本;從工程的物理結(jié)構(gòu)來看,建議將Native組件、RN Interface放在一個倉庫進行管理,主要是因為Native與JS側(cè)的很多通信都是通過字符串來匹配的,放在一起方便雙端與JS側(cè)的接口統(tǒng)一對齊,發(fā)布時也會更加方便。目前,外賣組件庫已經(jīng)擴展了幾十個業(yè)務(wù)組件,支持了線上近百個MRN頁面。
Native/MRN/H5選型標準
目前,美團外賣App存在三種技術(shù)棧:Native、MRN、H5,面對業(yè)務(wù)持續(xù)增長和安裝包不斷變大的壓力,選擇合適的技術(shù)棧顯得尤為重要。H5在性能和用戶體驗方面相比Native和基于Native渲染的RN相對弱一些,所以目前大部分H5頁面只是用來承載需求變更頻繁、需要即時上線的活動頁面。那么MRN和Native的界限是什么呢?當有一個新的頁面產(chǎn)生時,我們應(yīng)該如何做取舍?通過實踐,我們逐漸摸索了一套選型規(guī)則,如下:
- Native選型規(guī)則,強交互(同時存在2種及以上手勢操作),無法用二元函數(shù)描述的復(fù)雜動效,對用戶體驗要求極致的頁面,類似首頁、點菜頁、提單頁等。
- 對于強交互或強動畫,MRN技術(shù)棧支持效果不理想,不建議使用。其他情況下,建議使用MRN。
- H5適用于需要外鏈展示的輕展示頁面,比如向外投放活動的運營頁面等等。
具體選型細節(jié)可參考下表:
發(fā)布運維支撐
發(fā)布運維是一個成熟的軟件項目中非常核心的部分,它保證了整個項目能夠高效且穩(wěn)定地運轉(zhuǎn)。建立一個穩(wěn)定可靠的發(fā)布運維體系是我們建設(shè)整個外賣MRN技術(shù)體系的重要目標。但發(fā)布運維的建設(shè)上下游牽扯了眾多基建:擁有一個合理的工程結(jié)構(gòu)對發(fā)布運維來說至關(guān)重要。如果工程結(jié)構(gòu)臃腫且混亂,將會引起的一系列的權(quán)限問題、管理維護問題,這樣會嚴重制約整個發(fā)布運維體系的效率。所以MRN的工程架構(gòu)演進優(yōu)化也是發(fā)布運維體系建設(shè)的重要組成部分。
MRN分庫 & 工程結(jié)構(gòu)演進
業(yè)務(wù)分庫
任何一個大型、長期的前端技術(shù)項目,良好的工程結(jié)構(gòu)都是研發(fā)發(fā)布支撐中非常核心的部分。從2018年10月份,外賣正式啟動MRN項目以來,面臨涉及近百個MRN和幾十人參與的大規(guī)模MRN應(yīng)用計劃。從項目初期,我們就開始尋找一個非常適合開發(fā)維護的工程結(jié)構(gòu)。
在最開始的時候,我們的目標是快速驗證及落地,使用了一個Git庫與一個Talos項目(美團自研發(fā)布系統(tǒng))去承接所有頁面的開發(fā)及發(fā)布工作,同時對權(quán)限進行了收縮,保證初期階段的安全發(fā)布。然而隨著頁面的增多,每個版本的發(fā)布壓力逐漸增大。發(fā)布SOP上的三大關(guān)鍵節(jié)點權(quán)限:Git庫操作權(quán)限、Talos的發(fā)布權(quán)限、美團自研的線上降級系統(tǒng)Horn權(quán)限,互不相關(guān),負責人也各異,導致發(fā)布時常因各個節(jié)點的權(quán)限審批問題,嚴重阻塞效率。
隨著項目的大規(guī)模鋪開,我們的頁面數(shù)量、合并上線次數(shù)與初期已不可同日而語。為了解決逐漸臃腫的代碼倉庫問題及發(fā)布效率問題,我們將龐大而臃腫的RN庫根據(jù)業(yè)務(wù)維度和維護團隊拆分成了4個業(yè)務(wù)庫,分別是訂單業(yè)務(wù)、流量業(yè)務(wù)、商家業(yè)務(wù)、營銷業(yè)務(wù),并確認各庫的主R,建立對應(yīng)的Talos項目,而主R也是對應(yīng)Talos項目的負責人。同時所有的主R都有MRN灰度腳本的管控權(quán)限。這樣一來,MRN的工程結(jié)構(gòu)和Native的工程結(jié)構(gòu)完全對齊,每個責任人都非常明確自己的職責,不會來回地穿插在不同的業(yè)務(wù)之間,同時業(yè)務(wù)庫任意頁面的發(fā)布權(quán)限都進行了集中,RD只需要了解業(yè)務(wù)的負責人,即可找到對應(yīng)的主R完成這個業(yè)務(wù)的所有相關(guān)工作。
工程結(jié)構(gòu)
在項目初期,對于每個庫的工程結(jié)構(gòu),美團內(nèi)部比較流行的工程結(jié)構(gòu)有兩種:一個是適合小型業(yè)務(wù)開發(fā)的單工程多Bundle方案,另一個是相對更適合中大型業(yè)務(wù)開發(fā)的多工程多Bundle方案。
單工程單Bundle方案
顧名思義,單工程單Bundle方案的意思就是一個前端工程承載所有的業(yè)務(wù)代碼,最終的產(chǎn)物也只有一個RN Bundle。通過入?yún)Q定具體加載哪個頁面。
對于業(yè)務(wù)不多,參與人不多的團隊,使用單工程單Bundle的方式即可快速完成開發(fā)、發(fā)布。因為通過一次發(fā)布就可以完成整個發(fā)布的工作,但是帶來的弊端也是不可接受的:因為所有業(yè)務(wù)都耦合在一起,每次更新都會“牽一發(fā)而動全身”,增大了問題的隱患。如果多個業(yè)務(wù)需求同時提測的時候,在團隊配合上也是一個極大的挑戰(zhàn),因為新版本號會覆蓋舊版本號,導致兩個需求提測時會出現(xiàn)相互覆蓋的情況。所以我們在立項之初就排除了這種方案。
多工程多Bundle方案
多工程多Bundle方案的意思就是一個Git庫中存放了多個頁面文件夾,各個文件夾是完全獨立的關(guān)系,各自是一個完整的前端工程。擁有自己獨立的MRN配置信息、package.json、組件、Lint配置等(如下圖所示)。每個頁面文件夾都輸出一個獨立的RN Bundle。
相比于單工程單Bundle方案,多工程多Bundle方案將頁面進行解耦,使之基本可以滿足中大型MRN項目的需求。在外賣MRN項目初期,一直都使用著這樣的工程結(jié)構(gòu)進行開發(fā)。但是我們也為之付出了相應(yīng)的代價,即每個頁面的依賴都需要對應(yīng)RD去維護升級,依賴碎片化的問題日趨嚴重。同時在工程級別的管控,如統(tǒng)一Lint規(guī)則、Git Hook等也變得更加復(fù)雜。
多工程多Bundle方案 => 單工程多Bundle方案
隨著外賣MRN頁面規(guī)模以及參與人規(guī)模的進一步增大,多工程多Bundle方案的缺點日益凸顯。特別對于那些前端技術(shù)底子相對薄弱的團隊來說,依賴管理問題會變得很頭疼。在這種情況下,單工程多Bundle的方案就應(yīng)運而生了。
核心思路也很簡單:觀察一下單工程單Bundle方案和多工程多Bundle方案的優(yōu)缺點可知,單工程單Bundle依賴管理方便的優(yōu)點主要來自于“單工程”,而多工程多Bundle的業(yè)務(wù)解耦的優(yōu)點主要來自于“多Bundle”。所以結(jié)合這兩種工程方案的核心優(yōu)點,就可以設(shè)計一種新方案:單工程多Bundle。即用一個工程去承接所有的頁面代碼,但是又可以讓每個頁面輸出獨立的RN Bundle來保證互不影響。其實,這種方式類似于Native一個靜態(tài)庫的管理,如下圖所示:
通過分析MRN的打包原理可知,MRN通過一個配置文件配置了一個Bundle的所有業(yè)務(wù)信息以及mrn-pack2的打包入口。所以我們只需要讓配置文件支持多份Bundle信息的配置,通過打包命令與參數(shù)選擇正確的mrn-pack2打包入口,即可打出我們最終所需要的業(yè)務(wù)Bundle。如下圖所示:
核心優(yōu)勢:
- 整個工程采用一個package.json,管理業(yè)務(wù)庫中所有的依賴。這樣可以有效地解決各自頁面去管理自己依賴時,必然產(chǎn)生的依賴版本碎片化問題,避免同一依賴庫因為版本不一樣,而導致頁面表現(xiàn)不一樣的問題。
- 從依賴角度去規(guī)范各自頁面的使用工具規(guī)范,如A頁面使用某一種三方庫來實現(xiàn)某種功能,B頁面使用另一種三方庫也實現(xiàn)了同一種功能,單一依賴管理就可以從庫依賴的角度強制做技術(shù)選型,減少各個頁面的實現(xiàn)差異,從而降低維護成本。
- 讓業(yè)務(wù)同學可以更加專心地開發(fā)業(yè)務(wù)代碼,不用關(guān)心復(fù)雜的依賴問題,大大提升了開發(fā)效率。
- 實現(xiàn)了工程級別的管控,如Pre-Commit,腳手架方案管理將變得更加便捷。
這種工程組織形式也成為了MRN工程結(jié)構(gòu)的最佳實踐,而且美團內(nèi)部也有多個團隊采用了這種解決方案。目前已支撐超過幾百個頁面的開發(fā)和維護工作。
外賣發(fā)布運維體系
下圖展示了我們的發(fā)布運維全景,共覆蓋了開發(fā)交付、線上發(fā)布、線上監(jiān)控、有效應(yīng)對、復(fù)盤改進等五大模塊。接下來我們會逐一進行介紹。
(1)開發(fā)交付
開發(fā)階段,需求RD完成開發(fā),提交到Git庫的發(fā)布分支。對應(yīng)的業(yè)務(wù)庫主R角色(通常由RN經(jīng)驗較豐富的工程師來承擔)進行CodeReview,確認無誤之后會執(zhí)行代碼的合并操作。順便說一下,這也是外賣RN質(zhì)量保障長征路的第一步。
(2)線上發(fā)布
合入發(fā)布分支之后,就可以正式啟動一次RN Bundle發(fā)布。這里我們借助了美團內(nèi)部的Talos完成整個發(fā)布過程,Talos的發(fā)布模板與插件流水線規(guī)范了一次發(fā)布需要的所有操作,核心步驟包括發(fā)布準備(Git拉代碼、環(huán)境參數(shù)確認、本次發(fā)布說明填寫)、發(fā)布自檢(依賴問題檢查、Lint、單元測試)、正式打包(Build、版本號自更新)、產(chǎn)物上傳測試環(huán)境(測試/線上環(huán)境隔離、測試環(huán)境進行測試),雙重確認(QA、Leader確認發(fā)布)、產(chǎn)物上傳線上環(huán)境等等。
產(chǎn)物上傳線上環(huán)境,實際上是上傳到了美團內(nèi)部的CD平臺–Eva。在Eva上,我們可以借助RN Bundle的發(fā)布配置去約束發(fā)布App的版本號、SDK版本等,以及具體的發(fā)布比例及地區(qū),去滿足我們不同的發(fā)布需求。最終執(zhí)行發(fā)布操作,將RN Bundle上傳到CDN服務(wù)器,供用戶下載,完成整個發(fā)布流程。
(3)運維監(jiān)控
發(fā)布之后,運維是重中之重。首先我們的運維難點在于我們的業(yè)務(wù)橫跨兩個平臺——美團App與外賣App。由于它們在基建、擴展、網(wǎng)絡(luò)部分都存在差異,所以我們選取指標的維度不僅要從業(yè)務(wù)出發(fā),還要增加全局的維度,來確保外賣平臺MRN的正常運轉(zhuǎn)。基于這個層面的思考,我們選取了一系列RN核心指標(在下面的章節(jié)會詳細列舉),進行了全方位的監(jiān)控。目前外賣客戶端,已經(jīng)做到分鐘級監(jiān)控、小時級監(jiān)控和日級別監(jiān)控等三檔監(jiān)控。
在監(jiān)控手段上,首先我們使用了美團開源的Cat告警平臺(這部分已經(jīng)通過Talos插件完全自動化配置),確保當核心指標在線上出現(xiàn)波動、異常的時候,相關(guān)RD、QA以及業(yè)務(wù)負責人可以及時接受到報警,并由對應(yīng)的RD主R負責,快速進入到“有效應(yīng)對”的環(huán)節(jié)。同時為了能夠分階段、更好地處理問題,我們將核心指標報警分為【P1】與【P0】兩個級別,分別代表“提高警覺,確認問題”與“大事不好,馬上處理”。保障了一個問題出現(xiàn)之后能夠及時被發(fā)現(xiàn)并快速進行處理。
除了監(jiān)控報警手段之外,我們還會借鑒客戶端高可用性保障的經(jīng)驗。用一些日常運維的手段去發(fā)現(xiàn)問題。比如使用灰度小助手、數(shù)據(jù)日報等手段從宏觀角度主動去發(fā)現(xiàn)存在隱患的指標,及時治理,避免問題。
(4)有效應(yīng)對
根據(jù)“墨菲定律”:如果事情有變壞的可能,不管這種可能性有多小,它總會發(fā)生。即便我們在發(fā)布管控和線上監(jiān)控上做的再充分,線上問題最終還是無法避免的。所以當通過線上告、客訴等手段發(fā)現(xiàn)線上問題之后,我們需要及時的應(yīng)對問題、解決問題,把問題帶來的影響降低到最小,并以最快的速度恢復(fù)對用戶的服務(wù)。
在有效應(yīng)對的方面,我們主要靠兩種手段。第一種是存在B方案兜底的情況,使用Horn灰度配置,關(guān)掉MRN開關(guān),短時間內(nèi)恢復(fù)成Native頁面或者H5頁面繼續(xù)為用戶提供服務(wù),同時通知相關(guān)RD和QA快速定位問題,及時修復(fù),驗證并上線。第二種是無兜底方案的情況,CDN服務(wù)器(Eva)上撤掉問題Bundle,實現(xiàn)版本回滾,接下來的問題定位過程跟手段保持一致。
這兩種備案保障了外賣MRN業(yè)務(wù)的整體高可用性。
(5)復(fù)盤改進
在以上四個大環(huán)節(jié)中,問題可能會出現(xiàn)在任意一個環(huán)節(jié)。除了及時發(fā)現(xiàn)問題與解決問題,我們還需要盡力避免問題。這一點主要是靠我們內(nèi)部的例會、復(fù)盤會,對典型問題進行Review,將問題進行歸類,包括復(fù)盤流程規(guī)范問題、操作失誤問題、框架Bug等,并力圖通過規(guī)范流程、系統(tǒng)優(yōu)化來盡力地避免問題。
在外賣MRN項目實施過程中,我們共推動了二十多項規(guī)范流程、系統(tǒng)優(yōu)化等措施,大大保障了整體服務(wù)的穩(wěn)定性。
最后,我們用一張圖對外賣監(jiān)控運維體系做一個總結(jié),幫助大家有一個全局的認知。
混合式架構(gòu)流程
針對混合式架構(gòu)的流程,目前外賣技術(shù)團隊采用的是正常雙周版本迭代流程+周迭代上線流程。MRN頁面既可以跟版迭代,也可以不跟版迭代,這樣可以有效地減少流程的復(fù)雜度和降低QA的測試成本,而周迭代流程可以有效地利用MRN動態(tài)發(fā)版的靈活性。混合式開發(fā)和原生開發(fā)應(yīng)盡量保持時間節(jié)點和已有流程的一致。這種設(shè)計的好處在于,一方面隨著動態(tài)化的比例越來越高,版本迭代將可以無限拉長,另一方面從雙周迭代逐漸演變成周迭代的切換成本也得到大幅的降低。詳細可分為下面幾個階段:
評審階段
業(yè)務(wù)評審階段在原有的流程上,增加了技術(shù)選型階段。在技術(shù)選型時,明確是否會存在需要使用MRN頁面的情況,如果頁面可以完全不涉及到Native部分即可完成,就可以進入周迭代的發(fā)版流程。如果需求用MRN實現(xiàn),但是又涉及到Native部分,仍然走周迭代的上線流程。除正常開發(fā)需求的時間外,RD需綜合考慮到雙端上的適配成本。
開發(fā)階段
客戶端以周維度進行開發(fā),每周確定下周可提測的內(nèi)容,根據(jù)提測內(nèi)容是否為動態(tài)化的業(yè)務(wù)、下周是否在版本迭代周期內(nèi),決定跟版發(fā)布或周發(fā)布。
提測階段
提測前,為了保證MRN頁面的提測質(zhì)量,RD首先需要按照QA提供的測試用例提前發(fā)現(xiàn)適配問題。提測時需要在提測郵件中注明:(1)提測的Bundle名稱和對應(yīng)的版本號 ;(2)標明哪些組件涉及Native模塊 ;(3)依賴變更情況,如是否升級了基礎(chǔ)庫,升級后的影響范圍;(4) 重點測試點的建議。
上線階段
MRN由于其可動態(tài)發(fā)布的特性可以跟版發(fā)布,也可不跟版發(fā)布,但上線時間和灰度時間節(jié)點都保持了一致。不過版本還是動態(tài)發(fā)版,都默認周二上線,周四全量。
- 跟版發(fā)布:默認只對當前版本生效,需在雙周迭代三輪提測節(jié)點,周二當天將Bundle上線服務(wù)器,MRN的灰度開關(guān)全量打開。通過周四App的發(fā)版灰度比例來控制MRN的灰度比例,上線時需配置報警和灰度助手監(jiān)控,實時掌握MRN的線上數(shù)據(jù)。
- 不跟版發(fā)布:也同樣以周四作為全量發(fā)布窗口,Bundle需在周二時上線指定線上版本,指定QA白名單。測試通過后,在周三按照比例逐步灰度,周四正式全量,和跟版發(fā)布一樣,上線時需要配置報警和監(jiān)控。
架構(gòu)總結(jié)
引入MRN后,相對單平臺而言,架構(gòu)層級上,我們增加了2個MRN中間層去屏蔽Android和iOS平臺、原生組件之間的差異。這樣做的目的是為了讓上層業(yè)務(wù)開發(fā)者可以很快地使用框架進行業(yè)務(wù)開發(fā),完全不用關(guān)心平臺和組件間的差異。通過引入MRN技術(shù)棧,帶來的好處很明顯:
(1)使用MRN實現(xiàn)的頁面理論上可以實現(xiàn)一套代碼,部署到不同平臺上,開發(fā)效率得到大幅度提升。 (2)采用MRN框架,無論是加載性能還是頁面滑動性的用戶體驗上,都會比原來H5的方式要好。 (3)部分頁面具備了快速編譯、快速發(fā)布的能力。
但一個硬幣總有兩面,混合式架構(gòu)增加了架構(gòu)的復(fù)雜度,使得原本只要考慮一個平臺的事情,逐漸轉(zhuǎn)變成需要考慮三個平臺,另外Android本身具備碎片化的問題,這使得混合式架構(gòu)的適配問題較為突出。當出現(xiàn)問題時,我們的第一反應(yīng)由“這是什么問題”變成“它是否存在于兩個平臺,還是只在一個平臺上?”、“如果僅在一個平臺上,是在原生代碼還是React Native代碼出了問題?”、“歷史版本的MRN是否存在問題,是否需要修復(fù)”、“修復(fù)的效果在Android和iOS上的表現(xiàn)是否一樣”,這些問題增加了定位和修復(fù)工作的復(fù)雜性。另外,MRN的適應(yīng)場景也是有限的,并非所有的業(yè)務(wù)和頁面都適合改造成MRN,如何做選擇也需要進行有效的判斷,從而增加了決策成本。
針對上述問題,我們的建議是:
(1)減少分歧: - 在研發(fā)、測試、發(fā)布和運維環(huán)節(jié),MRN的頁面盡可能對齊Native原有的環(huán)節(jié),減少團隊理解的成本。 - 在Debug開發(fā)環(huán)境下,利用頁面浮層提示技術(shù)棧使用情況;Release環(huán)境下,利用工具、MRN自動化報表,及時的讓開發(fā)同學明確知道是Native頁面還是MRN頁面,減少確認。 - MRN頁面盡可能地避免原生組件的使用,而使用純JS代碼實現(xiàn),供MRN頁面使用的原生組件的需要高質(zhì)量的提供,減少下層組件的問題。 - 默認只修復(fù)當前的版本,出現(xiàn)嚴重問題時才考慮修復(fù)歷史版本,減少多版本帶來的復(fù)雜度提升。
(2)技術(shù)棧明確邊界
- 做好Native和MRN技術(shù)棧使用的邊界,盡可能用簡單的選型標準,讓合適的場景選用合適的技術(shù)棧,從而保證業(yè)務(wù)整體的可用性,讓用戶體驗依然如初。
(3)單技術(shù)棧轉(zhuǎn)向多技術(shù)棧團隊
- 培養(yǎng)全棧工程師,當團隊的同學都具備iOS、Android和MRN多個技術(shù)棧能力時,將會有效地提升開發(fā)的效率,短期內(nèi)可選擇iOS、Android和MRN工程師結(jié)伴編程的策略。
可用性體系
正如在“監(jiān)控運維”章節(jié)中所講到的那樣,線上運維是我們工作的重中之重。這個章節(jié)我們就講一下我們對于監(jiān)控指標的選取。鑒于外賣業(yè)務(wù)的特殊性,除了美團的外賣頻道之外,外賣業(yè)務(wù)還需要運行在獨立的外賣App上。如下圖所示:
外賣App經(jīng)過多年的發(fā)展,目前已逐漸成為一個平臺級應(yīng)用,承接了C端、閃購、跑腿等多個業(yè)務(wù)。與美團App相比,它們之間在很多基礎(chǔ)建設(shè)、擴展、網(wǎng)絡(luò)部分都存在差異。所以在監(jiān)控核心指標的選取上,我們除了保證C端MRN業(yè)務(wù)在美團以及外賣兩端的高可用性,還需要保證外賣App平臺本身基建的穩(wěn)定性,從而保證運轉(zhuǎn)在外賣App上所有MRN業(yè)務(wù)的高可用性。
而從監(jiān)控的大分類上來講,我們分為了【可用性指標】以及【性能指標】,它們分別關(guān)注業(yè)務(wù)本身的可用性,以及頁面的性能與用戶體驗。接下來,我們就依次進行講解。
MRN可用性指標
可用性指標也是我們關(guān)注的關(guān)鍵指標,它直接決定了我們的MRN頁面是否能夠正確、穩(wěn)定地為用戶提供服務(wù)。通過MRN Bundle加載全景,我們可以確定整個包加載的幾個關(guān)鍵節(jié)點。可以說,MRN業(yè)務(wù)的可用性就是取決于這些關(guān)鍵節(jié)點的成功率。
下載鏈路
MRN是一個動態(tài)化的框架,所有的MRN Bundle都是從CDN節(jié)點上遠程下載。所以下載成功是MRN業(yè)務(wù)可用的先決條件。有些普通的業(yè)務(wù)方是不需要關(guān)注這個指標的,而外賣App可能會因為網(wǎng)絡(luò)庫基建,出現(xiàn)啟動下載線程擁堵、DNS劫持等問題,所以我們把下載成功率作為外賣App監(jiān)控的全局指標。目前,外賣App的下載成功率長期穩(wěn)定在99.9%左右。
加載鏈路
加載鏈路可以細分為初始化引擎部分以及業(yè)務(wù)Bundle加載部分。前者跟基建有關(guān),代表從引擎創(chuàng)建到加載完Common包加載成功這段的成功率。這部分主要依賴MRN SDK的穩(wěn)定性,從我們的日報上看,穩(wěn)定性基本保持在99.99%以上。
而業(yè)務(wù)Bundle加載成功率(MRN PageLoad Success),是MRN頁面創(chuàng)建到業(yè)務(wù)視圖內(nèi)容渲染過程中,沒有發(fā)生錯誤的比例。它與跟拉包時網(wǎng)絡(luò)情況、MRN框架穩(wěn)定性和業(yè)務(wù)JS代碼都有關(guān)系。這也是我們關(guān)注的核心指標,因為它直接決定了我們某個頁面是否可以渲染成功,所以我們把這個指標同時列為了外賣App監(jiān)控告警的全局指標與單Bundle告警的指標。目前,整個外賣業(yè)務(wù)的Bundle加載成功率穩(wěn)定在99.9%以上。
使用鏈路
Bundle加載成功之后,頁面成功被渲染。但是在使用的過程中,可能會因為JS代碼,Native代碼的Bug出現(xiàn)JS Error、Native Crash等問題,這樣給用戶帶來的直觀反饋就是應(yīng)用閃退、頁面白屏等,造成了服務(wù)的不可用。所以在使用鏈路上出現(xiàn)問題率,基本也可以直觀反映出一個RN頁面的質(zhì)量以及它當前的運行狀況。
在使用鏈路上,我們主要關(guān)注的是JS Error率、JS Error個數(shù)以及頁面退出成功率(MRN PageExit Success)等。
JS Error很好理解,由于RN是由JS驅(qū)動的框架,所以一個頁面的JS Error率基本上可以綜合反映出一個頁面的可用性、穩(wěn)定性或者基建的穩(wěn)定性,故我們同樣把這個指標同時列為了外賣App監(jiān)控告警的全局指標與單Bundle告警的指標。我們用上報上來的JS Error數(shù)量做分子,該頁面的PV做分母,計算一個頁面的JS錯誤率,當JS Error個數(shù)短時間內(nèi)極速升高或者JS Error率有大幅上升時,就會觸發(fā)我們的JS Error告警。目前外賣大盤的JS Error率保持在萬分之一左右,略低于Native Crash率。
頁面退出成功率(MRN PageExit Success),理解起來不如前面的指標那么簡單,因為它表示的是用戶在退出MRN頁面時,業(yè)務(wù)視圖內(nèi)容已成功渲染的比例。它會包含所有已知和未知的異常,但是用戶進入頁面后快速退出的場景,也會被錯誤的統(tǒng)計在其中,因為用戶退出時可能頁面尚在加載中。相比于JS Error,它是一個更加綜合的指標,基本上涵蓋了加載失敗、渲染白屏、使用時出現(xiàn)錯誤等多個異常場景,基本上可以反映出一次MRN業(yè)務(wù)的單次可用性,相比于之前的指標會更加嚴格。我們把這個指標同時列為了外賣App監(jiān)控告警的全局指標與單Bundle告警的指標。我們希望它永遠能保持在99.9%以上,否則就會觸發(fā)告警。目前外賣大盤的MRN PageExit Success基本穩(wěn)定在萬分之三左右,我們最終的目標是希望穩(wěn)定在萬分之一左右。
最后,我們希望通過兩個“腦圖”快速回顧一下外賣全局監(jiān)控與單業(yè)務(wù)監(jiān)控關(guān)注的核心指標。
MRN性能指標
除了可用性指標,性能指標也是我們重點關(guān)注的內(nèi)容。如果加載時間過長,就會大大增加用戶離開頁面的概率。而頁面卡頓,也會影響用戶在使用層面的體驗,從而引發(fā)客訴或者業(yè)務(wù)損失。
根據(jù)Bundle加載全鏈路圖,我們也可以把性能指標分為兩個大類,一個是加載時耗時與使用時性能指標。前者主要關(guān)注Bundle從Load到渲染整個鏈路的耗時,后者主要關(guān)注使用時的性能指標,在這里主要是指頁面的FPS。
加載鏈路耗時
如上述所說,整個加載鏈路分為引擎初始化的時間以及Bundle本身加載及渲染的時間的時間。
引擎初始化的時間在整條鏈路上占比是最長的,因為初始化的時候會加載比一般業(yè)務(wù)代碼大得多的CommonJS。經(jīng)過觀察,這部分的時間總體表現(xiàn)較差,在iOS上50分位和90分位分別是0.3s和0.7s。在Android上表現(xiàn)更差,50分位和90分位分別是1.3s和1.8s。不過目前MRN已經(jīng)使用了預(yù)加載方案,即在App剛啟動時就初始化一個JS引擎,等實際使用時,直接復(fù)用該引擎即可,大大縮短了首次Bundle的整體加載時間。
頁面加載時間和頁面渲染時間是我們關(guān)注的第二類指標,從加載鏈路圖也可以發(fā)現(xiàn),頁面加載時間代表從開始加載Bundle到RN內(nèi)容渲染成功的整條時間,而頁面渲染時間則是它的子集,代表Bundle解析完畢,從JS StartApplication開始加載組件到渲染出第一幀的時間(iOS和Android的統(tǒng)計口徑不同)。區(qū)分這兩項指標也可以更好地分析整個加載鏈路上的瓶頸在哪,有助于針對性的做性能優(yōu)化。
以外賣iOS 50分位為例,我們發(fā)現(xiàn)頁面整體的加載時間在400ms左右,JS渲染時間只需要100ms左右,主要的性能瓶頸在Bundle加載以及JS Bundle的解析部分,這也是我們接下來需要重點研究課題。
使用時FPS
衡量用戶使用體驗比較直觀的一個指標就是FPS,較高的FPS會讓用戶更加順暢地體驗功能,完成操作。
目前,MRN在外賣側(cè)業(yè)務(wù)總體落地頁面復(fù)雜度適中,遇到復(fù)雜動畫也使用了BindingX來提升性能。通過監(jiān)控,外賣側(cè)的頁面總體表現(xiàn)良好,在iOS上幾近滿幀,在Android上表現(xiàn)稍差,平均在55幀左右,較深的視圖層級與較低的JS-Native的通信效率都是MRN FPS的殺手。如何提升MRN特別是在Android上的頁面性能也是我們下一階段研究的課題。
目前,外賣性能指標50分位的性能指標基本滿足線上需求,但是90分位的表現(xiàn)不盡如人意,特別是較低的FPS以及過長的頁面加載時間。革命尚未成功,同志仍需努力。
效率衡量
引入MRN,提升了本地的開發(fā)效率,但同時也增加了工程的復(fù)雜度,所以總體來說真的能提升實際開發(fā)效率嗎?在完成幾十個RN頁面的開發(fā)后,總結(jié)了一些公式,希望可以給其他團隊一些結(jié)論性的參考。首先設(shè)定三個方面去考量:人效提升、代碼復(fù)用、維護成本衡量,將外賣的所有MRN頁面加在一起,取平均值,可以得出較為準確的結(jié)論:
- 人效提升計算公式:∑(Android Native總?cè)巳?#43;iOS Native總?cè)巳?RN總?cè)巳?#xff09;/ ∑(Android Native總?cè)巳?#43;iOS Native總?cè)巳?#xff09;
- 代碼復(fù)用率計算公式: ∑(RN行數(shù)-平臺分支判斷代碼塊)/ ∑(RN行數(shù)+Android native+iOS Native)
- 維護成本計算公式:∑(Android Native原生總行數(shù)+iOS Native頁面總行數(shù)-RN頁面總行數(shù))/ ∑(Android Native頁面總行數(shù)+iOS Native頁面總行數(shù))
根據(jù)頁面的交互程度去進一步的劃分,得到如下的表格:
如表所示:人效提升的方面,主要取決于頁面是否存在復(fù)雜的交互,如果頁面存在復(fù)雜交互,就會不可避免的導致涉及到Native的雙端原生開發(fā),如部分交互需要Native Module實現(xiàn),最終的人效提升將大打折扣。而對于涉及較少的Native Module和展示型的頁面,MRN存在較大優(yōu)勢。但大家會很奇怪這種結(jié)果,為什么人效提升會大于50%?邏輯上Android和iOS雙端復(fù)用后,提升的效率理論上最大應(yīng)該是50%。這是由于RN bundle的熱加載極大地節(jié)省了Native的編譯時間,這一部分相對原生開發(fā)效率大概能提升20%以上,使得最終的人效提升大于50%。雙端復(fù)用率方面,對于純展示型的頁面,大概率可以完全由JS實現(xiàn),雙端復(fù)用率可以達到100%,后續(xù)雙端只需維護一份JS代碼即可,極大的降低了維護成本。對于一些交互復(fù)雜的頁面,需雙端各自封裝對應(yīng)的Native Module實現(xiàn),復(fù)用率下降,維護成本變高。
總結(jié)
隨著業(yè)務(wù)的快速發(fā)展,工程復(fù)雜度的不斷提升,在沒有外力的情況下,開發(fā)效率必然會持續(xù)下降。如何在資源有限的情況下不斷提升開發(fā)效率是一個永恒的話題。美團外賣客戶端通過借助美團基建MRN,推動混合式架構(gòu)來提升效率。截至目前,美團外賣業(yè)務(wù)已經(jīng)有60多個RN頁面上線,每天的PV高達上千萬,為用戶提供了穩(wěn)定可靠的服務(wù)。
混合式開發(fā)帶來的不僅僅是技術(shù)層面的挑戰(zhàn),更是對團隊成員、團隊組織能力的挑戰(zhàn)。MRN雖然能夠做到跨端,但是有時候仍然需要針對特定平臺單獨編寫代碼來解決問題,這就間接要求工程師必須熟悉三個平臺,團隊也必須有效組織各技術(shù)棧人才共同協(xié)作,才能真正用好MRN。
參考文獻
- 京東618:RN框架在京東無線端的實踐
- React Native架構(gòu)分析
- 點我達騎手Weex最佳實踐
- State of React Native 2018
- 使用React Native的五個理由
- iOS 開發(fā)是否要采用 React Native
- 開源React Native組件庫beeshell 2.0發(fā)布
- ESLint 在中大型團隊的應(yīng)用實踐
- CAT 3.0 開源發(fā)布,支持多語言客戶端及多項性能提升
作者簡介
曉飛、唐笛、維康,均為美團外賣前端團隊研發(fā)工程師。
招聘信息
美團外賣長期招聘 Android、iOS、FE 高級/資深工程師和技術(shù)專家,Base 北京、上海、成都,歡迎有興趣的同學投遞簡歷到tech@meituan.com(郵件標題注明:美團外賣前端團隊)。
總結(jié)
以上是生活随笔為你收集整理的React Native在美团外卖客户端的实践的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Boot中使用MyBati
- 下一篇: Spring Cloud中Hystrix