分散式版本

回報問題 查看來源

當程式碼集較大時,依附元件鏈結會變得非常深遠。即使是簡單的二進位檔,通常也取決於數萬個建構目標。如此規模龐大,簡直是無法在單一機器上合理時間內完成建構:沒有任何建構系統能規避機器硬體所施加的物理法則。實現這項工作的唯一方法是採用支援分散式建構的建構系統,其中工作單元會分散到任意且可擴充的機器上。假設我們將系統的工作拆解為更小的單元 (稍後會詳細說明),如此便能以願意付費的方式,盡快完成任何大小的建構工作。透過定義以構件為基礎的建構系統,這樣的擴充能力是我們一直努力的目標。

遠端快取

最簡單的分散式建構類型只使用遠端快取,如圖 1 所示。

採用遠端快取的分散式版本

圖 1:顯示遠端快取的分散式版本

所有執行建構作業的系統 (包括開發人員工作站和持續整合系統) 都會共用一般遠端快取服務的參照。這項服務可能是快速本機的短期儲存系統 (例如 Redis),或類似 Google Cloud Storage 的雲端服務。每當使用者需要直接建構成果或做為依附元件時,系統都會先使用遠端快取檢查該成果是否已存在。如果偵測到的話,它可以下載成果,而不是建構成果。如果沒有,系統會建構成果本身,然後將結果上傳回快取。也就是說,不會經常變動的低層級依附元件可建構一次並提供給所有使用者,不必讓每位使用者重新建構。Google 的許多構件都是由快取提供,而非從頭開始建構,因此可大大降低執行建構系統的成本。

為了讓遠端快取系統能夠順利運作,建構系統必須保證能完全重現建構作業。也就是說,對於任何建構目標,都必須能夠找出該目標的輸入項目組合,以便在任何機器上產生完全相同的輸入組合。這是確保下載成果結果與自行建構成果的唯一方法。請注意,如要這麼做,快取中的每個構件都必須對其目標和輸入內容的雜湊值進行索引鍵,這樣不同的工程師就能同時對同一個目標進行不同的修改,而遠端快取則會儲存所有產生的構件,並在不衝突的情況下適當提供這些構件。

當然,遠端快取會帶來任何好處,下載構件必須比建構速度更快。不過,如果快取伺服器與執行建構的電腦距離很遠,這種情況就會不見得如此。Google 的網路和建構系統經過仔細調整,以便快速分享建構結果。

遠端執行

遠端快取並非真正的分散式版本。如果快取遺失,或是您做出需要重新建構全部內容的低層級變更,您仍然需要在機器在本機上執行整個建構作業。真正的目標是支援遠端執行,透過這種方式,執行建構的實際工作可以分散到任意數量的工作站上。圖 2 說明遠端執行系統。

遠端執行系統

圖 2:遠端執行系統

在每位使用者的機器 (使用者為真人工程師或自動化建構系統) 上執行的建構工具,會將要求傳送到中央建構主要執行個體。建構主程序會將要求分成其元件動作,並安排在可擴充的工作站集區中執行這些動作。每個工作站都會根據使用者指定的輸入內容執行所要求的操作,並寫出產生的成果。這些成果會與其他執行需要這些構件的機器共用,直到最終輸出內容產生並傳送給使用者為止。

實作這類系統時,最難的部分就是管理工作站、主要執行個體和使用者本機電腦之間的通訊。工作站可能會依賴其他工作站產生的中繼構件,而且最終輸出結果需要傳回使用者的本機電腦。為此,我們可以讓每個工作站寫入其結果,並從快取讀取其依附元件,以上述描述的分散式快取為基礎。主要執行個體會阻止 worker 繼續執行,直到所需的所有項目都完成為止,此時程式就可以從快取讀取輸入內容。系統也會快取最終產品,讓本機電腦可進行下載。請注意,我們也需要另外透過使用者原始碼樹狀結構匯出本機變更,方便工作站在建構前套用這些變更。

為使這項作業能順利運作,前文介紹的所有構件式建構系統都必須一起設計。建構環境必須完全自述,以便我們在沒有人為介入的情況下啟動工作站。建構程序本身必須是完全獨立的,因為每個步驟可能會在不同的機器上執行。輸出內容必須完全確定性,以便每個工作站都能信任從其他工作站接收的結果。這樣的保證對工作導向系統而言非常困難,因此要建構可靠的遠端執行系統根本不可能。

Google 的分散式版本

自 2008 年起,Google 就一直使用分散式建構系統,同時採用遠端快取和遠端執行功能,如圖 3 所示。

高階建構系統

圖 3:Google 的分散式建構系統

Google 的遠端快取稱為 ObjFS。其中包含的後端會將建構輸出內容儲存在所有生產機器上發布的 Bigtable 中,以及在每部開發人員的機器上執行的前端 FUSE Daemon。透過 FUSE Daemon,工程師可以瀏覽建構輸出內容,就像瀏覽儲存在工作站中的一般檔案一樣,但是只會依據使用者直接要求的檔案內容下載提供。隨需提供檔案內容會大幅降低網路和磁碟用量。比起將所有建構輸出內容儲存在開發人員本機磁碟中,系統的建構速度快了兩倍。

Google 的遠端執行系統稱為 Forge。Blaze 中的 Forge 用戶端 (在 Bazel 的內部對等項目) 會將每個動作的要求傳送至在資料中心內名為「排程器」的工作。排程器會維護動作結果的快取,如果系統的其他使用者已建立動作,排程器就能立即傳回回應。如果未獲核准,系統會將該動作排入佇列。大型的執行器工作集區會持續讀取這個佇列中的動作、執行這些工作,並將結果直接儲存在 ObjFS Bigtables 中。這些結果會提供給執行程式進行日後的操作,或由使用者透過 objfsd 下載。

最後的結果是一套可擴充的系統,可有效支援在 Google 執行的所有建構作業。此外,Google 版本的規模十分龐大:Google 執行的數百萬項建構執行了數百萬個測試案例,每天透過數十億行原始碼產生了 PB 規模的建構輸出內容。這類系統不僅可讓工程師迅速建構複雜的程式碼集,還能實作大量仰賴我們建構的自動化工具和系統。