【AI科技大本營導讀】與大多數(shù)開發(fā)者的想象不同,Google只有一個代碼倉庫——全公司使用不同語言編寫的超過10億文件,近百TB源代碼都存放在自行開發(fā)的版本管理系統(tǒng)Piper中,只當項目開源且需要外部協(xié)作時,才會使用業(yè)界流行的Git。本文雖然發(fā)表于2016年,但是詳細解讀了Google采用這一方案背后的原因與經驗,直到今天仍然有很大的借鑒意義,值得一讀。
早期 Google 員工決定使用集中式源代碼管理系統(tǒng)。這種方法已維持了19年以上,至今,絕大多數(shù)軟件仍然存儲在這個共享代碼庫中。與此同時,隨著 Google 軟件工程師數(shù)量穩(wěn)步增加,代碼庫的規(guī)模呈指數(shù)增長(見圖1)。 因此,用于托管代碼庫的技術也發(fā)生了顯著變化。
圖1 Google代碼庫中包含了數(shù)千萬次commit
關鍵點:
Google展示了源代碼管理的集中式模型可以擴展到包含10億個文件,3500萬次提交記錄,由數(shù)以萬計的開發(fā)者共用的單一代碼庫。
優(yōu)點包括版本統(tǒng)一,廣泛的代碼共享,簡化的依賴管理,原子性變動,大規(guī)模代碼析構,跨團隊協(xié)作,靈活的代碼所有權和代碼可見性。
缺點包括必須為開發(fā)和執(zhí)行編寫可擴展的工具,同時還需維護代碼健壯性,解決潛在的代碼庫復雜性過高問題(例如不必要的依賴關系)。
Google的代碼規(guī)模
Google 代碼庫包含大約十億個文件,約3500萬次提交記錄。該代碼庫包含 86TB 的數(shù)據(jù),包括分布在900 萬個源文件的約 20 億行代碼。 文件總數(shù)還包括復制到發(fā)布分支的源文件、最新版本刪除的文件、配置文件、文檔和支持性數(shù)據(jù)文件。
2014 年,每周在 Google 代碼庫中約有1500 萬行代碼被修改,涉及文件數(shù)約25萬個。Linux 內核是一個典型的大型開源軟件代碼庫,該代碼庫包含 40,000 個文件,約 1500 萬行代碼。
Google 的代碼庫由來自世界各國數(shù)十個辦事處的 25000 多名 Google 軟件開發(fā)人員共享。 在工作日,他們通常會對代碼庫提交 16000 次更改,另有 24000 次更改由自動化系統(tǒng)提交。每天,代碼庫處理數(shù)十億次文件讀取請求,峰值每秒大約有 80 萬次查詢,工作日平均每秒大約有 50 萬次查詢。大部分流量來自 Google 內部的分布式編譯系統(tǒng)bazel。
背景
在審視使用單一代碼庫的優(yōu)缺點之前,需要了解一些 Google 使用的工具和工作流程。
Piper和CitC
Piper是一個大型單代碼庫,最初是基于 BigTable,現(xiàn)在是基于Spanner實現(xiàn)。Piper 分布在全球 10 個 Google 數(shù)據(jù)中心,依靠 Paxos算法來保證副本一致性。該架構提供了高冗余,并對延遲進行了優(yōu)化。此外,緩存和異步操作可以隱藏大量網(wǎng)絡延遲。
在推出Piper之前,Google 使用的是運行在一臺機器上的Perforce(加上自定義緩存基礎架構,提供服務超過10年)。Google 代碼庫規(guī)模不斷變大是開發(fā)Piper的主要原因。
由于 Google 的源代碼是公司最重要的資產之一,因此安全功能是 Piper 設計的關鍵考慮因素:
支持文件級訪問控制列表。
所有Piper用戶都可以看到大部分代碼庫;也可以對重要的配置文件或包含了關鍵業(yè)務算法的文件設置訪問限制。
對文件的讀寫訪問都進行日志記錄。如果敏感數(shù)據(jù)被意外地提交給 Piper,可以清除有問題的文件。管理員可以通過讀取日志確定誰訪問過該文件。
在 Piper 工作流程中,開發(fā)人員在更改代碼庫之前會創(chuàng)建文件的本地副本。 這些文件存儲在開發(fā)人員的工作區(qū)中。Piper 代碼庫中的更新可以根據(jù)需要被pull到工作區(qū)并與正在進行的工作進行合并。可以與其他開發(fā)人員共享工作區(qū)快照以供審查。工作區(qū)中的文件僅在經過 Google 的代碼審查過程后才會被提交到主代碼庫。
圖2 Piper工作流程
大多數(shù)開發(fā)人員通過名為Clients in Cloud 的系統(tǒng)或 CitC 訪問Piper,該系統(tǒng)由基于云的存儲后端和 Linux FUSE文件系統(tǒng)組成。開發(fā)人員的工作區(qū)是文件系統(tǒng)中的一個目錄。
CitC支持:
代碼瀏覽和使用Unix工具,無需本地克隆或同步狀態(tài)。
可在Piper存儲庫中的任何地方瀏覽和編輯文件,只有修改的文件才存儲在其工作區(qū)中。 意味著CitC工作區(qū)通常僅消耗少量存儲(平均工作空間少于10個文件)即可向開發(fā)人員呈現(xiàn)整個代碼庫。
對文件的所有寫入都作為快照存儲在 CitC 中,使得可以根據(jù)需要恢復以前的狀態(tài)??梢悦鞔_命名,恢復或標記快照以供審查。
CitC 工作區(qū)可以在任何連接到云的機器上使用,使得開發(fā)人員可以在 CitC 工作區(qū)中查看彼此的工作。
Piper 也可以在沒有 CitC 的情況下使用。開發(fā)人員可以將 Piper工作區(qū)存儲在本地計算機上。 Piper 還可以和 Git 進行有限的互操作。目前,超過 80% 的 Piper 用戶使用CitC,由于 CitC 有許多優(yōu)勢,使用率還在持續(xù)增長。
基于主干的開發(fā)
Google 在 Piper 源代碼庫之上實施基于主干的開發(fā)。絕大多數(shù)Piper用戶在“頭部”(head)進行開發(fā),指“主干”(trunk)或者“主線”(mainline)代碼最新版本的一份副本。對代碼庫的更改是單一串行的。在任何代碼提交之后,其他所有開發(fā)人員都能看到并使用新代碼。
在Google,通常只在發(fā)布上線時才會使用分支。發(fā)布分支是從代碼庫某次修改中分割出來的。漏洞修復和必須添加到發(fā)布版本的增強功能通常是在主干上進行開發(fā),然后進行cherry-pick引入到發(fā)布分支(參見圖3 )。由于需要保持穩(wěn)定性并限制發(fā)布分支上的過多變動,所以發(fā)布版本通常是“頭部”的快照,根據(jù)需要可以從“頭部”進行cherry-pick更新代碼。
圖3 發(fā)布分支模型
當開發(fā)新功能時,新舊代碼邏輯通常同時存在,通過使用條件標志來控制。這種技術避免了開發(fā)分支的需要,并且通過配置更新可以輕松啟用或者關閉某項功能。雖然給開發(fā)人員增加了一些復雜性,但是避免了開發(fā)分支合并問題。標志翻轉使得切換具有問題的新實現(xiàn)變得更加容易和快捷。該方法通常用于項目特定的代碼,而不是通用的庫代碼,且最終會刪除標志和舊代碼。
Google工作流程
Google采用了幾種最佳實踐和支持系統(tǒng),以避免在基于主干的開發(fā)模式中碰到的問題。
自動測試基礎設施:Google內部的自動測試設施可以對幾乎所有由于代碼更改而受影響的依賴項重新編譯。如果一次代碼更改造成編譯失敗,系統(tǒng)就會自動回滾撤消更改。為了減少錯誤代碼被提交到主代碼庫的可能性,Google采用了一個內部使用的“預提交”系統(tǒng),可以在更改代碼添加到代碼庫之前自動進行測試和分析??梢葬槍λ懈倪\行一組全局預提交分析,代碼所有者也可以創(chuàng)建僅在其指定代碼庫中的目錄上運行的自定義分析。
代碼審查:代碼審查者會對代碼質量進行評價,包括設計,功能,復雜度,測試,命名,注釋質量和代碼風格(Google為不同語言編寫了不同的風格指引文檔)。Google編寫了一個名為 Critique 的代碼審查工具,允許審查者查看代碼的演變,并對任何一行更改進行評價或吐槽。Google鼓勵開發(fā)人員不斷修改并與審查者進行交流,當審查者最終意見為“LGTM”(Looks Good To Me,我覺得可以)時,審查過程才算完成。
Google 代碼庫以樹結構呈現(xiàn):每個目錄都有一組所有者控制,由他們決定是否接受對目錄中文件的更改。變更通常會經過一位開發(fā)人員進行詳細的代碼審查,以衡量變更的質量,以及所有者的認可批準,以評估該變更是否適合他們所在的代碼庫位置。
靜態(tài)分析系統(tǒng)(Tricorder )和預提交系統(tǒng):這些系統(tǒng)在 Google 代碼審查工具中自動提供有關代碼質量,測試覆蓋率和測試結果的數(shù)據(jù)。 這些密集檢查會周期性地,或者當有代碼修改需要審查時被觸發(fā)。Tricorder 還為許多錯誤提供了一鍵修改建議。
代碼清理:Google使用Rosie進行大規(guī)模清理和代碼更改。開發(fā)人員可以創(chuàng)建一個大補丁,然后Rosie負責將大補丁分成較小的補丁進行獨立測試,并進行代碼審查,并在通過測試和代碼審查后自動提交。Rosie 根據(jù)項目目錄拆分補丁,依靠代碼所有權層次結構將補丁發(fā)送給合適的審查者。
圖4現(xiàn)實了每月通過 Rosie 進行的更改提交次數(shù),表明 Rosie 作為 Google 大規(guī)模代碼更改的工具的重要性。使用Rosie需要注意其使用成本。2013 年,Google 實施了正式的大規(guī)模代碼更改-審查流程,導致了從 2013 年到 2014 年通過 Rosie提交更改的數(shù)量減少。在評估 Rosie提交的更改時,審查委員會需要在更改帶來的收益和審查者時間、代碼庫大幅變動帶來的成本之間權衡。
圖4 每月通過 Rosie 進行的更改提交次數(shù)
總而言之,Google 使用了許多策略和工具來支持其龐大的代碼庫,包括基于主干的開發(fā),分布式源代碼存儲庫 Piper,工作區(qū)客戶端 CitC 以及工作流支持工具 Critique、CodeSearch、Tricorder和Rosie。下面我們討論這個模型的利弊。
分析
優(yōu)點(單代碼庫模型支持)
統(tǒng)一版本:單一代碼庫提供統(tǒng)一的版本控制和單一代碼來源。
廣泛的代碼共享和復用:如果一個團隊想要依賴另一個團隊的代碼,可以直接依賴。 Google代碼庫包含大量有用的庫,而單一代碼庫可以支持廣泛的代碼共享和復用。
簡化的依賴管理:Google編譯系統(tǒng)可以輕松地在目錄之間包含代碼,從而簡化依賴關系管理。對項目的依賴性更改會觸發(fā)依賴代碼的重建。由于所有代碼都在相同的存儲庫中進行版本控制,所以只有一個版本,也無需關心依賴關系的獨立版本。
原子性變動:開發(fā)人員可以用一致的操作對代碼庫中的數(shù)百或數(shù)千個文件進行重大更改;此外,在單代碼庫中,或至少在集中式服務器上,所有源代碼的可用性使得核心庫的維護者在提交高影響力更改之前可以更輕松地執(zhí)行測試和性能基準測試。
大規(guī)模代碼析構:單一代碼倉庫為查找和分析代碼,提供了巨大的方便。
跨團隊協(xié)作。
靈活的團隊邊界和代碼所有權:工程師不需要對共享庫進行分支開發(fā),或者跨倉庫合并來更新代碼。當項目所有權更改或計劃合并系統(tǒng)時,所有代碼都已在同一個庫中。
代碼可見性和清晰的樹結構,提供隱含的團隊命名空間:每個團隊在主樹中都有一個目錄結構,有效地充當項目自己的命名空間。 每個源文件都可以通過單個字符串唯一標識,該文件路徑可選地包含修訂版本號。
成本和權衡
開發(fā)和執(zhí)行所需的工具投資
單代碼庫通常意味著更簡單的工具因為工具只需和一個引用系統(tǒng)打交道。然而,這需要把工具拓展到能處理單代碼庫的規(guī)模。例如,Google為Eclipse編寫了一個自定義插件,使IDE能夠使用大型代碼庫。Google的代碼索引系統(tǒng)支持靜態(tài)分析,代碼瀏覽工具中的交叉引用,以及為 Emacs、Vim和其他開發(fā)環(huán)境提供豐富的 IDE 功能。這些工具需要持續(xù)的投入來管理日益增長的Google代碼庫規(guī)模。此外,Google還需要承擔運行這些系統(tǒng)的成本,因此必須權衡運行這些工具的執(zhí)行成本與提供給工程師有用數(shù)據(jù)的好處。
代碼庫規(guī)模的增長使得代碼查找變得愈加困難。開發(fā)人員必須能夠探索代碼庫,找到相關的庫,并了解如何使用它們以及誰編寫它們。 庫作者經常需要了解他們的 API 如何被使用。這需要對代碼搜索和瀏覽工具的投資。
代碼庫復雜性,包括不必要的依賴性和代碼查找的困難
訪問整個代碼庫鼓勵廣泛的代碼共享和復用。 有些人會認為,這種模式依賴于 Google 構建系統(tǒng)的可擴展性,使得添加依賴關系變得太容易,并且減少了軟件開發(fā)人員設計穩(wěn)定且考慮周全的 API 的動機。
由于創(chuàng)建依賴關系的輕松,通常團隊不要考慮其依賴關系圖,使代碼清理更容易出錯。此外,維護遺留項目會導致生產力下降。
為代碼健壯性付出的心血
Google 投入巨大的努力來維護代碼健壯性,以解決與代碼庫復雜性和依賴關系管理相關的一些問題。 例如,專用工具會自動檢測和刪除死碼,分割大的代碼析構,并自動分配代碼進行審查(如通過 Rosie),并將 API 標記為不推薦使用。 需要人力運行這些工具并管理相應的大規(guī)模代碼更改。審查由代碼清理和更新引致的簡單代碼析構也會產生成本。
備選方案
隨著像Git這樣的分布式版本控制系統(tǒng)(DVCS)的普及和使用越來越多,Google 曾考慮過是否將Piper轉移到Git作為其主要的版本控制系統(tǒng)。 Google 有一個團隊的任務是支持Git供 Google 的 Android 和 Chrome 團隊在主代碼庫外使用。由于外部合作伙伴和開源協(xié)作,使用 Git 對于這些團隊很重要。
要轉移到基于 Git 的源代碼托管,需要將 Google 的主代碼庫拆分成數(shù)千個獨立的代碼庫才能實現(xiàn)相當?shù)男阅?。這樣的重組需要改變Google開發(fā)人員的文化和工作流程。作為比較,Google 用Git 托管的Android代碼庫被拆分為 800多個不同的代碼庫。
Google 源代碼團隊目前的投入主要集中在內部源代碼系統(tǒng)的持續(xù)可靠性,可擴展性和安全性上。該團隊目前正在試用Mercurial,這是一款類似Git的開源DVCS。目標是向Mercurial客戶端添加可擴展性,以便高效地支持Google規(guī)模的代碼庫。這樣,Google工程師可以通過流行的DVCS風格的工作流來使用單代碼庫。
結論
源代碼管理的單一模型不適合所有人。它最適合像 Google 這樣的組織——具有開放和協(xié)作的文化。而對于代碼庫中大部分是私有的,或者組與組之間代碼不可見的組織來說并不適用。
原文鏈接:
https://cacm.acm.org/magazines/2016/7/204032-why-google-stores-billions-of-lines-of-code-in-a-single-repository/fulltext