牛逼!支付寶超級(jí) App 的架構(gòu)演進(jìn)
| 導(dǎo)語
本文基于重岳在 2019 年 DevOps 國際峰會(huì)北京站的分享內(nèi)容進(jìn)行總結(jié),希望通過本篇文章介紹近些年來支付寶面向超大業(yè)務(wù)體量的挑戰(zhàn),在移動(dòng)端構(gòu)建彈性動(dòng)態(tài)架構(gòu)部分做了怎樣的實(shí)戰(zhàn)與思考,期冀能給讀者們帶來些許幫助。

支付寶作為國民級(jí)應(yīng)用,當(dāng)前國內(nèi)年活躍用戶已經(jīng)超過 8.7 億,提供了超過 200 項(xiàng)以上的服務(wù),而崩潰率始終維持在萬分之五以下,而且每天支付寶都上線新的功能和改進(jìn)。做到今天這樣的成績,并不容易,是經(jīng)過長時(shí)間的實(shí)踐經(jīng)驗(yàn)積累下來的。
支付寶的架構(gòu)演進(jìn)主要經(jīng)歷了三個(gè)階段,如果用比喻的話,可以分為獨(dú)木舟、戰(zhàn)列艦和航空母艦三個(gè)階段。
1.獨(dú)木舟時(shí)代
支付寶剛推出移動(dòng)端時(shí),它的結(jié)構(gòu)非常之簡單,除了一些工具組件被劃分為模塊,業(yè)務(wù)代碼都是糅合在一起。剛開始并沒有太大問題,但是當(dāng)我們的研發(fā)人員迅速增長時(shí),問題開始變得棘手起來,僅僅舉幾個(gè)例子便可見一斑。
研發(fā)同學(xué)晚上提交的可以運(yùn)行的代碼,到第二天早上來更新一下就完全不能用,原因是其他不相干團(tuán)隊(duì)提交代碼覆蓋或者污染了自己的代碼。
臨近發(fā)布點(diǎn)的時(shí)候,通常是最忙的,但不是忙著趕功能,而是忙著解決合并代碼產(chǎn)生的各種問題,不僅浪費(fèi)時(shí)間,還耽誤測試同學(xué)的寶貴時(shí)間。
即使最后勉強(qiáng)發(fā)布了,穩(wěn)定性和性能也是非常糟糕的,因?yàn)楦鱾€(gè)模塊只管自己的,沒有統(tǒng)一的規(guī)范,也缺乏統(tǒng)一的監(jiān)控。
最令 Android 開發(fā)頭痛的是 65535 的問題,彼時(shí) Google 還沒有推出 multi-dex 的方案。
這些嚴(yán)重的問題讓我們的產(chǎn)品研發(fā)迭代變得無法持續(xù)下去,因此我們決定來一次徹底的重構(gòu),于是步入了戰(zhàn)列艦時(shí)代。
2.戰(zhàn)列艦時(shí)代
當(dāng)設(shè)計(jì)新一代的客戶端架構(gòu)時(shí),我們從三個(gè)方向進(jìn)行思考:團(tuán)隊(duì)協(xié)作、研發(fā)效率、性能與穩(wěn)定。
團(tuán)隊(duì)協(xié)作方面,我們希望整個(gè)架構(gòu)分層合理,基礎(chǔ)層面,將通用能力下沉,為更多的上層業(yè)務(wù)服務(wù),避免重復(fù)創(chuàng)造輪子;業(yè)務(wù)層面,各個(gè)業(yè)務(wù)團(tuán)隊(duì)能夠獨(dú)立開發(fā)管理,不會(huì)對(duì)不相關(guān)的業(yè)務(wù)造成影響?;谶@個(gè)初衷,我們形成了下圖這樣的架構(gòu):

整個(gè)客戶端架構(gòu)總共分成四層:業(yè)務(wù)層、服務(wù)層、組件層、框架層。
業(yè)務(wù)層:只需專注于業(yè)務(wù)邏輯與界面的實(shí)現(xiàn),當(dāng)需要調(diào)用如支付這樣的通用能力時(shí),研發(fā)同學(xué)直接使用下層提供的服務(wù)能力,不需自己開發(fā),如此能夠保證核心能力有收口,方便監(jiān)控。
服務(wù)層:常用模塊,如登錄、支付、營銷等,它們不僅自己是業(yè)務(wù),也向其他業(yè)務(wù)提供自己的服務(wù),我們將此類模塊歸類到服務(wù)層。
組件層:這一層提供的是客戶端通用能力,如安全、網(wǎng)絡(luò)、多媒體、存儲(chǔ)這些,它們提供穩(wěn)定的接口給上層使用者,同時(shí)不斷優(yōu)化自身內(nèi)部的性能和穩(wěn)定性,作為客戶端的基石,它們至關(guān)重要。
框架層:最為關(guān)鍵的部分,包括容器、微應(yīng)用、服務(wù)框架以及 Pipeline,客戶端的微應(yīng)用化、啟動(dòng)管理都依賴框架層的運(yùn)作。
我們將服務(wù)層、組件層和框架層合稱為 mPaaS,即移動(dòng)端上的 PaaS 服務(wù)。這些 PaaS 服務(wù)可以復(fù)用,我們不僅在支付寶里使用它們,也在其他集團(tuán)應(yīng)用,如螞蟻財(cái)富、網(wǎng)商銀行等中使用。
| 業(yè)務(wù)分治
要實(shí)現(xiàn)業(yè)務(wù)分治,最好的方式就在代碼上能夠進(jìn)行隔離,大家不必在同一個(gè) Codebase 中開發(fā),避免代碼合并沖突的現(xiàn)象。這個(gè)通常在 Android 上通常可以通過 aar 的方式來實(shí)現(xiàn),但是可惜的是我們重構(gòu)的時(shí)候 aar 還沒出來,而且即使有 aar,也存在打包時(shí)間隨代碼體積增大線性增長的問題。
我們的解決方案借鑒 OSGi 的概念,將整個(gè)客戶端以 Bundle 為單位劃分,每個(gè) Bundle 可以包含自己的代碼、頁面和資源。讀者可能會(huì)想,這究竟和 aar 有什么分別呢?其實(shí)區(qū)別很大!
首先,Bundle 里的代碼部分是已編譯的 dex,當(dāng)編譯 apk 時(shí),我們只需要合并 dex 即可,不需要像 aar 那樣將 class 編譯成 dex 再進(jìn)行合并,這樣大大節(jié)省了打包時(shí)間;其次,Bundle 是可以獨(dú)立運(yùn)行于自己的 ClassLoader 中的,并且我們可以通過殼代理的方式加載 Activity 等基礎(chǔ)組件,使得動(dòng)態(tài)下發(fā)業(yè)務(wù)成為可能;最后,Bundle 里還包含微應(yīng)用、服務(wù)和 Pipeline 相關(guān)的配置信息,框架會(huì)根據(jù)這些信息啟動(dòng)相應(yīng)的組件。

mPaaS 的服務(wù),即 Service 類似于 Spring 框架中的 Service,它對(duì)外提供接口服務(wù),而使用者不需要知道如何初始化服務(wù)的實(shí)例以及生命周期管理,這些完全由框架來托管。使用者只需要知道目標(biāo)服務(wù)接口類的方法參數(shù)即可,調(diào)用時(shí)通過框架提供的 API 來獲取實(shí)例。對(duì)于服務(wù)的發(fā)布者來說,他在自己的 bundle 中聲明接口類以及實(shí)現(xiàn)接口類派生的實(shí)例類,并注冊(cè)相關(guān)信息到 bundle 的 manifest 文件中。這種做法的本質(zhì)思想是 Inversion of Control,減少類之間的復(fù)雜依賴,避免繁瑣的初始化工作。
以依賴接口的方式進(jìn)行開發(fā),能夠解除服務(wù)使用者對(duì)服務(wù)提供者的依賴,在服務(wù)提供者尚未完全開發(fā)完成時(shí),使用者可以完全以 mock 的方式來模擬服務(wù),而不需要修改自己的業(yè)務(wù)代碼,當(dāng)然,前提是雙方協(xié)商好服務(wù)接口的協(xié)議。

支付寶中的頁面非常多,直接啟動(dòng) Activity 或者 ViewController 對(duì)我們來說遠(yuǎn)遠(yuǎn)不夠,我們選擇在它們上面增加 MicroApp,即微應(yīng)用的概念。微應(yīng)用具備唯一的應(yīng)用 ID,在框架中標(biāo)識(shí)自己的存在。微應(yīng)用具有統(tǒng)一的入口,根據(jù)使用方傳入的字典參數(shù)來管理 Activity 或 ViewController。這樣能夠帶來很多好處:
只要應(yīng)用 ID 和參數(shù)協(xié)議不變,使用方不需擔(dān)心目標(biāo)應(yīng)用內(nèi)部重構(gòu)帶來的影響,直接使用 Activity 或者 ViewController 類名造成的引用泛濫的問題不復(fù)存在。
微應(yīng)用的 ID 和字典參數(shù)特性,很容易生成 URL,從而實(shí)現(xiàn)外部應(yīng)用使用 URL 跳轉(zhuǎn)應(yīng)用內(nèi)頁面。
從數(shù)據(jù)的角度,我們可以按業(yè)務(wù)維度來統(tǒng)計(jì)用戶行為數(shù)據(jù)。
微應(yīng)用的概念不僅適用于原生頁面,同樣也適用于H5和小程序。注冊(cè)為H5或者小程序類型的應(yīng)用 ID,框架會(huì)自動(dòng)將啟動(dòng)過程delegate給H5或者小程序容器,而使用者完全不必關(guān)心應(yīng)用 ID 對(duì)應(yīng)的應(yīng)用類型。
綜上所述,微應(yīng)用化和服務(wù)接口所賦予的特性極大提高團(tuán)隊(duì)間協(xié)作效率,各研發(fā)小組之間的依賴更加簡單,可以各行其道,更關(guān)注于自身服務(wù)的打造建設(shè)。
| 性能優(yōu)化
我們一方面在架構(gòu)上作出重大改變來提高研發(fā)效率,另一方面也在不斷的進(jìn)行性能優(yōu)化,改善用戶體驗(yàn)。我們主要從三個(gè)層面來著手:
框架層面
制定統(tǒng)一開發(fā)規(guī)范,業(yè)務(wù)方使用統(tǒng)一的線程池、存儲(chǔ)、網(wǎng)絡(luò)等組件,并按需進(jìn)行加載,避免不必要的啟動(dòng)和耗時(shí)操作。
引入 Pipeline 機(jī)制,業(yè)務(wù)模塊如需在應(yīng)用啟動(dòng)時(shí)進(jìn)行初始化工作,必須使用Pipeline??蚣芤罁?jù)業(yè)務(wù)優(yōu)先級(jí)確定業(yè)務(wù)初始化實(shí)際。
利用 AOP 切面,對(duì)常用路徑進(jìn)行耗時(shí)統(tǒng)計(jì),追蹤性能瓶頸。
基礎(chǔ)指標(biāo)
對(duì)于常用指標(biāo),如閃退、ANR、內(nèi)存、存儲(chǔ)、電量、流量等,進(jìn)行長期追蹤。我們能夠明確獲悉每個(gè)版本之間這些指標(biāo)上的差異,并進(jìn)行采樣分析,定位并解決問題。
向下突破
我們不僅僅在應(yīng)用層面進(jìn)行優(yōu)化,同時(shí)也向下探索性能提升的可能性。在這方面,我們也收獲頗豐,比如在 Android 上某些系統(tǒng)版本,通過在啟動(dòng)階段禁用 GC 的方式獲得 20%~30% 的啟動(dòng)時(shí)間縮減;在 iOS 上,利用系統(tǒng)本身的 Background Fetch 機(jī)制提高進(jìn)程活躍時(shí)間,實(shí)現(xiàn)應(yīng)用秒起。
3.航母時(shí)代
隨著移動(dòng)支付的不斷普及,面對(duì)海量的用戶和業(yè)務(wù)需求,高可用、彈性動(dòng)態(tài)成為支付寶客戶端更為艱巨的挑戰(zhàn)。支付寶作為集支付、金融、生活為一體的服務(wù)平臺(tái),需要能夠快速穩(wěn)定的發(fā)布服務(wù)和引入第三方服務(wù),同時(shí)對(duì)于用戶的反饋和訴求必須能夠積極迅速的響應(yīng)。
| 動(dòng)態(tài)研發(fā)模式

我們?cè)谘邪l(fā)模式上作出改變以業(yè)務(wù)快速迭代的要求,業(yè)務(wù)逐步由原生頁面向 Web 混合頁面遷移。原有的研發(fā)模式能夠很好的滿足團(tuán)隊(duì)協(xié)作的要求,但是隨著業(yè)務(wù)規(guī)模的不斷增大,代碼量相應(yīng)膨脹導(dǎo)致安裝包太大,在 iOS 上一度超過代碼段上限,無法通過 AppStore 審核;另外基于集中時(shí)間點(diǎn)的迭代發(fā)布,通常是一個(gè)月發(fā)布一個(gè)版本,遠(yuǎn)不能滿足業(yè)務(wù)的更新速度要求。相較于原生應(yīng)用開發(fā),Web 應(yīng)用的優(yōu)勢非常明顯:
只需要一套代碼,Web 應(yīng)用可以在 iOS 和 Android 客戶端中運(yùn)行,能夠相對(duì)減少人員的投入。
每個(gè)用戶日常使用的功能僅僅是支付寶龐大平臺(tái)中的一小部分,H5應(yīng)用可以做到動(dòng)態(tài)下發(fā),因此可以消除冗余的存儲(chǔ),降低包大小。
近些年來 React Native,Weex 等動(dòng)態(tài)渲染引擎在社區(qū)非?;钴S,但經(jīng)過小范圍的應(yīng)用以及考慮到 Web 技術(shù)的不斷發(fā)展以及其在業(yè)界公認(rèn)的地位,我們最終還是選擇 Web 技術(shù)作為動(dòng)態(tài)研發(fā)模式的基礎(chǔ)。
Web 應(yīng)用迭代擺脫了客戶端集中時(shí)間點(diǎn)發(fā)布的束縛,各業(yè)務(wù)線迭代計(jì)劃變得自主可控。
| 打磨 Web 體驗(yàn)

盡管 Web 應(yīng)用優(yōu)勢明顯,但在移動(dòng)端上的短板也是顯而易見的,它提供的用戶體驗(yàn)、性能以及能力上的限制與原生應(yīng)用有相當(dāng)大的差距。為了彌補(bǔ)這些差距,我們做了大量的改進(jìn),主要在以下幾個(gè)方面:
前后端分離,我們將頁面資源離線化,這樣節(jié)省了資源請(qǐng)求消耗的時(shí)間,使得頁面打開速度提升明顯,解決了在網(wǎng)絡(luò)環(huán)境較差下容易出現(xiàn)白屏的問題。同時(shí),數(shù)據(jù)請(qǐng)求使用 Native 網(wǎng)絡(luò)通道,可優(yōu)化的空間更大,安全性更高。
差量更新,客戶端更新某業(yè)務(wù)應(yīng)用版本時(shí),不需下載完整的新版本資源包,而是下載由發(fā)布平臺(tái)根據(jù)客戶端本地安裝版本計(jì)算生成的體積更小的差量包,這樣不僅能夠節(jié)省帶寬和流量,也提升了業(yè)務(wù)更新的速度。
推拉結(jié)合,解決業(yè)務(wù)最新版本覆蓋率的問題,每次發(fā)布新版本時(shí),業(yè)務(wù)可主動(dòng)觸發(fā)消息到客戶端,客戶端收到通知后會(huì)更新該業(yè)務(wù)應(yīng)用版本。同時(shí),客戶端會(huì)定時(shí)去檢查服務(wù)端是否有版本發(fā)布,這樣能夠保證版本發(fā)布后大多數(shù)用戶在短時(shí)間內(nèi)獲得最新的應(yīng)用。
容錯(cuò)補(bǔ)償,客戶端可能由于網(wǎng)絡(luò)、安全或者存儲(chǔ)權(quán)限等原因,不能使用或者及時(shí)獲得離線包,這種情況我們也考慮進(jìn)來了。我們?cè)诎l(fā)布離線資源時(shí),發(fā)布平臺(tái)會(huì)自動(dòng)生成對(duì)應(yīng)的在線 URL 并配置到應(yīng)用信息中,當(dāng)客戶端加載 Web 應(yīng)用時(shí)發(fā)現(xiàn)離線包不可用,會(huì)立刻啟用該 URL 加載內(nèi)容,能夠最大程度保證業(yè)務(wù)可用性。
Android 獨(dú)立瀏覽器內(nèi)核,Android 碎片化的問題自其誕生之初業(yè)已存在,而且目前看上去沒有得以解決的跡象。不同系統(tǒng)、不同廠商中的瀏覽器內(nèi)核同樣存在差異,這導(dǎo)致層出不窮的兼容性問題令研發(fā)同學(xué)頭疼不已,這也違背 Web 一統(tǒng)天下的愿景。為了徹底解決并掌控這些問題,我們引入了獨(dú)立的UC瀏覽器內(nèi)核并集成在應(yīng)用中,這樣所有的問題都集中到 UC 團(tuán)隊(duì)解決,變得非??煽?,根據(jù)數(shù)據(jù)統(tǒng)計(jì),使用 UC 瀏覽器內(nèi)核后瀏覽器相關(guān)的閃退和 ANR 有明顯的下降。同時(shí),安全上出現(xiàn)的漏洞,我們可以在第一時(shí)間修復(fù)并發(fā)布,遠(yuǎn)比廠商升級(jí)更有效率。
Web 應(yīng)用全方位監(jiān)控,資源加載異常、JS 執(zhí)行異常、白屏、加載耗時(shí)等性能數(shù)據(jù)會(huì)被收集上報(bào)至后臺(tái),可以及時(shí)發(fā)現(xiàn)異常。
| 小程序
我們不僅自身提供各種各樣的服務(wù),也需要引入第三方服務(wù)來服務(wù)更多的人群,以往我們只能引入簡單的第三方 H5 頁面,它們只能使用支付寶提供的少量功能,而且開發(fā)人員能力的差異導(dǎo)致用戶體驗(yàn)不是很理想。小程序?qū)⒅Ц秾毜哪芰θ骈_放出來,從開發(fā)到測試皆有完整的 IDE 等工具鏈支持,同時(shí) DSL 簡單易用,對(duì)于第三方來說,能夠快速開發(fā)上線一款體驗(yàn)和功能比以往更強(qiáng)大的小程序。

| 線上高可用保障體系

在支付寶,線上風(fēng)險(xiǎn)是每個(gè)研發(fā)人員在業(yè)務(wù)前必須厘清的事情,評(píng)估風(fēng)險(xiǎn),預(yù)防風(fēng)險(xiǎn),監(jiān)控風(fēng)險(xiǎn),風(fēng)險(xiǎn)應(yīng)急處理方案在上線都要準(zhǔn)備好。支付寶線上的高可用保障體系由灰度發(fā)布、實(shí)時(shí)監(jiān)控、診斷定位和容災(zāi)恢復(fù)四大部分組成。
灰度發(fā)布
灰度發(fā)布是預(yù)防風(fēng)險(xiǎn)的有效手段之一,對(duì)于客戶端來說,線下測試的再怎么完備也無法保證在用戶環(huán)境下一切正常,直接發(fā)布至全量用戶是非常危險(xiǎn)的操作,在支付寶內(nèi)部屬于嚴(yán)重違規(guī)。我們的發(fā)布平臺(tái)提供多種灰度策略,包括白名單灰度、時(shí)間窗灰度、百分比灰度、基于機(jī)型地域系統(tǒng)等維度的灰度。新版發(fā)布前,優(yōu)先選取活躍用戶和問題高發(fā)的機(jī)型進(jìn)行灰度,灰度期間發(fā)現(xiàn)并修復(fù)問題,不斷擴(kuò)大灰度范圍,直到閃退率、卡死率等指標(biāo)符合發(fā)布標(biāo)準(zhǔn)后再進(jìn)行全量的發(fā)布。
實(shí)時(shí)監(jiān)控
首先,制定各種線上監(jiān)控指標(biāo),包括閃退、卡死、流暢度、流量、內(nèi)存、存儲(chǔ)以及業(yè)務(wù)不可用埋點(diǎn)等。
其次,上報(bào)策略上閃退、卡死、業(yè)務(wù)不可用埋點(diǎn)等高優(yōu)先指標(biāo)實(shí)時(shí)上報(bào),第一時(shí)間發(fā)現(xiàn)異常;數(shù)據(jù)上報(bào)使用獨(dú)立的進(jìn)程,確保不影響主業(yè)務(wù)邏輯;當(dāng)處于業(yè)務(wù)高峰時(shí)期,比如春節(jié)紅包、雙 11 等大型活動(dòng)時(shí),我們能夠動(dòng)態(tài)調(diào)整上報(bào)策略以緩解日志服務(wù)端的壓力。除了自動(dòng)上傳和周期性上傳策略外,我們通過下發(fā)診斷指令至客戶端去獲取平時(shí)用不到但駐留在客戶端的日志,比如 logcat 日志。
診斷定位
我們能夠根據(jù)客戶端上報(bào)的各種埋點(diǎn)日志,完整描繪出用戶的操作路徑,根據(jù)這些信息,我們得以嘗試重現(xiàn)用戶的問題,數(shù)據(jù)的真實(shí)性相對(duì)用戶提供的信息更加可靠,能減少錯(cuò)誤信息產(chǎn)生的干擾。另外,通過診斷指令上傳的 logcat 日志,能夠更加完整的信息,幫助我們更清晰的定位問題,因此我們通常都要求研發(fā)同學(xué)在寫代碼的過程中能夠多輸出些有用的信息。

容災(zāi)處理
當(dāng)故障發(fā)生時(shí),第一時(shí)間要求是能夠止血,避免損失擴(kuò)大,我們通常會(huì)預(yù)置開關(guān)到業(yè)務(wù)邏輯當(dāng)中,當(dāng)出現(xiàn)業(yè)務(wù)大面積異?;蛸Y損時(shí),后臺(tái)推送業(yè)務(wù)開關(guān)至客戶端中,將業(yè)務(wù)能夠臨時(shí)屏蔽下線。
客戶端在啟動(dòng)階段發(fā)生的死鎖、閃退或者主頁異常超過一定閾值,會(huì)自動(dòng)清理應(yīng)用數(shù)據(jù),將應(yīng)用還原至初始狀態(tài),能夠一部分因?yàn)閿?shù)據(jù)異常導(dǎo)致的啟動(dòng)問題。
我們使用 hotpatch 技術(shù)來修復(fù)原生代碼,同樣 hotpatch 本身是個(gè)有風(fēng)險(xiǎn)的技術(shù),因此也要經(jīng)歷灰度發(fā)布的階段來逐步驗(yàn)證線上穩(wěn)定性,一旦發(fā)生因 patch 引起的問題,要立刻回滾 patch。

·················END·················
推薦閱讀
? 耗時(shí)2年,Android進(jìn)階三部曲第三部《Android進(jìn)階指北》出版!
推薦我的技術(shù)博客
推薦一下我的獨(dú)立博客: liuwangshu.cn ,內(nèi)含Android最強(qiáng)原創(chuàng)知識(shí)體系,一直在更新,歡迎體驗(yàn)和收藏!
BATcoder技術(shù)群,讓一部分人先進(jìn)大廠
你好,我是劉望舒,百度百科收錄的騰訊云TVP專家,著有三本技術(shù)暢銷書,蟬聯(lián)四屆電子工業(yè)出版社年度優(yōu)秀作者,谷歌開發(fā)者社區(qū)特邀講師。
前華為面試官,現(xiàn)大廠技術(shù)負(fù)責(zé)人。
歡迎添加我的微信 henglimogan ,備注:BATcoder,加入BATcoder技術(shù)群。
明天見(??ω??)
