分散式版本

如果有大型的程式碼集,依附元件鏈結可能會變得非常深。即使是簡單的二進位檔,通常也會依賴數萬個建構目標。在這種規模下,單部機器在合理的時間內完成建構作業是無法達成的:沒有任何建構系統能規避機器硬體上所植入物理的基本法律。要達成上述目標,唯一方法就是採用支援分散式建構的建構系統,其中系統執行的工作單元會分散在任意且可擴充的機器上。假設我們已將系統作業細分為足夠小型的單元 (稍後會詳細說明),這可讓我們按照願意支付費用的速度,盡快完成任何大小的版本。我們藉由定義構件型建構系統,一直致力實現此擴充能力。

遠端快取

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

採用遠端快取的分散式建構作業

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

每個執行建構作業的系統 (包括開發人員工作站和持續整合系統) 都會共用一般遠端快取服務的參照。這項服務可能是 Redis 等快速本機短期儲存系統,或像 Google Cloud Storage 之類的雲端服務。每當使用者需要建構成果時 (無論是直接建構成果,還是做為依附元件),系統都會先透過遠端快取檢查,確認該構件是否已存在。如果是的話,它可以下載成果,而非建構成果。如果沒有,系統會建構成果,並將結果上傳回快取。這表示,不會頻繁變更的低階依附元件可以建構一次,並在所有使用者共用,而不需要每個使用者重新建構。Google 會透過快取提供許多構件,而非從頭開始建構,大幅降低建構系統的執行成本。

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

當然,如果想利用遠端快取帶來任何好處,下載成果時必須比建構速度更快。有時情況並非如此,尤其是快取伺服器與執行建構作業的機器距離很遠時。Google 的網路與建構系統經過微調,能夠快速分享建構結果。

遠端執行

遠端快取並非真正的分散式版本,如果快取遺失或您所做的低階變更需要重新建構所有內容,您仍需在本機機器上執行整個建構作業。真正的目標在於支援遠端執行,讓建構的實際工作可以分散到任意數量的工作站。圖 2 說明遠端執行系統。

遠端執行系統

圖 2:遠端執行系統

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

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

為了讓這項功能順利運作,先前所述的構件型建構系統所有部分都需要搭配在一起。建構環境必須完全由使用者自己說明,我們才能在不必人為介入的情況下啟動工作站。由於每個步驟可能會在不同的機器上執行,因此建構程序本身必須完全獨立。輸出內容必須完全確定,這樣每個工作站都能信任其他工作站接收到的結果。這樣的保證對於以工作為基礎的系統來說非常困難,因此建構可靠的遠端執行系統並不可能。

Google 的分散式版本

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

高階建構系統

圖 3 Google 的分散式建構系統

Google 的遠端快取稱為 ObjFS。它由一個後端將建構輸出內容儲存在 Bigtable,並分散到整個生產機器機群的 Bigtable,以及一個名為 objfsd 的前端 FUSE Daemon,其會在每位開發人員的機器上執行。FUSE Daemon 可讓工程師瀏覽建構輸出內容,就像是儲存在工作站中的一般檔案一樣,但僅針對使用者直接要求的少數檔案,隨選下載的檔案內容。隨需提供檔案內容可大幅減少網路和磁碟用量,比起將所有建構輸出內容儲存在開發人員本機磁碟上,系統的建構速度是兩倍。

Google 的遠端執行系統稱為 Forge。Blaze 中的 Forge 用戶端 (Bazel 的內部同等項目) 會將每個動作的要求傳送至在 Google 資料中心 (名為 Scheduler) 中執行的工作。排程器會保留動作執行結果的快取,如果系統任何其他使用者已建立動作,排程器就能立即傳回回應。如果沒有,系統會將動作排入佇列。大量的 Executor 工作集區會持續讀取這個佇列中的動作、執行這些動作,並將結果直接儲存在 ObjFS Bigtable 中。這些結果將提供給執行工具以供日後動作使用,或讓使用者透過 objfsd 下載。

最終成果是一個可以擴充的系統,能有效支援在 Google 執行的所有版本。Google 的建構作業規模十分龐大:Google 執行數百萬個建構作業,執行數百萬個測試案例,每天從數十億行原始碼產生數 PB 的建構輸出內容。如此一來,我們的工程師不僅可快速建構複雜的程式碼集,也讓我們能夠實作許多依賴我們的建構的自動化工具和系統。