分散式版本

當程式碼集較大時,依附元件鏈結可能會變得非常深。即使是簡單的二進位檔,通常也會依賴數萬個建構目標。在這樣的規模下,在單一機器上我們無法在合理時間內完成建構作業:沒有任何建構系統可迴避機器硬體施行的基本法律準則。完成這項作業的唯一方法是使用支援分散式建構的建構系統,也就是系統執行的作業單元,會分散在任意且可擴充的機器中。假設我們將系統的工作細分為足夠的單位 (稍後會詳細說明),這讓我們能夠按照願意付費的方式迅速完成任何大小的版本。透過定義構件型建構系統,我們一直致力實現這個擴充能力。

遠端快取

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

具備遠端快取的分散式建構系統

圖 1:顯示遠端快取的分散式建構作業

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

為了讓遠端快取系統正常運作,建構系統必須確保建構作業可完全重現。也就是說,要針對任何建構目標,必須決定目標的輸入組合,如此一來,同一組輸入內容就能在任何機器上產生完全相同的輸出內容。這是確認下載構件的唯一方法,與自行建構成果相同。請注意,這會要求快取中的每個成果都必須在目標及其輸入的雜湊上設定索引鍵,如此一來,不同工程師就能同時對同一個目標做出不同的修改,且遠端快取會儲存所有產生的成果,而不會發生衝突。

當然,如要享有遠端快取的好處,下載構件的速度必須比建構構件更快。這並不一定是如此,尤其是如果快取伺服器與執行建構的機器相距很遠。Google 網路和建構系統經過仔細微調,以便快速分享建構結果。

遠端執行

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

遠端執行系統

圖 2:遠端執行系統

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

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

為達到這個目的,先前所述的成果型建構系統的所有部分必須一起完成。建構環境必須完全自我描述,以便我們在不需人為介入的情況下啟動工作站。建構程序本身必須完全獨立,因為每個步驟可能會在不同的機器上執行。輸出內容必須完全具有決定性,讓每個工作站都能信任其他工作站收到的結果。這類保證對於以工作為基礎的系統來說極為困難,因此就難以在基礎上建構可靠的遠端執行系統。

Google 的分散式建構系統

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

高階建構系統

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

Google 的遠端快取稱為 ObjFS。這包括一個後端,用來儲存建構輸出內容的 Bigtable 儲存在我們所有的生產機器中,以及一個名為 objfsd 的前端 FUSE Daemon (在每個開發人員的機器上運作)。FUSE Daemon 可讓工程師像瀏覽工作站中的一般檔案一樣瀏覽建構輸出內容,但只會針對使用者直接要求的少量檔案隨選下載檔案內容。隨需提供檔案內容可大幅降低網路和磁碟用量,而且與開發人員的本機磁碟儲存所有建構作業輸出內容相比,系統的建構速度可以快兩倍。

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

最終結果是可調度資源的系統,有效支援在 Google 執行的所有建構作業。Google 的規模正成極大:Google 執行了數百萬個建構作業,執行數百萬個測試案例,每天從數十億行原始碼產生數 PB 的建構輸出內容。這類系統不僅讓我們的工程師能快速建構複雜的程式碼集,還能導入大量仰賴我們建構的自動化工具和系統。