為何Google將幾十億行原始碼放在一個倉庫?

AI科技大本營發表於2019-02-02

作者 | Rachel Potvin,Josh Levenberg

譯者 | 張建軍

編輯 | apddd

 

【AI科技大本營導讀】與大多數開發者的想象不同,Google只有一個程式碼倉庫——全公司使用不同語言編寫的超過10億檔案,近百TB原始碼都存放在自行開發的版本管理系統Piper中,只當專案開源且需要外部協作時,才會使用業界流行的Git。本文雖然發表於2016年,但是詳細解讀了Google採用這一方案背後的原因與經驗,直到今天仍然有很大的借鑑意義,值得一讀。

 

早期 Google 員工決定使用集中式原始碼管理系統。這種方法已維持了19年以上,至今,絕大多數軟體仍然儲存在這個共享程式碼庫中。與此同時,隨著 Google 軟體工程師數量穩步增加,程式碼庫的規模呈指數增長(見圖1)。 因此,用於託管程式碼庫的技術也發生了顯著變化。

 

關鍵點:

 

  • Google展示了原始碼管理的集中式模型可以擴充套件到包含10億個檔案,3500萬次提交記錄,由數以萬計的開發者共用的單一程式碼庫。
  • 優點包括版本統一,廣泛的程式碼共享,簡化的依賴管理,原子性變動,大規模程式碼析構,跨團隊協作,靈活的程式碼所有權和程式碼可見性。
  • 缺點包括必須為開發和執行編寫可擴充套件的工具,同時還需維護程式碼健壯性,解決潛在的程式碼庫複雜性過高問題(例如不必要的依賴關係)。

 

Google的程式碼規模

 

Google 程式碼庫包含大約十億個檔案,約3500萬次提交記錄。該程式碼庫包含 86TB 的資料,包括分佈在900 萬個原始檔的約 20 億行程式碼。 檔案總數還包括複製到釋出分支的原始檔、最新版本刪除的檔案、配置檔案、文件和支援性資料檔案。

 

2014 年,每週在 Google 程式碼庫中約有1500 萬行程式碼被修改,涉及檔案數約25萬個。Linux 核心是一個典型的大型開源軟體程式碼庫,該程式碼庫包含 40,000 個檔案,約 1500 萬行程式碼。

 

Google 的程式碼庫由來自世界各國數十個辦事處的 25000 多名 Google 軟體開發人員共享。 在工作日,他們通常會對程式碼庫提交 16000 次更改,另有 24000 次更改由自動化系統提交。每天,程式碼庫處理數十億次檔案讀取請求,峰值每秒大約有 80 萬次查詢,工作日平均每秒大約有 50 萬次查詢。大部分流量來自 Google 內部的分散式編譯系統bazel。

 

背景

 

在審視使用單一程式碼庫的優缺點之前,需要了解一些 Google 使用的工具和工作流程。

 

Piper和CitC

 

Piper是一個大型單程式碼庫,最初是基於 BigTable,現在是基於Spanner實現。Piper 分佈在全球 10 個 Google 資料中心,依靠 Paxos演算法來保證副本一致性。該架構提供了高冗餘,並對延遲進行了優化。此外,快取和非同步操作可以隱藏大量網路延遲。

 

在推出Piper之前,Google 使用的是執行在一臺機器上的Perforce(加上自定義快取基礎架構,提供服務超過10年)。Google 程式碼庫規模不斷變大是開發Piper的主要原因。

 

由於 Google 的原始碼是公司最重要的資產之一,因此安全功能是 Piper 設計的關鍵考慮因素:

 

  • 支援檔案級訪問控制列表。
  • 所有Piper使用者都可以看到大部分程式碼庫;也可以對重要的配置檔案或包含了關鍵業務演算法的檔案設定訪問限制。
  • 對檔案的讀寫訪問都進行日誌記錄。如果敏感資料被意外地提交給 Piper,可以清除有問題的檔案。管理員可以通過讀取日誌確定誰訪問過該檔案。

 

在 Piper 工作流程中,開發人員在更改程式碼庫之前會建立檔案的本地副本。 這些檔案儲存在開發人員的工作區中。Piper 程式碼庫中的更新可以根據需要被pull到工作區並與正在進行的工作進行合併。可以與其他開發人員共享工作區快照以供審查。工作區中的檔案僅在經過 Google 的程式碼審查過程後才會被提交到主程式碼庫。

大多數開發人員通過名為Clients in Cloud 的系統或 CitC 訪問Piper,該系統由基於雲的儲存後端和 Linux FUSE檔案系統組成。開發人員的工作區是檔案系統中的一個目錄。 

 

CitC支援:

 

  • 程式碼瀏覽和使用Unix工具,無需本地克隆或同步狀態。
  • 可在Piper儲存庫中的任何地方瀏覽和編輯檔案,只有修改的檔案才儲存在其工作區中。 意味著CitC工作區通常僅消耗少量儲存(平均工作空間少於10個檔案)即可向開發人員呈現整個程式碼庫。
  • 對檔案的所有寫入都作為快照儲存在 CitC 中,使得可以根據需要恢復以前的狀態。可以明確命名,恢復或標記快照以供審查。
  • CitC 工作區可以在任何連線到雲的機器上使用,使得開發人員可以在 CitC 工作區中檢視彼此的工作。

 

Piper 也可以在沒有 CitC 的情況下使用。開發人員可以將 Piper工作區儲存在本地計算機上。 Piper 還可以和 Git 進行有限的互操作。目前,超過 80% 的 Piper 使用者使用CitC,由於 CitC 有許多優勢,使用率還在持續增長。

 

基於主幹的開發

 

Google 在 Piper 原始碼庫之上實施基於主幹的開發。絕大多數Piper使用者在“頭部”(head)進行開發,指“主幹”(trunk)或者“主線”(mainline)程式碼最新版本的一份副本。對程式碼庫的更改是單一序列的。在任何程式碼提交之後,其他所有開發人員都能看到並使用新程式碼。

 

在Google,通常只在釋出上線時才會使用分支。釋出分支是從程式碼庫某次修改中分割出來的。漏洞修復和必須新增到釋出版本的增強功能通常是在主幹上進行開發,然後進行cherry-pick引入到釋出分支(參見圖3 )。由於需要保持穩定性並限制釋出分支上的過多變動,所以釋出版本通常是“頭部”的快照,根據需要可以從“頭部”進行cherry-pick更新程式碼。

 

 

當開發新功能時,新舊程式碼邏輯通常同時存在,通過使用條件標誌來控制。這種技術避免了開發分支的需要,並且通過配置更新可以輕鬆啟用或者關閉某項功能。雖然給開發人員增加了一些複雜性,但是避免了開發分支合併問題。標誌翻轉使得切換具有問題的新實現變得更加容易和快捷。該方法通常用於專案特定的程式碼,而不是通用的庫程式碼,且最終會刪除標誌和舊程式碼。

 

Google工作流程

 

Google採用了幾種最佳實踐和支援系統,以避免在基於主幹的開發模式中碰到的問題。 

 

自動測試基礎設施:Google內部的自動測試設施可以對幾乎所有由於程式碼更改而受影響的依賴項重新編譯。如果一次程式碼更改造成編譯失敗,系統就會自動回滾撤消更改。為了減少錯誤程式碼被提交到主程式碼庫的可能性,Google採用了一個內部使用的“預提交”系統,可以在更改程式碼新增到程式碼庫之前自動進行測試和分析。可以針對所有更改執行一組全域性預提交分析,程式碼所有者也可以建立僅在其指定程式碼庫中的目錄上執行的自定義分析。

 

程式碼審查:程式碼審查者會對程式碼質量進行評價,包括設計,功能,複雜度,測試,命名,註釋質量和程式碼風格(Google為不同語言編寫了不同的風格指引文件)。Google編寫了一個名為 Critique 的程式碼審查工具,允許審查者檢視程式碼的演變,並對任何一行更改進行評價或吐槽。Google鼓勵開發人員不斷修改並與審查者進行交流,當審查者最終意見為“LGTM”(Looks Good To Me,我覺得可以)時,審查過程才算完成。

 

Google 程式碼庫以樹結構呈現:每個目錄都有一組所有者控制,由他們決定是否接受對目錄中檔案的更改。變更通常會經過一位開發人員進行詳細的程式碼審查,以衡量變更的質量,以及所有者的認可批准,以評估該變更是否適合他們所在的程式碼庫位置。

 

靜態分析系統(Tricorder )和預提交系統:這些系統在 Google 程式碼審查工具中自動提供有關程式碼質量,測試覆蓋率和測試結果的資料。 這些密集檢查會週期性地,或者當有程式碼修改需要審查時被觸發。Tricorder 還為許多錯誤提供了一鍵修改建議。

 

程式碼清理:Google使用Rosie進行大規模清理和程式碼更改。開發人員可以建立一個大補丁,然後Rosie負責將大補丁分成較小的補丁進行獨立測試,並進行程式碼審查,並在通過測試和程式碼審查後自動提交。Rosie 根據專案目錄拆分補丁,依靠程式碼所有權層次結構將補丁傳送給合適的審查者。

 

圖4現實了每月通過 Rosie 進行的更改提交次數,表明 Rosie 作為 Google 大規模程式碼更改的工具的重要性。使用Rosie需要注意其使用成本。2013 年,Google 實施了正式的大規模程式碼更改-審查流程,導致了從 2013 年到 2014 年通過 Rosie提交更改的數量減少。在評估 Rosie提交的更改時,審查委員會需要在更改帶來的收益和審查者時間、程式碼庫大幅變動帶來的成本之間權衡。

總而言之,Google 使用了許多策略和工具來支援其龐大的程式碼庫,包括基於主幹的開發,分散式原始碼儲存庫 Piper,工作區客戶端 CitC 以及工作流支援工具 Critique、CodeSearch、Tricorder和Rosie。下面我們討論這個模型的利弊。

 

分析

 

優點(單程式碼庫模型支援)

 

  • 統一版本:單一程式碼庫提供統一的版本控制和單一程式碼來源。
  • 廣泛的程式碼共享和複用:如果一個團隊想要依賴另一個團隊的程式碼,可以直接依賴。 Google程式碼庫包含大量有用的庫,而單一程式碼庫可以支援廣泛的程式碼共享和複用。
  • 簡化的依賴管理:Google編譯系統可以輕鬆地在目錄之間包含程式碼,從而簡化依賴關係管理。對專案的依賴性更改會觸發依賴程式碼的重建。由於所有程式碼都在相同的儲存庫中進行版本控制,所以只有一個版本,也無需關心依賴關係的獨立版本。
  • 原子性變動:開發人員可以用一致的操作對程式碼庫中的數百或數千個檔案進行重大更改;此外,在單程式碼庫中,或至少在集中式伺服器上,所有原始碼的可用性使得核心庫的維護者在提交高影響力更改之前可以更輕鬆地執行測試和效能基準測試。
  • 大規模程式碼析構:單一程式碼倉庫為查詢和分析程式碼,提供了巨大的方便。
  • 跨團隊協作。
  • 靈活的團隊邊界和程式碼所有權:工程師不需要對共享庫進行分支開發,或者跨倉庫合併來更新程式碼。當專案所有權更改或計劃合併系統時,所有程式碼都已在同一個庫中。
  • 程式碼可見性和清晰的樹結構,提供隱含的團隊名稱空間:每個團隊在主樹中都有一個目錄結構,有效地充當專案自己的名稱空間。 每個原始檔都可以通過單個字串唯一標識,該檔案路徑可選地包含修訂版本號。

 

成本和權衡

 

開發和執行所需的工具投資

  • 單程式碼庫通常意味著更簡單的工具因為工具只需和一個引用系統打交道。然而,這需要把工具擴充到能處理單程式碼庫的規模。例如,Google為Eclipse編寫了一個自定義外掛,使IDE能夠使用大型程式碼庫。Google的程式碼索引系統支援靜態分析,程式碼瀏覽工具中的交叉引用,以及為 Emacs、Vim和其他開發環境提供豐富的 IDE 功能。這些工具需要持續的投入來管理日益增長的Google程式碼庫規模。此外,Google還需要承擔執行這些系統的成本,因此必須權衡執行這些工具的執行成本與提供給工程師有用資料的好處。
  • 程式碼庫規模的增長使得程式碼查詢變得愈加困難。開發人員必須能夠探索程式碼庫,找到相關的庫,並瞭解如何使用它們以及誰編寫它們。 庫作者經常需要了解他們的 API 如何被使用。這需要對程式碼搜尋和瀏覽工具的投資。

 

程式碼庫複雜性,包括不必要的依賴性和程式碼查詢的困難

  • 訪問整個程式碼庫鼓勵廣泛的程式碼共享和複用。 有些人會認為,這種模式依賴於 Google 構建系統的可擴充套件性,使得新增依賴關係變得太容易,並且減少了軟體開發人員設計穩定且考慮周全的 API 的動機。
  • 由於建立依賴關係的輕鬆,通常團隊不要考慮其依賴關係圖,使程式碼清理更容易出錯。此外,維護遺留專案會導致生產力下降。

 

為程式碼健壯性付出的心血

  • Google 投入巨大的努力來維護程式碼健壯性,以解決與程式碼庫複雜性和依賴關係管理相關的一些問題。 例如,專用工具會自動檢測和刪除死碼,分割大的程式碼析構,並自動分配程式碼進行審查(如通過 Rosie),並將 API 標記為不推薦使用。 需要人力執行這些工具並管理相應的大規模程式碼更改。審查由程式碼清理和更新引致的簡單程式碼析構也會產生成本。

 

備選方案

 

隨著像Git這樣的分散式版本控制系統(DVCS)的普及和使用越來越多,Google 曾考慮過是否將Piper轉移到Git作為其主要的版本控制系統。 Google 有一個團隊的任務是支援Git供 Google 的 Android 和 Chrome 團隊在主程式碼庫外使用。由於外部合作伙伴和開源協作,使用 Git 對於這些團隊很重要。

 

要轉移到基於 Git 的原始碼託管,需要將 Google 的主程式碼庫拆分成數千個獨立的程式碼庫才能實現相當的效能。這樣的重組需要改變Google開發人員的文化和工作流程。作為比較,Google 用Git 託管的Android程式碼庫被拆分為 800多個不同的程式碼庫。

 

Google 原始碼團隊目前的投入主要集中在內部原始碼系統的持續可靠性,可擴充套件性和安全性上。該團隊目前正在試用Mercurial,這是一款類似Git的開源DVCS。目標是向Mercurial客戶端新增可擴充套件性,以便高效地支援Google規模的程式碼庫。這樣,Google工程師可以通過流行的DVCS風格的工作流來使用單程式碼庫。

 

結論

 

原始碼管理的單一模型不適合所有人。它最適合像 Google 這樣的組織——具有開放和協作的文化。而對於程式碼庫中大部分是私有的,或者組與組之間程式碼不可見的組織來說並不適用。

原文連結

相關文章