中心化決議管理——雲端分析

作者:錢佳衛,研發工程師,產品研發和工程架構部-Client Infrastructure-App Infra-DevOps-Developer Tools

前言

CocoaPods 雲端分析能力是位元組跳動的終端技術團隊(Client Infrastructure) 下 Developer Tools 部門提供的一系列雲化基礎設施之一, Developer Tools 團隊致力於建設下一代移動端雲化基礎設施,團隊通過雲 IDE 技術、分佈式構建、編譯鏈接等技術,優化公司各業務的研發和交付過程中的質量、成本、安全、效率和體驗。

一、背景

iOS 組件化研發模式下,CocoaPods 已然成為 iOS 業界標準的依賴管理工具。但隨着業務能力不斷拓展迭代,組件數量不斷增多,導致App工程複雜度急劇增大,依賴管理效率嚴重下降,甚至出現潛在的穩定性問題。為了能夠更快、更穩定得管理大型項目的組件依賴,iOS build 部門打造了一套中心化依賴管理服務——雲端依賴分析,從工具鏈的層面收斂了依賴管理流程,加速了決議速度,聚合了失敗問題。

圖片

二、什麼是雲端依賴分析

圖片

基於 CocoaPods 的 iOS 工程管理,每次執行 pod install,都需要先將組件索引信息 Spec 倉庫同步到本地,一般都依靠於 git 倉庫的 clone,然後讀取 Podfile、Lockfile 以及其他配置文件,開始進入依賴分析、依賴下載、工程整合等幾個步驟。

圖片

雲端分析是一個依賴於位元組跳動自研製品庫平台,通過工具鏈上傳本地工程構建物料,快速返回依賴分析結果,中心化管理 iOS 工程依賴的雲端服務。雲端分析服務會依賴於製品庫提供所有組件索引信息;並且通過雲端分析本地工具在環境準備過程中獲取本地工程物料,統一上傳至雲端進行依賴決議任務,雲端藉助於一系列優化手段以及服務器性能,快速返回一個決議結果,本地接收到決議結果之後進行後續的依賴下載與工程整合過程。

雲端分析的接入方式也極其容易,不需要增加配置文件,也不需要修改原有研發模式,以無侵入、無接入成本、不影響研發流程的方式接入到工程項目中。唯一需要做的,僅僅是在 CocoaPods 工具鏈中加入雲端分析的 RubyGem 插件,並在 pod install 命令中增加一個開啟優化功能的控制開關參數。

三、如何加速決議

3.1 製品庫 (全量組件索引信息)

基於 Cocoapods 的 iOS 開發體系對 iOS 的產物管理是非常粗放的,直接將不同的 git 倉庫作為構建產物(podspec 文件)的索引倉庫,擔當了製品庫的角色。隨着 iOS 工程的複雜化,git 倉庫的文件信息增加導致組件索引信息查詢困難,倉庫的同步速度緩慢。BitNest 製品庫是公司自研的移動端的產物管理系統,用於管理持續集成過程中所產生的構建產物。製品庫將分離在各個 git 倉庫的 podspec 源進行了中心化的管理,通過一套完整的 CLI 指令,能夠快速拉取、查詢 podspec 信息。雲端分析服務藉助於製品庫能力的幫助,能夠在雲端實時訪問一個全量完整的 podspec 源信息。每次CocoaPods 任務都不需要再去更新 podspec 源信息,也不會因為不及時更新 podspec 源信息而找不到最新發版的組件 podspec 信息。

3.2 緩存機制

圖片

在介紹緩存機制之前,先簡單介紹一下 pod install 中依賴分析的運行流程。在第一次執行的時候(忽略 lockfile),CocoaPods 會通過 DSL 從 Podfile 中讀取具體的 plugin,source,target,pod 等內容,創建相應的對象完成準備階段。在每個 Target 對象中每個 pod 都創建成了 Dependency 對象,並且都會有具體的 Requirements 對象。所有 Target 對象的所有 Dependency 對象都逐個被加入到堆棧中,並創建一個 Graph 依賴節點圖。每個 Dependency 對象根據其 Requirements 去對應的 Source 倉庫尋找對應的 pod,如果 Requirements 中沒有倉庫信息,就從 podfile 公共 Source 中遍歷尋找。找到對應的 pod 之後,會先建立一個版本列表,並從版本列表中找出所有符合 Requirements 要求的 pod,然後讀取對應是 podspec 文件內容。決議中會對 Spec 對象中隱式的 pod 創建新的 Dependency 加入到分析堆棧和 Graph 中。如果某個版本的 Spec 在遍歷 Graph 依賴圖時不滿足另一個同名依賴的 Requirements,就會進行出棧回撤和依賴圖回撤,直至所有 Dependency 都被找到對應的 Spec 對象為止,分析就完成了。可見,在 CocoaPods 依賴管理過程中,有大量重複的對象創建和排序查找過程,極大的降低了研發效率。試想,讓 CocoaPods 任務所需的對象一直保持就緒狀態,每當收到任務請求立即執行依賴分析工作,就可以快速返回結果。雲端分析服務集中化了所有 CocoaPods 的依賴管理任務,針對重複的工作搭建了對象緩存機制。採用懶加載的模式,對新增對象進行緩存,在下一次任務進來之後立刻進入依賴決議過程。

3.2.1 排序 Version 緩存

圖片

在分析每個 pod 時,為了能獲取最新版本的 pod 依賴,CocoaPods 會對 source 倉庫中的所有版本號建立對應的 Version 對象,並進行排序。目前,公司內部大部分製品版本已經達到上萬的數量級,而且在不指定 source 源的情況下,二進制版本和源碼版本都會被排序並讀取,最終獲取一個滿足要求且最新的版本。由於組件版本號都以 「.」 和 「-」 分段,大部分組件版本都存在4個或者5個字段以上。這也致使上萬個組件在進行排序的過程中,每次排序對比都需要遍歷4次以上,使時間複雜度提升了好幾倍,極大得增加了耗時。

為了更快得獲取到有序的版本列表,由製品庫服務維護了所有 pod 組件從大到小排序的版本文件;每增加一個新的 pod 版本,製品庫都會向文件中插入一個新版本;刪除時,則會刪除相應的版本字段。

有了有序的版本文件,雲端分析增加 Version 緩存的主要目的是為了將版本分段信息一直維持在 Version 對象中,可以快速判斷當前 Version 是否滿足依賴的要求。Version 緩存可以讓依賴管理過程提速大約10-12秒左右

雲端分析在無版本緩存的情況下,會優先讀取版本文件中的數據,直接獲得有序的版本列表;如果版本列表長度與 source 中組件版本目錄長度不一致,會回退到原始方法(版本列表出錯,確保分析的正確性)。在緩存命中的情況下,也需要判斷緩存版本列表長度是否與 pod 版本目錄長度相等(有新增版本,緩存未新增),則會從版本列表數組中查找出差異版本,並對緩存進行修正。

3.2.2 Spec 對象緩存

圖片

CocoaPods 在從排序版本中查找滿足依賴要求的 podspec 時,會將所有滿足依賴要求的 podspec 版本內容全部讀取進來,進行依賴決議遍歷。如果在不註明具體版本的情況下,所有版本的 podspec 文件都將被讀取,並且在不註明具體 source 源的情況下,所有 source 存在的 pod 也都會被讀取。一萬個 podspec 文件讀取就需要花費 30 秒左右(據不同磁盤而定)

雲端分析會對每次分析任務 IO 讀取的 podspec 文件內容進行緩存。在下次任務獲取 Spec 對象時,可以根據 source,pod_name,version 三個字段直接得到對應的Spec對象。

同時,為了確保 Spec 的正確性,防止 Spec 在不改變版本而更改內容的情況出現。Spec 對象緩存是以一個多維數組的形式存在,通過判斷 podspec 文件的修改時間,來更新緩存中的 podspec 內容為最新提交的,確保 checksum 計算與本地拉倉依賴分析的計算值相同,實現雲端依賴分析的正確性。後續,也會增加 Spec 緩存命中次數,Spec 對象過期時間等,實現 Spec 緩存的清理策略。

3.2.3 緩存復用

圖片

雲端分析也會對分析結果進行緩存,下一次遇到相同的分析任務能夠直接復用。雲端在獲取一次物料之後,會對物料做一次全局 hash 計算和一次分段 hash 計算,分別緩存完整的分析結果分析結果圖 Graph。針對下一次分析任務,如果是完全相同的物料可以直接返回一個可用的完整分析結果;如果未匹配,會通過一些 target,platform 等信息計算出一級平台信息 key,來確定具體 app 信息;再對所有target 下的組件依賴逐個計算 hash 值,獲得二級 hash 數組 key,並對應一個分析結果圖 Graph value;通過模糊匹配的方式對 hash 數組 key 進行匹配,匹配到依賴個數相同最多的相近圖,來替換物料中的 locked_dependencies,來加速分析。當然,模糊匹配能力也有一定的局限性,無法對原本上傳 lockfile 物料的分析任務進行加速。

3.3 物料剪枝

雲端分析會將 CocoaPods 對象轉變為位元組流進行傳輸。具體的上傳物料與分析結果具體如下:

圖片

1. 上傳物料

雲端分析工具鏈會將 Podfile 對象、lockfile 生成的 Molinillo Graph 對象、指定的 Source 對象、插件適配器,所有的外部源 Specs 對象(具體為指定 git,path 和 podspec 的 pre-release 對象)作為上傳物料。但其實,雲端分析並不需要這些本地對象的全部信息,可以對這些對象進行剪枝,例如 Podfile 對象僅需要 target_definitions 的鏈表即可;Molinillo Graph 對象僅需要所有 pod 對應的節點,而不需要記錄操作節點的 log;Source 對象僅需要知道 name 和 repo_dir 即可,等等。其中,部分決議優化插件需要通過插件適配器額外傳輸一些配置 Config 對象。

2. 結果返回

雲端分析返回的結果為以 Target 為 key,相應的 Specs 數組為 value 的 hash 對象。結果返回之前,會先對所有 Spec 的 Source 進行剪枝。由於每個 Spec 對應的 Source 在後續流程中僅使用到 url 的字段進行分類與生成 lock 文件。因此,可以刪除 Source 對象其他無用的字段,最小化傳輸內容,加快響應時間。對返回結果進行剪枝後,傳輸內容大小可以減少大約10MB以上

圖片

3.4 決議策略兼容

為了確保決議結果的正確性和唯一性(single truth),雲端分析兼容了位元組跳動內部各 CocoaPods 決議策略優化的工具鏈。根據工程中構建配置參數,雲端分析本地插件識別出具體的決議策略,並傳遞到雲端分析服務器並激活對應決議策略算法進行快速決議。同時,結合已有的決議優化策略和雲端的優化加速機制,讓 CocoaPods 的依賴管理流程達到秒級返回

四、總結

本文主要分享了目前位元組跳動內部的一種 CocoaPods 雲端化的優化方案,針對大量重複的 iOS 工程流水線構建任務進行了收斂和復用,在保證依賴決議正確性的前提下加速了依賴管理速率,提升了研發效能。目前雲端分析服務已經完成第一階段的開發並落地使用,已被公司內部幾個核心的生產線使用。如頭條接入雲端分析服務後,pipeline 的依賴分析階段耗時加速60%以上。後續,對於 CocoaPods 的下載優化,工程緩存服務也已經在技術探索中,相關技術文章將陸續分享,敬請期待!

擴展閱讀

CocoaPods原理詳解://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzA5MTM1NTc2Ng==&action=getalbum&album_id=1477103239887142918&scene=173&from_msgid=2458325057&from_itemidx=1&count=3&nolastread=1#wechat_redirect

CocoaPods優化://www.infoq.cn/article/adqsbwtvsyzuvh429p8w

 

加入我們

我們是位元組的 Client Infrastructure 部門下的 Developer Tools 團隊,團隊成員由 IDE 專家及構建系統專家組成,團隊致力於通過客戶端雲化技術以及編譯構建技術,優化公司各業務的研發和交付過程中的質量、成本、安全、效率和體驗。同時,在實踐的過程中我們也看到了很多令人興奮的新機會,希望有更多對編譯工具鏈技術感興趣的同學加入我們一起探索。

職位鏈接

//jobs.bytedance.com/referral/pc/position/detail/?token=MTsxNjYzMTM3NTEwNTU2OzY2ODgyMDc4MjQ2MDQyMzUyNzI7Njc5OTgyMjIzMjczNTQ1MTQwMA

圖片

【掃碼投遞簡歷】