從2013年開(kāi)始,我們先后進(jìn)行了不同路徑的多樣性架構(gòu)探索,在實(shí)踐過(guò)程中也經(jīng)歷了各種曲折與壓力,最終實(shí)現(xiàn)了2015年的這個(gè)全新架構(gòu),實(shí)現(xiàn)了無(wú)線服務(wù)端基于API Gateway的架構(gòu)框架、客戶端的模塊化開(kāi)發(fā)、測(cè)試與部署,支持運(yùn)行期間的模塊實(shí)時(shí)加載、按需Lazyloding、Remote加載,從而實(shí)現(xiàn)模塊級(jí)動(dòng)態(tài)升級(jí)以及代碼級(jí)熱修復(fù),并
且逐步推動(dòng)數(shù)百人的客戶端研發(fā)團(tuán)隊(duì)由不堪重負(fù)、效率低下的大版本大火車開(kāi)發(fā)模式向模塊間獨(dú)立迭代、發(fā)布輕量級(jí)的開(kāi)發(fā)方向演進(jìn)。
同時(shí)在架構(gòu)探索期間,攜程做了App相關(guān)的很多性能優(yōu)化,比如底層網(wǎng)絡(luò)通道治理的優(yōu)化、應(yīng)用層插件容器加載啟動(dòng)速度以及存的優(yōu)化、業(yè)務(wù)中間件Hybrid的優(yōu)化等等,逐步保證隨著業(yè)務(wù)的不斷的迭代,能保證用戶的比較好的優(yōu)化體驗(yàn)。
App服務(wù)端架構(gòu)變遷
早期App服務(wù)端架構(gòu)
早期App服務(wù)端架構(gòu)使用了傳統(tǒng)的PC無(wú)線開(kāi)發(fā)架構(gòu),即在PC Web應(yīng)用基礎(chǔ)上增加一些無(wú)線端的REST接口直接供給App訪問(wèn),沒(méi)有考慮架構(gòu)的擴(kuò)展性、 靈活性、安全型等因素。
圖1 攜程無(wú)線服務(wù)端架構(gòu)V1
如圖1所示,服務(wù)端系統(tǒng)一方面以Web應(yīng)用的方式提供給PC端瀏覽器訪問(wèn),另一方面為支持移動(dòng),在Web應(yīng)用基礎(chǔ)上增加一些REST接口直接供App訪問(wèn)。相應(yīng)地,無(wú)線接口和Web應(yīng)用作為同一工程開(kāi)發(fā),作為同一個(gè)應(yīng)用部署,這種架構(gòu)設(shè)計(jì)思路是很直接和自然的,可以快速把PC端功能復(fù)制到App上,其思想設(shè)計(jì)是在現(xiàn)有Web應(yīng)用上打補(bǔ)丁,體現(xiàn)的是PC思維無(wú)線化,把App簡(jiǎn)單作為PC端應(yīng)用的翻版,并把兩者物理上捆綁在一起,在早期也能滿足當(dāng)時(shí)的業(yè)務(wù)需求,但是隨著平臺(tái)化的發(fā)展,以及業(yè)務(wù)越來(lái)越復(fù)雜和多樣性,這種架構(gòu)設(shè)計(jì)帶來(lái)的一些列的問(wèn)題逐步暴露出來(lái),其中最突出的急需解決的有三個(gè)問(wèn)題:耦合、重復(fù)造輪子、系統(tǒng)穩(wěn)定性,具體如下所示:
無(wú)線接口和Web應(yīng)用緊耦合,Web端的修改會(huì)影響無(wú)線接口,Web端的發(fā)布導(dǎo)致無(wú)線接口被動(dòng)連帶發(fā)布,Web端的Bug影響無(wú)線接口的可用性,反過(guò)來(lái)也一樣,無(wú)線接口的任何變化會(huì)影響Web應(yīng)用。
此外其中酒店無(wú)線接口和機(jī)票的無(wú)線接口,或者其他BU無(wú)線的接口,也存在著較為嚴(yán)重的耦合問(wèn)題,這種耦合帶來(lái)的問(wèn)題,最嚴(yán)重最明顯的就是這個(gè)BU的接口調(diào)整或者修改Bug,有可能會(huì)影響其他BU接口的穩(wěn)定型,從而帶來(lái)每次發(fā)布,要帶來(lái)更多的測(cè)試回歸工作。
無(wú)線接口除了給App提供業(yè)務(wù)數(shù)據(jù),還需要考慮一系列非功能性因素的接口功能驗(yàn)證,如通訊協(xié)議和數(shù)據(jù)格式封裝、安全控制、日志記錄,性能監(jiān)控等,這些對(duì)每個(gè)無(wú)線接口都適用。如果App和后端系統(tǒng)直連,意味著每個(gè)后端系統(tǒng)都需要單獨(dú)支持這些通用功能,導(dǎo)致重復(fù)開(kāi)發(fā)。一旦這些通用需求有變化(如對(duì)數(shù)據(jù)傳輸進(jìn)行加密增強(qiáng)),所有后端系統(tǒng)都要強(qiáng)制同步修改和上線,給項(xiàng)目管理和產(chǎn)品發(fā)布帶來(lái)很大挑戰(zhàn)。
App和多個(gè)后端系統(tǒng)直連,只要一個(gè)系統(tǒng)出問(wèn)題,就會(huì)影響App的可用性,比如酒店服務(wù)出了問(wèn)題比如變慢或者耗用CPU過(guò)多資源,其機(jī)票服務(wù)或者其他服務(wù)會(huì)受到一定影響,其典型的弊端就是缺乏故障隔離機(jī)制,缺少負(fù)載均衡、缺少監(jiān)控、缺少熔斷等影響后端穩(wěn)定性的問(wèn)題,導(dǎo)致App的健壯性很差,非常脆弱。
攜程App服務(wù)端架構(gòu)V2.0
基于架構(gòu)V1.0三個(gè)比較嚴(yán)重的缺點(diǎn),于是我們開(kāi)始嘗試使用一種新的無(wú)線架構(gòu)V2:基于API Gateway的無(wú)線服務(wù)端架構(gòu)。
基于如圖2所示的無(wú)線API Gateway架構(gòu),具備如下功能特點(diǎn)。
App實(shí)際上和PC端瀏覽器是對(duì)等的,PC端應(yīng)用有服務(wù)端,App也需要自己獨(dú)立的服務(wù)端,兩個(gè)服務(wù)端都需要針對(duì)自身的特點(diǎn),獨(dú)立開(kāi)發(fā),獨(dú)立部署,同時(shí)實(shí)現(xiàn)邏輯和物理層面的解耦,從架構(gòu)層面徹底擺脫P(yáng)C思維無(wú)線化。
核心邏輯從Web應(yīng)用剝離出來(lái),進(jìn)行服務(wù)化改造,服務(wù)實(shí)現(xiàn)時(shí)不區(qū)分PC和無(wú)線,App和Web應(yīng)用都依賴于這些服務(wù),一套接口,多方調(diào)用。
-
統(tǒng)一無(wú)線API Gateway網(wǎng)關(guān)入口,保持系統(tǒng)的穩(wěn)定性
提供統(tǒng)一的無(wú)線網(wǎng)關(guān),所有App調(diào)用指向此網(wǎng)關(guān),網(wǎng)關(guān)包括通用層、接口路由層、適配層。通用層包括通訊協(xié)議適配、數(shù)據(jù)封裝、安全、監(jiān)控、日志、隔離、熔斷、限流、反爬這些系統(tǒng)級(jí)功能,每個(gè)接口調(diào)用都需要同樣邏輯,這些功能統(tǒng)一由網(wǎng)關(guān)前置處理,避免重復(fù)開(kāi)發(fā)。具體實(shí)現(xiàn)時(shí),每個(gè)通用處理邏輯封裝成攔截器,遵循統(tǒng)一的過(guò)濾接口,并且做到可配置,網(wǎng)關(guān)依次調(diào)用這些攔截器,這樣可以支持通用邏輯的靈活擴(kuò)展。
無(wú)線API Gateway應(yīng)該目前很多公司都有自己的實(shí)現(xiàn),目前市場(chǎng)上也提供了很多開(kāi)源項(xiàng)目Zuul、Archaius、Hystrix、Eureka等幫助我們?nèi)?shí)現(xiàn)自己的Gatway。
API Gateway具備的功能特點(diǎn)
圖2 攜程無(wú)線服務(wù)端架構(gòu)V2.0
攜程基于Netflix的開(kāi)源項(xiàng)目Zuul開(kāi)發(fā)了無(wú)線APIGateway架構(gòu)如上圖2所示,其Gateway的職能是負(fù)責(zé)接收來(lái)自無(wú)線端的所有API請(qǐng)求,并將他們路由到正確的目標(biāo)應(yīng)用服務(wù)器,并且提供限流、隔離、熔斷等功能,保證了無(wú)線服務(wù)的長(zhǎng)期穩(wěn)定運(yùn)行,擁有的彈性容錯(cuò)機(jī)制也減少了日常運(yùn)維工作。同時(shí)該Gateway提供了多維度的監(jiān)控?cái)?shù)據(jù),并與報(bào)警系統(tǒng)對(duì)接,實(shí)時(shí)監(jiān)控線上情況,達(dá)到運(yùn)維自動(dòng)化。其API Gateway具有的幾個(gè)核心職能:路由、隔離、限流、熔斷、反爬、監(jiān)控報(bào)警,具體如下所示:
-
接口路由:核心功能,需要根據(jù)各種條件將請(qǐng)求路由到正確的目的地。在實(shí)現(xiàn)上采用了路由服務(wù),Gateway定期從路由服務(wù)獲取路由表,達(dá)到了解耦、實(shí)時(shí)更新的效果;經(jīng)過(guò)通用邏輯預(yù)處理后,無(wú)線接口請(qǐng)求將進(jìn)一步分發(fā)給后端處理(各個(gè)Adapter)。URL和Adapter在配置文件里做映射,分發(fā)邏輯根據(jù)請(qǐng)求中的URL信息,找到對(duì)應(yīng)的Adapter,然后把請(qǐng)求交給Adapter處理。
-
隔離:由于Gateway接收了所有業(yè)務(wù)請(qǐng)求,請(qǐng)求多種多樣,當(dāng)某類請(qǐng)求出問(wèn)題時(shí),不能影響其他請(qǐng)求處理。對(duì)此,Gateway實(shí)現(xiàn)了資源隔離,防止某類請(qǐng)求將資源耗光,繼而影響其他服務(wù)。
-
限流:對(duì)于任何一類請(qǐng)求,都設(shè)置了容量上限,并不能無(wú)限制處理。Gateway可以為每類請(qǐng)求設(shè)置并發(fā)上限,當(dāng)?shù)竭_(dá)上限時(shí),Gateway將不在轉(zhuǎn)發(fā)請(qǐng)求,而是直接返回,保護(hù)后端服務(wù)。如果在后端服務(wù)過(guò)載的情況下,仍然轉(zhuǎn)發(fā)請(qǐng)求,只會(huì)惡化問(wèn)題。
-
熔斷:當(dāng)一個(gè)服務(wù)在不能提供服務(wù)時(shí),Gateway如果斷續(xù)向它轉(zhuǎn)發(fā)請(qǐng)求,不但不能解決問(wèn)題,往往還會(huì)惡化問(wèn)題。Gateway引入了一個(gè)熔斷機(jī)制,當(dāng)某一服務(wù)在過(guò)去一段時(shí)間內(nèi)的錯(cuò)誤比率到達(dá)一個(gè)閾值,Gateway則停止向該服務(wù)轉(zhuǎn)發(fā)請(qǐng)求,稱之為熔斷,特定時(shí)間過(guò)去后,Gateway會(huì)探測(cè)此服務(wù)是否恢復(fù)正常,正常則開(kāi)始正常轉(zhuǎn)發(fā),若不正常繼續(xù)熔斷。
-
反爬:Gateway積極對(duì)接安全接口,會(huì)根據(jù)IP、clientId、以及算法校驗(yàn)阻斷非法請(qǐng)求,保護(hù)后端服務(wù)。
-
監(jiān)控報(bào)警:Gateway接入了Cat、Clog、并對(duì)接了運(yùn)維報(bào)警工具。當(dāng)出現(xiàn)問(wèn)題時(shí),會(huì)及時(shí)報(bào)警,盡早發(fā)現(xiàn)問(wèn)題,減少損失。
API Gateway 智能升降級(jí)
Gateway支持集中管控的同時(shí),也帶來(lái)單點(diǎn)問(wèn)題。假設(shè)后臺(tái)某個(gè)服務(wù)接口,由于某種原因,性能有嚴(yán)重問(wèn)題,對(duì)應(yīng)Adapter處理很慢,那么網(wǎng)關(guān)所在服務(wù)器的線程很快被耗盡,導(dǎo)致單個(gè)接口拖垮整個(gè)系統(tǒng)。這種問(wèn)題,單純通過(guò)增加機(jī)器,水平擴(kuò)展網(wǎng)關(guān)數(shù)量是解決不了的,實(shí)踐中,我們引入了智能升降級(jí)機(jī)制來(lái)快速隔離單個(gè)接口的影響,從而實(shí)現(xiàn)了接口的自動(dòng)隔離熔斷機(jī)制,其實(shí)現(xiàn)原理如圖3所示。
圖3 Gateway接口自動(dòng)升級(jí)降級(jí)流程圖
針對(duì)特定一個(gè)接口,如果在一定時(shí)間間隔內(nèi)(比如5分鐘),它的超時(shí)失敗率到了一定比例(比如5%),網(wǎng)關(guān)會(huì)對(duì)該接口做降級(jí)處理,隨機(jī)拋棄部分流量,比如只允許50%流量通過(guò)。下一個(gè)5分鐘再評(píng)估,如果失敗率還沒(méi)有改善,允許通過(guò)的流量降到25%,以此類推。如果成功率好轉(zhuǎn),網(wǎng)關(guān)對(duì)該接口做升級(jí)處理,提升通過(guò)的流量比例,為了快速恢復(fù),一般提升到原流量4倍,然后在下一個(gè)時(shí)間段再評(píng)估是否觸發(fā)升降級(jí)。
整個(gè)過(guò)程全自動(dòng)智能處理(為防止誤判,可支持人工干預(yù)),這樣單個(gè)接口出問(wèn)題,不會(huì)影響整個(gè)網(wǎng)關(guān)的處理能力。
攜程App服務(wù)端架構(gòu)演進(jìn)總結(jié)
攜程App服務(wù)端架構(gòu)通過(guò)一系列的拆分和整合,既優(yōu)化了公司整體應(yīng)用架構(gòu),又為App做大做強(qiáng)奠定良好基礎(chǔ),其帶來(lái)的好處是全方面的,增加了架構(gòu)的可擴(kuò)展性、健壯性、穩(wěn)定性、靈活性,并且提高了團(tuán)隊(duì)的開(kāi)發(fā)效率和團(tuán)隊(duì)長(zhǎng)遠(yuǎn)的收益,其具體表現(xiàn)在:
-
實(shí)現(xiàn)PC端應(yīng)用和移動(dòng)端應(yīng)用分離,使兩者徹底解耦,各自獨(dú)立發(fā)展,App從寄生藤變成并蒂蓮。攜程在做Gateway架構(gòu)的第一步就是做PC端和無(wú)線端的業(yè)務(wù)解耦,以及各BU之間的業(yè)務(wù)解耦,實(shí)現(xiàn)各BU無(wú)線業(yè)務(wù)和PC業(yè)務(wù)的獨(dú)立部署、獨(dú)立發(fā)布。
-
底層核心的SOA服務(wù)基于統(tǒng)一業(yè)務(wù)規(guī)則提供邏輯和數(shù)據(jù),接口不區(qū)分PC、無(wú)線或其他渠道(如Open API),避免重復(fù)開(kāi)發(fā),避免業(yè)務(wù)邏輯被污染。所有前端一視同仁,而且如果以后增加其他端,也不需要做過(guò)的改動(dòng),其擴(kuò)展性和靈活性能滿足新業(yè)務(wù)拓展的需要。
-
根據(jù)無(wú)線本身的特點(diǎn),支持系統(tǒng)層面的集中處理和業(yè)務(wù)層面的分散處理。通用邏輯支持插件化擴(kuò)展,可以根據(jù)需要逐步補(bǔ)充;Adapter實(shí)現(xiàn)內(nèi)外部接口的無(wú)縫轉(zhuǎn)換,可以針對(duì)無(wú)線場(chǎng)景,做邏輯增強(qiáng)(如服務(wù)聚合,客戶端性能埋點(diǎn)、接口性能監(jiān)控)等。
-
移動(dòng)研發(fā)團(tuán)隊(duì)和各業(yè)務(wù)線研發(fā)團(tuán)隊(duì)各司其職,每個(gè)團(tuán)隊(duì)專注于自己擅長(zhǎng)部分,移動(dòng)團(tuán)隊(duì)負(fù)責(zé)App客戶端和網(wǎng)關(guān)通用邏輯處理,PC服務(wù)端負(fù)責(zé)PC相關(guān)的業(yè)務(wù)邏輯處理,H5服務(wù)端負(fù)責(zé)H5相關(guān)的業(yè)務(wù)邏輯處理,各個(gè)研發(fā)團(tuán)隊(duì)獨(dú)立研發(fā)和發(fā)布,不耦合,即各業(yè)務(wù)線研發(fā)團(tuán)隊(duì)負(fù)責(zé)底層SOA服務(wù)及前端Adapter適配。
攜程App客戶端架構(gòu)變遷
App早期架構(gòu)
攜程App的第一個(gè)版本在2011發(fā)布,那時(shí)候App架構(gòu)很簡(jiǎn)單,基本上就是在傳統(tǒng)的MVC的架構(gòu)基礎(chǔ)上封裝了一個(gè)數(shù)據(jù)服務(wù)層即代理數(shù)據(jù)層,如圖4所示。
圖4 攜程早期客戶端架構(gòu)V1
在攜程業(yè)務(wù)發(fā)展的早期,移動(dòng)App經(jīng)歷從無(wú)到有的階段,為了快速上線搶占市場(chǎng),其移動(dòng)App開(kāi)發(fā)的MVC架構(gòu)成了“短平快”思路的首選。
在如上圖4所示的MVC的體系架構(gòu)中,業(yè)務(wù)控制層負(fù)責(zé)整個(gè)App中主要邏輯功能的實(shí)現(xiàn);業(yè)務(wù)邏輯Model層則負(fù)責(zé)數(shù)據(jù)結(jié)構(gòu)的描述以及數(shù)據(jù)持久化的功能;數(shù)據(jù)服務(wù)層作為數(shù)據(jù)的代理媒介層,主要負(fù)責(zé)與Control層進(jìn)行數(shù)據(jù)通信,包括實(shí)現(xiàn)基礎(chǔ)框架數(shù)據(jù)通信,序列化和反序列的機(jī)制等;而移動(dòng)界面UI View層作為展現(xiàn)層負(fù)責(zé)渲染整個(gè)App的UI。這種架構(gòu)分工清晰,簡(jiǎn)潔明了,并且這種系統(tǒng)架構(gòu)在語(yǔ)言框架層就得到了Android和iOS的支持,所以非常適用于App的startup開(kāi)發(fā)。
但是這種架構(gòu)在開(kāi)發(fā)的后期會(huì)由于其超高耦和性,從而造就龐大Controller層,而這也是一直被人所詬病。最終的MVC都從Model-View-Controller走向了Massive-View-Controller的終點(diǎn),其最嚴(yán)重的結(jié)果就是Control層的代碼越來(lái)越多,在攜程內(nèi)部很多類,早期都超過(guò)了2000行,同時(shí)Control層和View層之間存在一些較高的耦合。其對(duì)應(yīng)的App工程結(jié)構(gòu)架構(gòu)如圖5所示:當(dāng)時(shí)無(wú)論iOS和Android工程,都只有一個(gè)工程結(jié)構(gòu)CtripWireless。
圖5 攜程前期App工程架構(gòu)圖
單個(gè)工程去實(shí)現(xiàn)一個(gè)App的好處就是各個(gè)業(yè)務(wù)線的接口通信方便,調(diào)用簡(jiǎn)單隨意,可以隨意使用工程中的任何公共和業(yè)務(wù)組件,并且接入學(xué)習(xí)成本低。但是隨著業(yè)務(wù)越來(lái)越復(fù)雜,以及各BU業(yè)務(wù)通信交互的需求越來(lái)越多,其各個(gè)BU的業(yè)務(wù)耦合越來(lái)越嚴(yán)重,這個(gè)直接為后期插件化Bundle架構(gòu)埋下了伏筆。
基于攜程業(yè)務(wù)不斷快速發(fā)展,后來(lái)活躍用戶已經(jīng)超過(guò)1億,日活用戶千萬(wàn),很快觸及到了當(dāng)時(shí)Android虛擬機(jī)機(jī)制的設(shè)計(jì)缺陷,即移動(dòng)端在Android上面臨了兩個(gè)比較嚴(yán)重的問(wèn)題,這兩個(gè)問(wèn)題導(dǎo)致的嚴(yán)重后果就是在2.3的系統(tǒng)里面,用戶直接都不能安裝和使用。
一是單dex 65535方法數(shù)限制,二是線性內(nèi)存分配器(LinearAlloc)限制。今天的Android開(kāi)發(fā)者看到這兩個(gè)限制都不會(huì)陌生。前者是因?yàn)锳ndroid的早
期設(shè)計(jì)中,對(duì)dex文件中方法id用16位整型標(biāo)記,單個(gè)dex文件中的方法數(shù)無(wú)法超過(guò)65535,eclipse環(huán)境中生成不了未做過(guò)proguard的deBug apk。
后者則是dalvik虛擬機(jī)用來(lái)加載類的堆內(nèi)存大小被硬編碼了,2.3以下是5M,2.3以上是8M,致使App無(wú)法安裝的原因就是因?yàn)檫@個(gè)堆內(nèi)存被耗盡導(dǎo)致dexopt失敗。
現(xiàn)在來(lái)看肯定大家都覺(jué)得不是問(wèn)題,因?yàn)镚oogle已經(jīng)給出了一些可靠的解決方案,輔以更加先進(jìn)的gradle + Android Studio,開(kāi)發(fā)者們可能根本不會(huì)再遇到這兩個(gè)經(jīng)典問(wèn)題,官方的MultiDex分dex機(jī)制解決了方法數(shù)限制的問(wèn)題,其中main dex最小化原則,結(jié)合dalvik LinearAlloc heap size調(diào)整(修改
到了16M),使得dexopt的失敗幾率大幅下降。而ART的出現(xiàn)徹底不再存在LinearAlloc這樣的限制。
但是我們回過(guò)來(lái)再看,那個(gè)在用戶Android 2.3還占50%的時(shí)代里,是如何通過(guò)軟件架構(gòu)調(diào)整解決這個(gè)問(wèn)題的,其中的經(jīng)驗(yàn)有我們值得借鑒和學(xué)習(xí)的地方。
App V2.0架構(gòu)
基于上述我們遇到的問(wèn)題,我們?cè)谠瓉?lái)的傳統(tǒng)架構(gòu)上又做了重新調(diào)整和優(yōu)化,提出了移動(dòng)端架構(gòu)V2.0,其主要設(shè)計(jì)思路就是:
在業(yè)務(wù)快速發(fā)展過(guò)程當(dāng)中,發(fā)展到5.0的時(shí)候App上已經(jīng)承載了很多業(yè)務(wù)功能,但其中一些功能用戶使用頻率比較低,并且之前快速試錯(cuò)被證明效果不佳的一些功能也大量存留在現(xiàn)有版本中。這些不常使用的功能不應(yīng)該始終占用程序資源,所以從架構(gòu)上進(jìn)行縱向分離,保證主要重要場(chǎng)景的體驗(yàn),是這一時(shí)期的主要設(shè)計(jì)思路,這時(shí)期的架構(gòu)設(shè)計(jì)圖如圖6所示。
圖6 攜程移動(dòng)架構(gòu)V2
要實(shí)現(xiàn)這個(gè)架構(gòu),第一步就是進(jìn)行各個(gè)BU業(yè)務(wù)線的功能解耦,這個(gè)工作花費(fèi)了整個(gè)團(tuán)隊(duì)大概3個(gè)月時(shí)間3個(gè)App大版本的周期去進(jìn)行。
進(jìn)行功能解耦的重要思想,就是實(shí)行輕重分離,主次分明的思想;在代碼模塊的組織架構(gòu)上進(jìn)行重要的調(diào)整,保證主要重要的App功能快速迭代和性能穩(wěn)定,將附屬的使用頻率不高的新功能,使用H5容器進(jìn)行動(dòng)態(tài)加載,所以在V2.0的架構(gòu)上,攜程App就是個(gè)典型的Hybrid App ,可以看到剛開(kāi)始就核心模塊酒店和機(jī)票采用Native 進(jìn)行開(kāi)發(fā),其他模塊基本是采用H5去實(shí)現(xiàn)。
V2.0架構(gòu)基礎(chǔ)上,做了一系列的工作就是將App中比較雞肋的功能比如客戶價(jià)值和轉(zhuǎn)化率低的功能轉(zhuǎn)成H5實(shí)現(xiàn)。這樣做的好處就是集中精力去優(yōu)化Native業(yè)務(wù)體驗(yàn),同時(shí)也能減小Android因?yàn)榉椒〝?shù)超標(biāo)的限制壓力。
在V2.0這個(gè)階段還做了一件事情去解決dex 65535的問(wèn)題,即將工程項(xiàng)目里面出現(xiàn)的不再使用的類和不再使用的方法進(jìn)行了集中清理,這樣的好處是代碼也整理干凈了,如果方法數(shù)超出的不是太多的話通過(guò)清理就可以讓方法數(shù)減少到65536以下,同時(shí)還清理了不使用的jar包、重復(fù)引入的jar包以及對(duì)第三方j(luò)ar包進(jìn)行瘦身,一般來(lái)說(shuō)jar里面的方法數(shù)最好,清除一兩個(gè)無(wú)用的jar包就能大大的減少方法數(shù)。
同時(shí)這個(gè)階段還定義了一個(gè)原則,一些信息說(shuō)明展示或者活動(dòng)優(yōu)惠頁(yè)面,非用戶主流程的頁(yè)面都是采用H5去實(shí)現(xiàn),一方面減少開(kāi)發(fā)成本,同時(shí)也是為了應(yīng)對(duì)方法數(shù)增多的壓力。
上面三種方法都是從傳統(tǒng)的技術(shù)防守的角度即防止引入更多的方法和類,以及在原有工程角度上去瘦身,但是這兩個(gè)方法都不能本質(zhì)上去解決單dex 65535方法數(shù)限制App不能安裝的問(wèn)題,要想根本解決這個(gè)問(wèn)題,就必須減少單個(gè)Dex的大小,使用新的技術(shù)進(jìn)攻的手段去一勞永逸的去解決這個(gè)問(wèn)題。
所以接下來(lái)做了比較重大的決定就是各個(gè)BU進(jìn)行解耦,每個(gè)BU單獨(dú)獨(dú)立一個(gè)工程,每個(gè)獨(dú)立插件有獨(dú)立的UI界面邏輯和資源、存儲(chǔ)及網(wǎng)絡(luò)通信數(shù)據(jù)處理邏輯,通過(guò)共用統(tǒng)一的基礎(chǔ)庫(kù)接口訪問(wèn)網(wǎng)絡(luò)服務(wù)、圖片庫(kù)、定位庫(kù)等。V2.0架構(gòu)對(duì)應(yīng)的App工程結(jié)構(gòu)如圖7所示。
圖7 架構(gòu)V2.0對(duì)應(yīng)的工程結(jié)構(gòu)圖
攜程Dex動(dòng)態(tài)加載方案實(shí)現(xiàn)
在當(dāng)時(shí)為了徹底解決方法數(shù)溢出的問(wèn)題,基于上面解耦的基礎(chǔ)上采用了多Dex分包方案,當(dāng)時(shí)攜程的做法是借鑒Facebook提供的方案去動(dòng)態(tài)分包,將一個(gè)apk中的dex文件分割成多個(gè),然后動(dòng)態(tài)加載dex文件。首先簡(jiǎn)單描述下Facebook的思路:
攜程與Facebook的dex形式完全一致,這是因?yàn)槲覀円彩鞘褂肍acebook開(kāi)源工具buck編譯的。
Facebook將加載Dex的邏輯放于單獨(dú)的nodex進(jìn)程,這是一個(gè)非常簡(jiǎn)單、輕量級(jí)的進(jìn)程。它沒(méi)有任何的ContentProvider,只有有限的幾個(gè)Activity、Service。
android:name="com.facebook.nodex.startup.splashscreen.NodexSplashActivity">
所以依賴集為Application、NodexSplashActivity的間接依賴集即可,而且這部分邏輯應(yīng)該相對(duì)穩(wěn)定,我們無(wú)須做動(dòng)態(tài)掃描。這就實(shí)現(xiàn)了一個(gè)非常輕量級(jí)的依賴集方案。
加載Dex邏輯也非常簡(jiǎn)單,由于NodexSplashActivity的intent-f ilter指定為Main與LAUNCHER。首先拉起nodex進(jìn)程,然后初始化NodexSplashActivityActivity,若此時(shí)Dex已經(jīng)初始化過(guò),即直接跳轉(zhuǎn)到主頁(yè)面。
Facebook加載Dex的方案,其加載流程圖如圖8所示。
圖8 Facebook 加載 Dex 流程圖
這種方式好處在于依賴集非常簡(jiǎn)單,同時(shí)首次加載Dex時(shí)也不會(huì)卡死。但是它的缺點(diǎn)也很明顯,即每次啟動(dòng)主進(jìn)程時(shí),都需先額外啟動(dòng)一個(gè)nodex進(jìn)程。盡管nodex進(jìn)程邏輯非常簡(jiǎn)單,但是也需要加載時(shí)間100ms以上。但是攜程對(duì)這個(gè)啟動(dòng)時(shí)間非常敏感,當(dāng)時(shí)推動(dòng)產(chǎn)品很難會(huì)去采用這個(gè)方案。
基于這個(gè)方案的缺點(diǎn),我們?cè)谄浠A(chǔ)上進(jìn)行了優(yōu)化方案,即能不能主進(jìn)程直接加載Dex方案,具體定的方案策略如下。
Dex形式并不是重點(diǎn),假定我們使用當(dāng)前的Dex形式,即assets/secondary-program-dex-jars/secondary-N.dex.jar。
主Dex應(yīng)該保證簡(jiǎn)單,即類似Facebook,只需要少量與Dex加載相關(guān)的類即可,并且這部分代碼是相對(duì)穩(wěn)定。我也無(wú)須去更改任何非加載相關(guān)的代碼。
這個(gè)是重點(diǎn),我們應(yīng)該通過(guò)什么加載方案去實(shí)現(xiàn)這樣的分包規(guī)則。首先大家明確若是點(diǎn)擊圖標(biāo),的確無(wú)須再起一個(gè)進(jìn)程是可行的方案,但是問(wèn)題就在于在Application初始化時(shí),或是在attachBaseContext時(shí),我們無(wú)法確保即將進(jìn)入的是主界面Activity。可能系統(tǒng)要起的是某一個(gè)Service或Receiver或者Notification,這種跳轉(zhuǎn)方式是不行的。
圖9 Multiple Dex 加載流程圖
如圖9所示,有兩個(gè)關(guān)鍵問(wèn)題需要解決:
通過(guò)何種方式掛起主進(jìn)程?
掛起主進(jìn)程過(guò)程中,是否會(huì)產(chǎn)生ANR?
關(guān)于問(wèn)題1,進(jìn)程同步可以使用pthread_mutex_xxx、 pthread_cond_xxx,但是mutex或cond要放于共享內(nèi)存中,這種實(shí)現(xiàn)方式較為復(fù)雜,所以我最后實(shí)現(xiàn)時(shí)采用的是一個(gè)最簡(jiǎn)單的方法即每隔95ms去檢測(cè)TempFile是否存在,如果存在則直接進(jìn)入主程序,同時(shí)在加載dex的工作線程中去判斷,如果加載dex成功,則創(chuàng)建TempFile。
關(guān)于問(wèn)題2,在掛起主進(jìn)程的同時(shí),去啟動(dòng)一個(gè)工作線程去加載dex,也就是這個(gè)線程是非UI主線程,不會(huì)造成阻塞UI主線程的情況,經(jīng)過(guò)多次測(cè)試,也確實(shí)沒(méi)發(fā)生ANR現(xiàn)象,這個(gè)通過(guò)分析ANR現(xiàn)象的本質(zhì)就能得出這個(gè)結(jié)論。
基于Facebook的基礎(chǔ)上我們優(yōu)化實(shí)現(xiàn)了動(dòng)態(tài)加載Dex的方案,比較完美徹底地解決了因?yàn)榉椒〝?shù)超標(biāo)而無(wú)法安裝的問(wèn)題,同時(shí)也不用擔(dān)心隨著業(yè)務(wù)發(fā)展,代碼中方法越來(lái)越多的問(wèn)題。
同時(shí)在這個(gè)階段,也就是2015年初的時(shí)候,攜程開(kāi)始全面由Eclipse工具遷移到Android studio + Gradle的構(gòu)建方式,同時(shí)由于Google支持了MutilDex方案,所以后來(lái)就直接使用了官方提供的方案。
V2.0架構(gòu)解耦之后,不同BU工程的依賴是解除了,良好的解決了以前各個(gè)不同BU相互依賴的問(wèn)題,同時(shí)也可以支持多個(gè)團(tuán)隊(duì)進(jìn)行并行開(kāi)發(fā)。但是這個(gè)階段的階段架構(gòu)存在以下兩個(gè)明顯嚴(yán)重的問(wèn)題:
即會(huì)存在如果其他BU的工程修改了,如果沒(méi)及時(shí)通知對(duì)方人員,全全局報(bào)錯(cuò),整個(gè)工程編譯都無(wú)法通過(guò),影響到其他BU的正常開(kāi)發(fā)工作。
打包不可配置,構(gòu)建編譯速度慢,因?yàn)閿y程BU很多,業(yè)務(wù)也很全而復(fù)雜,大概解耦成有10幾個(gè)工程,因?yàn)椴豢蛇x擇所以需全量編譯,所以造成一次構(gòu)建速度最慢的時(shí)候差不多30分鐘,一般10分鐘以上,所以整個(gè)開(kāi)發(fā)效率比較低,開(kāi)發(fā)人員的體驗(yàn)感也比較差。
App架構(gòu)V3.0
基于上述缺點(diǎn),我們?cè)赩2.0的架構(gòu)基礎(chǔ)上又進(jìn)行了優(yōu)化,提出了V3.0的架構(gòu),具體的架構(gòu)圖如圖10所示。V3.0架構(gòu)在V2.0的工程解耦升級(jí)的基礎(chǔ)上去完成了,V3.0架構(gòu)是基于Bundle的動(dòng)態(tài)加載插件化架構(gòu),即幾乎工程中的任何組織形態(tài)都可以看成Bundle, 而最終攜程App 由一系列的Bundle組合而成,運(yùn)行在可以容納加載的Bundle容器DynamlicLoader中。
圖10 V3.0架構(gòu)圖
如圖10所示,應(yīng)用層的酒店、機(jī)票、火車票等都是一個(gè)個(gè)獨(dú)立的APK,它們之間獨(dú)立開(kāi)發(fā),互相不受影響。最終統(tǒng)一以插件的方式集成到統(tǒng)一的攜程APK里面。酒店和機(jī)票之間通迅方式采取兩種方式,BUS數(shù)據(jù)總線跳轉(zhuǎn) 和 URL Scheme跳轉(zhuǎn)。
V3.0架構(gòu)對(duì)應(yīng)的工程結(jié)構(gòu)圖如圖11所示。
圖11 架構(gòu) V3.0對(duì)應(yīng)的工程結(jié)構(gòu)圖
如圖11所示,現(xiàn)有的工程結(jié)構(gòu),有超過(guò)30個(gè)Bundle(apk),并且隨著未來(lái)業(yè)務(wù)的發(fā)展,其Bundle是越來(lái)越多。為了解決Bundle過(guò)多造成編譯速度過(guò)慢的問(wèn)題,我們采用配置文件去動(dòng)態(tài)靈活配置,各個(gè)BU需要使用什么Bundle,通過(guò)簡(jiǎn)單的一句配置,將其加到工程中即可,同時(shí)其他不需要打進(jìn)來(lái)的Bundle支持aar(.a)和源碼依賴,按需添加依賴即可。
為了一勞永逸解決我們V2.0遇到的Dex方法數(shù)超標(biāo)的問(wèn)題,我們內(nèi)部基于目前攜程App的現(xiàn)狀研發(fā)實(shí)現(xiàn)了一個(gè)動(dòng)態(tài)加載的插件化框架DynamicLoader,支持即時(shí)加載,按需加載,遠(yuǎn)程加載三種方式。即時(shí)加載,即剛開(kāi)始就直接加載進(jìn)來(lái),按需加載是使用的時(shí)候才去加載,遠(yuǎn)程加載即剛開(kāi)始沒(méi)有這個(gè)工程,然后用戶通過(guò)遠(yuǎn)程安
裝就可以直接使用這個(gè)功能。這種機(jī)制同時(shí)也支持了我們后續(xù)使用到了Hotfix機(jī)制。在這里首先簡(jiǎn)單總結(jié)下目前市場(chǎng)上出現(xiàn)了比較著名的開(kāi)源的插件化框架如表1所示。
表1 市場(chǎng)主流插件化技術(shù)對(duì)比
如表1所示,攜程在2015上半年開(kāi)始著手研究自己的插件化框架,同時(shí)也對(duì)當(dāng)時(shí)市場(chǎng)上的插件化技術(shù)做了調(diào)研,最終得出結(jié)果,當(dāng)時(shí)市場(chǎng)上的主流框架都不能滿足攜程當(dāng)時(shí)工程結(jié)構(gòu)的現(xiàn)狀和當(dāng)時(shí)插件化的需求,也就是接入其插件化之后,攜程的各個(gè)BU團(tuán)隊(duì)需要很多額外的開(kāi)發(fā)成本去實(shí)現(xiàn)整體遷移,同時(shí)還不能有效保證后續(xù)的插件化穩(wěn)定性,基于此背景下,攜程的插件化應(yīng)運(yùn)而生,其實(shí)現(xiàn)原理是通過(guò)系統(tǒng)的ClassLoader動(dòng)態(tài)加載類,通過(guò)系統(tǒng)的AssetManager去動(dòng)態(tài)加載插件的資源,同時(shí)通過(guò)修改aapt的源碼去替換系統(tǒng)的Appt解決各BU資源之間沖突的問(wèn)題。關(guān)鍵是各BU原有的代碼和現(xiàn)有的開(kāi)發(fā)模式都不需要額外的去改動(dòng)從而增加額外的開(kāi)發(fā)成本,插件化的思想即一切皆Bundle組件的思想,每個(gè)Bundle有自己的版本號(hào),通過(guò)BundleManager 去管理Bundle的升級(jí)。
在V3.0架構(gòu)推進(jìn)階段,為了需要支持按需加載的時(shí)候,其Bundle加載的速度,我們約定了一個(gè)規(guī)則:即每個(gè)Bundle加載的時(shí)間不需要超過(guò)500ms。所以需要對(duì)大Bundle進(jìn)行拆分,比如酒店和機(jī)票內(nèi)部又拆分了自己的6個(gè)Bundle。
V3.0架構(gòu)就比較適合中到大型團(tuán)隊(duì),并且解耦之后,可以支持多個(gè)團(tuán)隊(duì)的并行開(kāi)發(fā),也可以滿足多個(gè)版本的同時(shí)開(kāi)發(fā)和發(fā)布。每個(gè)BU團(tuán)隊(duì)所做的工作就是在發(fā)布之前提供一個(gè)Bundle即可,然后到發(fā)布集成階段,將其集成到攜程的統(tǒng)一APK里面。
進(jìn)入到2015年后,攜程在軟件架構(gòu)上逐漸趨于平穩(wěn)。在V2.0原有插件加載基礎(chǔ)上,研究了更多行業(yè)內(nèi)Android應(yīng)用的技術(shù)架構(gòu),并且也結(jié)合官方MultiDex的實(shí)現(xiàn)。
V3.0在V2.0解耦的基礎(chǔ)上,自己實(shí)現(xiàn)了動(dòng)態(tài)加載插件化框架,并且在此基礎(chǔ)上增加動(dòng)態(tài)熱補(bǔ)丁功能,通過(guò)攜程內(nèi)部的Hotfix發(fā)布平臺(tái),實(shí)現(xiàn)了攜程客戶端補(bǔ)丁版本更新直接覆蓋,用戶無(wú)需安裝新版本就可以將嚴(yán)重的Bug修復(fù)掉。類似阿里的AndFix熱修復(fù)技術(shù)框架。
App架構(gòu)V4.x
V3.0架構(gòu)已經(jīng)可以支持多個(gè)團(tuán)隊(duì)的快速高效并行開(kāi)發(fā),但是技術(shù)永遠(yuǎn)在前進(jìn),所以未來(lái)的V4.x架構(gòu)我們還在進(jìn)一步推進(jìn)探索中,比如我們做Native App能否像Web網(wǎng)站一樣隨時(shí)部署,即用即取,能否做到跨平臺(tái)的體驗(yàn)良好的Native App開(kāi)發(fā),能否實(shí)現(xiàn)數(shù)十個(gè)工程秒級(jí)部署編譯,從而大大提高開(kāi)發(fā)效率,這些問(wèn)題是我們Native開(kāi)發(fā)人員一直在探索追求的話題。
-
目前攜程正在推進(jìn)和已經(jīng)進(jìn)行的技術(shù)架構(gòu):
-
推出了基于ReactNative的Moles框架;
-
基于FreelLine和LayoutCast的熱部署方案;
-
Bundle的更加輕量級(jí)組件化、服務(wù)化;
總結(jié)
架構(gòu)是非常值得分享和討論的,好的技術(shù)架構(gòu)能夠持續(xù)支持偉大的商業(yè)夢(mèng)想。但是無(wú)論什么優(yōu)秀的可擴(kuò)展性好的技術(shù)架構(gòu),都不能脫離于業(yè)務(wù)而存在,最終都會(huì)隨著業(yè)務(wù)的不斷發(fā)展,而同時(shí)其架構(gòu)也在進(jìn)行不同程度的演進(jìn)與優(yōu)化。一個(gè)好的架構(gòu)首先是必須是能解決公司遇到的現(xiàn)實(shí)技術(shù)問(wèn)題和符合滿足公司目前架構(gòu)技術(shù)現(xiàn)狀,其次能帶來(lái)技術(shù)性的革新從而引領(lǐng)業(yè)務(wù)的發(fā)展。
其次做架構(gòu)之前,要想清楚這樣設(shè)計(jì)的目的是什么,通過(guò)架構(gòu)設(shè)計(jì)使程序模塊化,做到模塊內(nèi)部的高聚合和模塊之間的低耦合,做到基本符合迪米特、依賴倒置、里氏替換、接口隔離等原則。這樣做的好處是使得程序在開(kāi)發(fā)的過(guò)程中,開(kāi)發(fā)人員只需要專注于一點(diǎn),提高程序開(kāi)發(fā)的效率,并且更容易進(jìn)行后續(xù)的測(cè)試以及定位問(wèn)題。但設(shè)計(jì)不能違背目的,對(duì)于不同量級(jí)的工程,具體架構(gòu)的實(shí)現(xiàn)方式必然是不同的,切忌犯為了設(shè)計(jì)而設(shè)計(jì),為了架構(gòu)而架構(gòu)的毛病。
核心關(guān)注:拓步ERP系統(tǒng)平臺(tái)是覆蓋了眾多的業(yè)務(wù)領(lǐng)域、行業(yè)應(yīng)用,蘊(yùn)涵了豐富的ERP管理思想,集成了ERP軟件業(yè)務(wù)管理理念,功能涉及供應(yīng)鏈、成本、制造、CRM、HR等眾多業(yè)務(wù)領(lǐng)域的管理,全面涵蓋了企業(yè)關(guān)注ERP管理系統(tǒng)的核心領(lǐng)域,是眾多中小企業(yè)信息化建設(shè)首選的ERP管理軟件信賴品牌。
轉(zhuǎn)載請(qǐng)注明出處:拓步ERP資訊網(wǎng)http://www.ezxoed.cn/
本文標(biāo)題:攜程移動(dòng)端架構(gòu)演進(jìn)與優(yōu)化之路
本文網(wǎng)址:http://www.ezxoed.cn/html/solutions/14019320183.html