阿里巴巴開源移動容器化框架Atlas的技術演進之路
摘要:在2017云棲大會深圳峰會開源專場上,阿里巴巴手淘技術部資深技術專家倪生華(玄黎)做了題為《Atlas-容器化演進之路》的精彩演講,玄黎從Atlas的發展、特性、技術原理以及開源運作等四個方面為大家分享了手淘的移動容器化框架Atlas的技術演進之路。面對All in手淘的航母戰略,如何實現組件化?本文不容錯過。
以下內容根據嘉賓演講視頻以及PPT整理而成。
本次分享將主要分為以下四個部分:
一、Atlas的發展
二、Atlas的特性
三、Atlas的技術原理
四、Atlas的開源運作
一、Atlas的發展
Atlas開發動力
當大家打開手機淘寶時,可能就會發現阿里巴巴的手淘的業務其實很大,基本上可以在手淘中看到阿里巴巴所有的業務,比如聚劃算、天貓等等。特別是在2013年的時候,整個阿里集團開始all in手淘,手淘成為了阿里巴巴集團的一艘航母,基本上所有的業務都需要能夠在手淘上運行。

Atlas發展歷程
但是突然發現手淘自己還沒有準備好,那時候手淘基本上一個月發布一個新的版本屬于比較正常的情況,但是當突然多出了100多個業務,而這些業務可能幾天就需要更新一個版本,這樣就出現了平臺的發展速度趕不上業務發展速度的情況。除此之外,另一個變化是之前只有一個團隊進行開發,但是all in手淘戰略提出之后,手機淘寶包含了100多個業務,團隊之間的協作成為了非常大的問題。所以在這樣的需求變得越來越強烈的情況下,手淘非常需要一套框架來支持整個阿里事業部幾百個團隊的協作以及各自業務的快速迭代。所以從2012年開始,手淘技術團隊就開始嘗試構建移動容器化框架,那個時候的Atlas框架還是基于插件化的設計,進程之間相互隔離,插件彼此獨立,框架完全是一個提供服務的空殼。 
插件化架構
插件化的框架策略使用了兩年,后來就發現這樣的策略存在著很多的問題,所以在2014年,手淘技術團隊痛定思痛,完完全全地重寫了一遍Atlas,所以現在大家所看到的開源的Atlas其實就是第二版的Atlas,也就是從原來多進程的方式轉化到單一進程的方式,但是同時也實現了多進程的遍歷系統,支持業務的獨立開發、獨立部署、獨立運行。到2015年的時候,基于業務的需求重新做了按需加載,實現了對于遠程組件的支持,并且對于容器進行了升級。所以從2015年到2016年,Atlas在阿里巴巴內部穩定運行了兩年,而在207年初的時候之前正式開源了Atlas這個項目。

插件化所帶來的問題
接下來首先介紹一下Atlas在2012年時的插件化架構設計。如下圖所示的就是當時Atlas插件化的架構,最底層是容器進程:ContainerActivity與ContainerService,在這之上會存在各種各樣獨立的插件進程,比方說天貓、聚劃算等等。這些插件進程分別都有各自的生命周期,獨立地進行管理。為了實現這樣的架構就需要在系統層實現很多的事情,也就是基本上系統所要做的進程管理,Atlas 也都要完成。當時插件化的架構設計所帶來的好處就是插件之間的隔離,包括內存調度等都進行了完全的隔離;除此之外就是不需要再次開發,有新的業務進來可以部署并且直接運行。這樣的架構設計大概運行了兩年,其實也才真正完成了一年多時間就發現了一些非常嚴重的問題。第一個問題就是因為形成了整個物理的隔離,所有的APK都是隔離的,但是手機卻不是隔離的,用戶會將這些APK都放在同一個包里面。一個簡單的例子就是天貓引進了一個庫,聚劃算也引進了這個庫,在兩個APK包里面,并且可能有很多代碼屬于這個庫的,而這種情況可能出現的比較多,所以代碼的可用性就會非常非常差,這樣就會對于后面的統一調度以及管控上面造成非常不利的情況。第二方面,因為Atlas 插件化的架構設計是基于進程式的,大家都知道進程與線程相比更加重量級,所以使用插件式的方案要比原生的不采用插件式的方案的內存消耗要更大。第三部分是因為要在系統之上隔一層,所以整體的插件適配情況會比較差,插件每發布一個版本甚至每個廠家發布一個版本都需要去重新進行識別。

組件化的思考
這樣的插件化架構運行了兩年的時間,后來團隊發現簡直痛不欲生,所以在2014年初的時候整個團隊就開始重新思考手淘到底需要什么樣的架構。基于之前整個插件化的方案,在經過了深入的思考后認為:
- 首先,手淘需要高復用性,手淘不是一個大雜燴,需要進行統一地管控,對于所有的中間件、所有的服務都需要有一方進行統一管控。
- 第二方面就是要做到低侵入性,對于開發者而言,他們不需要感知容器的需求,只需要進行編碼就可以了,這就是低侵入性,也就是開發者不需要對于系統由特別深刻的了解就可以運用大量的黑科技,比如說開發者并不需要了解安卓系統底層設計,而只需要在安卓容器上使用Java層的API就可以完成自己想要實現的功能。
- 第三個方面就是高可用性,高可用性也好,性能也好,要保證使用了插件不能對于應用的性能產生太大的損耗。
- 最后一個方面也是業務插件方提出的請求,就是希望業務和業務之間使用插件隔離開來,獨立地進行開發和調試。業務今天可以跟著手淘的航空母艦一起跑,未來也可以自己獨立地運行。
在2014年初的時候,手淘技術團隊在想清楚這四條原則之后,大概花費了一個月的時間構建出了新版的Atlas框架體系。

Atlas體系
如下圖所示就是現在的Atlas體系結構,在整個容器中沒有進行混淆之前不會超過100個類。像圖中最下面的一層,稱之為Hack層,包括OS Hack toolkit & verifier,這里其實主要是為了對系統能力做一些擴展,然后做一些安全校驗。上面的一層是Bundle Franework,就是我們的容器基礎框架,這一層仿照整個OSG2層容器的概念定義了一層所有容器插件化的生命周期提供Bundle管理、加載、生命周期、安全等一些最基本的能力。再之上的一層是運行期管理層,主要在容器的運行時,對于所有Bundle的版本控制進行管理;以及清單,會把所有的Bundle和它們的能力列在一個清單上,在調用時方便查找,這樣就可以知道到底有多少個容器;這一層還存在對于類加載器以及資源的代理,這里就是和業界一些插件化框架機制類似的地方,Atlas會代理系統的運行環境,讓Bundle運行在自己的容器框架上。這一層最右邊的則是整個運行期間的監控器,可以監控運行的情況,也方便了工程期開發調試。最上面一層就是業務,這一層是面向開發者的,開發者只需要知道存在AtlasBridgeApplication就可完全去使用容器的能力,除此之外還有Tools,可以用來進行校驗和判斷。這樣帶來情況是使得組件開發與在后臺的開發是一模一樣的,經過一個代理的Resource和Classloader進行操作。而這些東西對于組件開發而言,完全不需要深入了解,這部分內容會在之后的章節會繼續展開進行分享。

Atlas業務分層
Atlas的整體業務分層如下圖所示。底層稱之為服務層或者是宿主APP,大家都知道很多應用會引入第三方的中間件或者組件,將這些組件放在基礎服務層,也就是上層的業務統一會使用的業務能力層,而這一層的業務能力不會直接去調用應用模塊。在服務層之上就是各自的業務模塊,這些業務模塊可以和技術服務層進行交互,也可以和其他業務模塊通過協議的模式進行交互。但是如果業務模塊需要使用到一些公共的業務模塊,就可以抽離出一層偏業務的服務。

基于整個Atlas的容器,手淘做到了什么樣的程度呢?大概是從2013年開始,手淘開始開發Atlas容器框架,到2014年正式成立。下圖中左邊的這張圖就顯示了手淘Android端的發布次數,從2013年發布了42次增長到2016年發布了600多次,這里的版本是指的真實交付到用戶客戶端上面的。右面這張圖是從手淘團隊從2013年開始到2016年為止,發布一個新版本所需要的時間情況,大家可以看到在2013年發布一個新的版本需要平均10天以上的時間才能交付給用戶,而到了2016年幾乎一兩天就可以交付給用戶一個新的版本,可以認為手淘的業務每天都在發生著更新。在這樣的快速迭代之上,目前整個手淘大概會有70多個備案明確的業務;對于人員而言,手機淘寶自己的部門大概會有400多個工程師,而整個阿里系里面還有20多個BU在參與其中。這些在Android方面的基礎都是基于Atlas來做的,基于Atlas才能實現對于綜合業務的快速支持。
Atlas帶來的價值

二、Atlas的特性
在第一個章節介紹了Atlas的整體發展歷程,Atlas的發展其實是基于整個業務的快速迭代和分工協作進行的,接下來為大家分享Atlas的一些特性。從2015年開始Atlas進行了組件化,整體進行了更新,擁有了很多新的特性。
對于Atlas的特性而言,可以大致如下圖所示。首先,Atlas對于開發者而言是無侵入性的,沒有任何沖突,在開發過程中不需要做任何感知,開發者原來是怎樣開發的現在依然是怎樣開發。第二個特性就是組件是獨立進行開發和調試的,組件的開發可以不依賴于其他模塊。第三個特性就是高兼容性和高穩定性,Atlas從開始運行到今年位置在阿里實際運行了4年多,組件化設計從2015年開始到現在已經運行了2年多,扛過了兩年的雙11。同時它具備動態更新能力,所謂動態更新能力就是目前Atlas可以支持組件獨立開發,Atlas兼容從Android 4.X一直到Android 8,而且對于市面上所知道的ROM都是兼容的,而且目前沒有發現任何一個ROM在手機上面會出現問題。目前手淘已經穩定運行了2年多,整體手淘的Crash率一直維持在萬分之五左右,因為容器導致的crash占比小于百分之一,而手淘Atlas的性能損耗在5%之內,也就是使用容器和不使用容器加載方式來點擊的性能損耗在5%以內。

對于Atlas的特性而言,在另一個角度上將其分為兩部分。第一方面Atlas可以做到工程師獨立,在所有的開發過程中都可以做到獨立開發、獨立調試、獨立集成以及獨立發布,也就是說業務開發可以完全脫離手淘,可以認為沒有這個大的IP存在,就像一個Web應用一樣將其放上去,甚至不用去找應用市場進行信息發布。這就是在整個工程化體系上面每個Bundle獨立開發以及獨立發布。第二個方面是在整個運行期間,之前提到了本質上是基于OS G2去做的,因為Atlas實現了資源的隔離和aapt分段,運行期中在Bundle和Bundle之間也實現了資源的隔離。所以總體而言,Atlas可以實現透明,靈活,穩定,敏捷,這同樣也是在最開始構建Atlas時所提出的四個要求。

Atlas與其他組件化方案的對比
在下圖中將市面上所有組件化解決方案進行了羅列。對于組件化方案可以進行對比去看,不一定有好壞之分,只是適不適合某個特定的場景,比如對于需要對組件進行獨立支持和獨立加載的APK適合什么樣的組件化方案,Atlas就不支持這樣加載獨立的APK,如果需要支持加載Web APK或者第三方的APK可能選擇Atlas并不合適。第二點,Atlas支持動態更新所謂的插件宿主,也就是宿主容器中間件這個層面也是支持動態更新的。第三點就是對于四大組件的動態啟動,現在對于四大組件的動態增加Atlas是部分支持的。對于首次插件啟動的性能而言,如果插件引用了容器化或者組件化的方案,那么在插件首次啟動時,性能都會有一定的影響,這是因為引入這樣的方案會將資源和類的加載過程向后推。在Atlas中對于插件首次啟動的性能進行了優化,所以用戶可以感知到插件啟動是非常快速的,這個優化是基于系統層面的,并且是基于一個進程的,所以物理隔離是沒有做的,如果相關組件掛掉了,那么這個插件也會掛掉。而且Atlas的hook的程度其實是非常輕的。對于代碼運行隔離而言,其實因為Atlas的整體運行機制基本上可以做到組件和組件之間的隔離,目前非侵入式這些Atlas都是可以做到的。對于插件的懶加載,Atlas采用的策略是只有在用到插件時才會進行加載,一個像手淘這樣的應用可能會有70多個模塊,但是用戶可能并不會全部使用到,所以沒有必要在一開始全部加載進來,只需要在用戶要使用的時候把模塊加載進來就可以,比如用戶在手淘中點擊了天貓或者聚劃算,才會將這些插件啟動起來,不使用時不會啟動。其實下圖中標紅的部分也就是與其他的組件化方案相比,Atlas的一些特點,如果這些特點正好符合應用的需求,就可以嘗試使用Atlas,如果這些特點與需求相差甚遠,可能Atlas就并不適合這樣的場景。

Atlas動態能力
以上是在組件化的方案上進行對比,而現在比較火的就是動態更新能力。其實在阿里運行的最多的還是基于Atlas這一套的東西,接下來就為大家分享一下Atlas的動態能力。這部分從六個方面來看:
- 支持類型,目前Atlas支持Class文件,SO和資源文件的增刪改操作,可以說是全類型的動態能力。
- 兼容性,Atlas適配Android 4.x - 7.x版本,而且已經在線上穩定運行了2年多的時間。
- 高性能,通過去Verify等手段,達到極小的性能損耗。
- 補丁大小,Atlas自己有一套Cache代碼的git,包括OS、資源文件的利用,通過精細化Diff的方法,達到非常小的Patch包,手淘在增加業務時包的大小也不會也太大增加。
- 成功率,目前成功率還是非常高的,與業界相比,具有較高的部署成功率和效率,目前影響成功的最大因素就是用戶的磁盤空間。
- 開發透明,這一點也就是對于開發者友好。如果開發者需要去開發動態部署的能力或者動態更新的能力,其實不需要去關心應用是不是動態更新的,只需要在業務代碼里提交一個新的版本,Atlas會幫助開發者自動生成Diff包。

目前存在的局限也可能是所有動態更新中都會存在的局限,就是暫時不支持Manifest中的權限等內容,如果涉及到Manifest里面的權限就需要做系統的注冊,所以這些Atlas還是不支持的;而且Atlas對于Activity,Service的新增也是有限支持的。
三、Atlas的技術原理
上面就主要為大家介紹了Atlas的兩個特性:組件化和周邊化。接下來會為大家簡單地介紹一些Atlas的整個技術原理。
類加載機制
組件化也好,動態部署也好,都需要進行類加載來完成這樣的事情。大家都知道所有的類加載都是基于ClassLoader來執行的,Atlas的核心就是在每個Bundle下面去設置一層BundleClassLoader,Bundle類加載器的邏輯則是在Bundle進行查找的時候首先會去查找自己的Bundle里面有沒有這個類,如果有的話就不會再去尋找,如果沒有就會去找Host,也就是整個宿主類的Classloader里面有沒有這個類,如果還沒有就會去系統或者其所依賴的關系中去找尋這個類。對于下面整張圖而言,其實原理非常簡單,還是Classloader雙親委托這樣的一件事情,Atlas只是在Android和Java的基礎之上設計了兩層的Classloader,通過自己定義的Classloader的加載規則來實現對于整個宿主的隔離,從這個意義上講是比較簡單的。

資源處理機制
除了類之外,開發過程中比較關心的就是資源問題。那么Atlas的資源處理機制時怎樣做的呢?其實也非常簡單,但是還是會與ClassLoader有所區別的。在Android某些系統上面將資源進行隔離時會存在一定的兼容性問題,所以Atlas采用了如下圖中右圖所示的另外一種做法,把所有的Bundle資源在編譯期進行了隔離,每個Bundle的資源段都會通過AP去進行修改,給出一個統一的資源字段,通過在加載的時候將每個Bundle的資源放到AssetsManager里面。Atlas沒有做運行時的隔離,而是通過編譯期的資源分段模式實現了資源隔離的方案,所以在Bundle里面可以使用到Bundle自身的資源也可以使用到宿主的資源。

動態更新能力
目前Atlas動態更新支持三種模式:
- 第一種模式指的是組件遠程下載。當拿到這個需求的時候,大家可能都會比較慌,有一個Bundle特別大可能無法裝入到APK里面,而在Atlas里面其實做的事情也很簡單,它只是編譯的過程中讓這個Bundle參與編譯,但是在最后發布過程中將這個Bundle去除掉,在需要它運行的時候從遠端把這個Bundle下載下來。這種方式使用的比較多的地方就是手淘的預裝包,一般現在的APK都會非常大,通過這種方式可以將主要功能放入到UPDATE APK里面去,一些次要的功能可以通過遠程包配置,當這個Bundle啟動的時候再去下載遠程包,這就可以認為是從無到有。當然這并不是嚴格意義上的從無到有,因為在編譯的過程中已經將所有的Manifest都已經打包進去了,次要的功能通過遠程下載的方式進行配置。
- 第二種動態加載的模式是業務組件動態更新。因為整個宿主類或者中間層的服務類本身的變動一般而言都是比較少的,基本上這些類不會每天都發生更新變化,應對這樣的需求可以采用業務組件動態更新,因為可以認為是完全獨立的ClassLoader進程,可以認為是剝離在整個Android系統的進程之外的,可以實現什么時候需要就去加載什么樣的資源。業務組件的動態更新其實是通過組件Diff和Merge的過程進行更新的,并且這樣使直接在組件的生命周期內做,兼容性極好。因為不會涉及到系統操作,只是一個資源虛化,原來是直接加載資源,現在是Patch下來之后直接生成一個新的資源,這種方式的兼容性會非常好。
- 第三縱動態加載模式是如果真的需要修改宿主類時就回歸到常見的通過類、資源、SO文件等的diff算法,通過merge算法來實現更新,有比較好的兼容性。

Bundle的部署
下圖是現在Bundle部署的diff過程。在進行Bundle打包的時候可能會打一份源代碼,發布哪個就會在倉庫里面存儲一個版本,第二次發布的打了一個新的包,并對每個資源進行diff,比如class與class之間通過dex格式形成一個新的classes.dex。Resource與Resource之間可能會去基于MD5進行diff。而對于research和assert會基于Dex merge的模式進行diff。對于資源文件也是一樣,基于手機上的文件和Patch文件生成一個新的資源合集,基于所有的Patch會生成新的運營期的APK文件的容器模型。今天可能發布一個Patch明天還會發布一個Patch,為了支持Patch最小化就需要使用增量發布。增量發布能夠使得用戶只要更新了一次,下次再進行更新只要發生變化的資源和類就可以了,不需要將所有的從源文件到類都更新,這樣就可以保證更新量比較小,速度也比較快。

目錄結構
下圖是Atlas在手機上面的目錄結構。其實在每個Bundle下都會各自有一個目錄結構,第一次發布完成之后會合成一個version 1,在后面patch目錄中會合成一個patch 1,當發布了第二個版本時,會在運行的時候去取Version 2這個版本。由于基于目錄結構來做的,所以回滾操作也會非常簡單。所以如果基于動態部署來實現,更新版本后如果發現有問題,只需要在服務端下發一個命令就可以刪掉或者回滾,而不需要用戶進行操作。在服務端也存在清單的概念,所以可以精確感知到哪幾個版本或者模塊是存在問題的。所以基于這套機制,客戶端的發布和之前服務端的發布是一模一樣的,開發者可以去隨時發布或者隨時回滾,大大地提升了發布和回滾的效率,一個命令下去可能幾秒鐘之內就能夠回滾到上一版本或者之前的版本上去。

中間件代碼部署
下圖展示的是Atlas為了規避性能損失的一個措施,其實在整個容器這部分,因為Android的限制需要根據classLoader來更新,會使用插裝的方式和Verify機制。而現在發現基于系統的dex機制可以不使用插裝的機制,所以現在Atlas的做法是用戶手機上面設置多個dex,如果新的dex和原來是重復的,就會將重復的dex刪除掉,自動合并到新的dex里面去,這樣做的好處是這個機制完全符合谷歌的Manifest規則體系的,其次因為取消了Verify機制,整體的性能會非常快。

宿主資源部署
因為安卓本身有一個限制,所有的資源必須得在base包里,新增一個資源是不生效的。所以一個做法是在打包的時候預留很多空資源。另外更新已有的資源則通過資源覆蓋來完成。在宿主中會預留一些空資源段,當新增的時候就可以適應這些主dex預留的資源段。也就是如下圖中顯示的會有很長一段null的資源,如果增加了資源就需要對于這些標記為null的資源段進行更新。

四、Atlas的開源運作
到目前為止,手淘技術團隊有一個專職的開發團隊去維護Atlas。目前整個阿里系能夠看得到的使用了Atlas的應用有很多,而且目前阿里巴巴對內和對外的Atlas是一套代碼,所以大家不必擔心Atlas開源之后不會進行維護了。

下圖展示的就是Atlas未來的一些版本計劃。接下來首先還需要繼續提升Atlas的自身穩定性以及兼容性。其次,因為開源了Atlas,所以也需要降低接入的成本。大概6月份,希望能夠提供出IDE快速調試開發插件,來完善Atlas工具鏈。并希望新增支持動態Service/適配Android O。大概在9月份Atlas會提供極簡接入工具,并對于動態部署擴展(小patch方式)/dexmerge的算法進行改進。

來自:https://zhuanlan.zhihu.com/p/27080871