分散式版本

回報問題 查看原始碼

當您的程式碼數量較大時,依附元件鏈結可能會變得非常深。即使是簡單的二進位檔,也可能依賴數萬個建構目標。在這種規模的範圍內,在單一機器上以合理的時間完成建構作業是不可能的,因為任何建構系統都無法克服機器硬體上的物理物理基礎。這項工作的唯一方法就是使用支援分散式建構的建構系統,讓系統完成的工作單元分散在任意且可擴充的機器上。假設系統已將作業範圍拆分為幾個小型單元 (稍後會進一步說明),這將方便我們快速完成任何大小的建構作業。這種擴充性是定義以構件為基礎的建構系統所發展的神聖希望。

遠端快取

最簡單的建構類型僅採用遠端快取,如圖 1 所示。

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

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

每個執行建構的系統 (包括開發人員工作站和持續整合系統) 都會共用通用遠端快取服務的參照。這項服務可能是快速本地的短期儲存系統 (例如 Redis) 或雲端服務 (如 Google Cloud Storage)。每當使用者需要建立構件 (無論是直接或做為依附元件) 時,系統會先使用遠端快取來檢查該成果是否存在。如果有,可以下載成果,而不用建構。否則,系統會建構構件本身,並將結果上傳至快取。也就是說,只要建構一次並不頻繁變動的低階依附元件,就可以跨所有使用者共用,而不需要由每位使用者重新建構。Google 提供的許多構件是由快取提供,而非從頭開始建立,可大幅降低執行建構系統的成本。

為了讓遠端快取系統正常運作,建構系統必須保證建構作業可重現。也就是說,對於任何建構目標,您必須能夠確定該目標的輸入組合,以便在任何機器上產生相同的輸入項目。這是確保下載構件結果與自行建構結果一樣的唯一方法。請注意,這需要快取中的每項成果都具有目標的目標和輸入的雜湊,如此一來,不同的工程師可以同時對同一個目標進行不同的修改,遠端快取也會儲存所有產生的構件,並在適當情況下妥善處理這些構件。

當然,如要享有遠端快取帶來的好處,下載成果的速度必須比建構快取更快。這不一定是如此,尤其是當快取伺服器與執行建構的機器相距很遠時。Google 的網路和建構系統皆經過精心挑選,以便快速分享建構結果。

遠端執行

遠端快取並非真正的分散式建構。如果快取遺失,或者您進行低層級的變更需要重新建構所有內容,您仍然必須在本機上執行本機建構。實際目標是支援遠端執行,讓執行建構作業的實際工作可以分散至任意數量的工作站。圖 2 說明遠端執行系統。

遠端執行系統

圖 2:遠端執行系統

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

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

為了達成這個目標,前提構件建構系統的所有部分都必須事先整合。建構環境必須完全自述,以便在不人為介入的情況下啟動工作站。建構程序本身必須完全獨立,因為每個步驟可能會在不同的機器上執行。輸出必須完全確定,因此每個工作站都能信任它從其他工作站接收的結果。對於以工作為基礎的系統提供這類保證,極其困難,使得在建構這些遠端系統之外,無法建構可靠的遠端執行系統。

Google 的分散式版本

從 2008 年開始,Google 就一直使用分散式建構系統,利用遠端快取和遠端執行功能,如圖 3 所示。

高階建構系統

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

Google 的遠端快取稱為 ObjFS。它包含一個後端,用於將建構輸出內容儲存在我們所有生產機器的 Bigtable 中,以及在每個開發人員機器上執行的 objfsd 的前端 FUSE 精靈。FUSE Daemon 可讓工程師瀏覽建構輸出內容,就如同它們是儲存在工作站上的一般檔案一樣,但是檔案隨選需要僅針對使用者直接要求的部分檔案進行隨選下載。以隨選方式提供檔案內容可大幅減少網路和磁碟的使用量,與儲存所有本機輸出結果至開發人員的本機磁碟相比,系統的建構速度快了兩倍。

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

最終結果是一個系統,能調度資源,有效支援在 Google 執行的所有建構作業。Google 規模的規模相當龐大:Google 執行了數百萬個測試案例,執行每天處理數百萬個測試案例,並產生每天數十億原始碼的建構輸出內容。透過這類系統,我們的工程師不僅可迅速建構複雜的程式碼集,也能實作大量仰賴建構系統的自動化工具和系統。