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