Code:美團程式碼託管平臺的演進與實踐

美團技術團隊發表於2023-02-04
美團程式碼託管平臺經過長期的打磨,完成了分散式架構的改造落地,託管數以萬計的倉庫,日均Git相關請求達到千萬級別。本文主要介紹了美團程式碼託管平臺在迭代演進過程中面臨的挑戰及解決思路,希望對大家有所幫助或啟發。

1. 引言

Code是美團自研的程式碼託管平臺,其中包括了程式碼版本管理、分支管理及程式碼評審等功能,協同眾多研發流程工具平臺,支撐內部所有工程師的日常研發工作。經過近3年的建設,目前Code託管了數以萬計的倉庫,日常處理千萬級的Git相關請求,穩定支撐著美團研發流程規範的持續落地。本文主要介紹美團在建設程式碼託管平臺過程中面臨的一些挑戰和實踐經驗。

2. 美團程式碼託管平臺建設之路

2.1 程式碼託管平臺的發展史

回顧美團程式碼託管平臺的發展史,整個歷程可以劃分為三個階段:單機部署、多機部署以及自研分散式程式碼託管平臺。

第一階段:單機部署

美團最初的程式碼託管平臺,和絕大多數Web系統一樣,單機部署即可執行,所有使用者的請求均透過Web應用進行響應。由於Git使用基於檔案組織形式的儲存模式,無論是透過頁面訪問還是執行Git命令操作,最終都會表現為磁碟的檔案讀寫,高IO磁碟尤為重要。整體架構如下圖1所示:

圖1 單機部署

第二階段:多機部署

在訪問規模不大的情況下,第一階段這種單機架構可以滿足日常的開發需求。但隨著研發團隊業務需求的不斷增長,測試自動化流程的逐步完善,擴充套件性瓶頸也愈發明顯,主要表現為以下2個方面:

  • 儲存:由於公司資源限制和地域分配不均等因素,程式碼託管平臺部署機器已配置最大容量的可用SSD磁碟,使用率仍高達80%,可用空間嚴重不足。
  • 負載:隨著研發人員的不斷增多,在訪問高峰期,CPU和IO負載高達95%以上,頁面出現嚴重的卡頓,僅能透過限流保障系統的持續服務。

因而,單機部署無法再承載高峰期的訪問量,系統擴容刻不容緩。於是,我們開始設計了一套能夠透過多機負載同一倉庫IO的讀寫分離架構方案,以解決較為嚴重的IO負載問題。在讀寫分離架構中,最重要的是要保證使用者視角的資料一致性(使用者隨時可以讀取提交的最新程式碼),這裡採取了以下措施:

  1. 寫操作僅發生在主節點。
  2. 採用懶漢同步模式,在讀取資料時觸發從節點同步資料,若失敗,則路由到主節點。
  3. 採用獨主兜底模式,遇遇到突發情況時可以迅速禁用從節點,保障資料安全。

圖2 多機部署

如圖2所示,我們將倉庫訪問形式按照應用層協議區分為HTTP和SSH,分別由對應的解析代理模組進行讀寫分發操作後再下發到主從節點(此處採用了Round-Bobin的演算法分發讀請求),使得讀吞吐量整體擴大了2倍。對於從節點,我們部署了Agent,在使用者發起讀請求時會觸發同步倉庫資料的Fetch操作,以保證資料的一致性。

第三階段:自研分散式程式碼託管平臺

在第二階段,雖然透過多機負載IO的讀寫分離架構短暫性地解決了擴充套件性瓶頸問題,但倉庫資料仍在持續不斷地指數增長。同時,除擴充套件性問題之外,可用性瓶頸也凸顯出來,主要表現在以下2個方面:

  • 運維:無論是日常迭代更新版本還是熱修復緊急Bug,都需要停服才能部署系統,停服期間使用者無法使用程式碼託管平臺。
  • 備份:系統採用冷備份的方式多副本儲存Git資料,無法保證核心資料的實時恢復,異常情況下存在資料丟失風險。
    因此,搭建具備高可用性和水平擴充套件性的分散式架構迫在眉睫。我們調研了業界主流程式碼託管平臺的分散式方案,並結合公司內部的業務特性,最終選擇了基於應用層分片的分散式架構,該架構滿足了以下2個特性:
  • 高可用:採用三副本多活模式,規避程式碼丟失風險,且系統版本更新無需停服,單機斷電、當機均可正常提供服務。
  • 水平擴充套件:可透過擴容分片叢集的方式進行儲存和負載擴充套件,實現廣義下的“無限”容量。

綜上所述,Code基於GitLab生態開源元件二次開發,並採用了應用層分片多活模式的分散式架構方案,簡介如下:

  1. 底層儲存服務基於GitLab生態開源元件二次開發,有良好的生態和豐富的功能支援。
  2. 各服務間均透過gRPC進行互動通訊,主要考慮點是Git大多數為二進位制資料通訊,gRPC基於HTTP 2.0,有良好的傳輸效能和流式支援。
  3. 透過路由模組實現邏輯層與儲存層有效隔離,邏輯層對物理分片無感知,儲存層如同一個整體提供服務。
  4. 採用了多活複製模式的資料保障架構,提高讀寫吞吐量,滿足日均千萬級的請求量需求。
  5. 針對於應用層分片的劣勢,在架構設計時也做了相應的針對性最佳化,具體如下:

    • 熱點庫:提供了自動化的分片遷移能力,在發現倉庫出現熱點時,可進行分片遷移達到分片均衡。
    • 跨分片資料互動:透過業務層的Git事務包裝,我們使用共享Object的模式並確保相互關聯的倉庫均落在同一分片上,既避免了跨分片通訊的問題,也減少了磁碟空間佔用和訪問時延。

圖3 Code系統架構圖

3. 美團程式碼託管平臺架構演進的落地和挑戰

程式碼託管平臺在架構演進過程中,最終完成了以下兩個目標:

  • 高可用:縮短停機時間,提高可用性,系統穩定可靠。
  • 高擴充套件:針對計算和儲存資源,可以實現水平擴充套件。

接下來,針對於每個目標,本文分別從技術挑戰、方案選型、設計及解決方案等方面詳細介紹我們的實踐經驗。

3.1 擴充套件性目標

3.1.1 技術挑戰

在進行水平擴充套件改造時,主要面臨了以下兩類挑戰:

  • 規模性:在研發流程自動化等背景下,美團程式碼託管平臺需要具備千萬級吞吐、低延遲及高可用的系統效能,以提高研發效率。
  • 相容性:技術改造涉及的場景比較多,主要有兩方面的考量:(1)使用者低感知,新老系統保證現有通訊方式及平臺使用方式不變;(2)兼顧過渡時期底層儲存介質多樣性、運維體系相容等問題,並保障上下游系統的正常執行。

3.1.2 方案選型

經過對主流程式碼託管平臺(GitHub、GitLab、Bitbucket等)的調研分析,我們發現各大平臺主要採用了以下兩種架構解決擴充套件性問題。

圖4 架構設計方案對比

透過上述對比可以發現,如果直接接入共享儲存,暫時無法滿足程式碼託管平臺的穩定性和效能要求(若Git機制進行並行最佳化,且使用更高讀寫效能的分散式儲存系統,或許是一個不錯的選擇)。在共享儲存最佳化改造成本較高的前提下,我們最終採用了應用層分片的分散式架構,它既滿足擴充套件性的要求,也更加成熟和穩定,並表現出不錯的效能。

3.1.3 方案設計

我們透過代理模組實現了請求分發,透過路由模組實現了倉庫分片,透過應用模組的無狀態改造實現了彈性伸縮,從而達成了水平擴充套件的架構目標。下面將對這些模組進行詳細的介紹。

代理模組

  • SSH Proxy:提供Git-SSH操作代理,提供Git-SSH請求代理,透過路由模組獲取路由資訊,到目標機器執行SSH操作。SSH Proxy元件基於go-crypto庫開發,實現了公鑰識別使用者,流量控制,長連線超時處理,SSH轉gRPC等功能。後續計劃引入signature校驗,以應對不同的使用場景。
  • HTTP Proxy:提供Git-HTTP/Web請求代理,透過路由模組儲存的倉庫分片對映關係,決策倉庫路由節點。HTTP Proxy基於Go-Gin開發,實現了請求甄別,流量控制,多層代理等功能。最初HTTP Proxy還被作為灰度遷移的核心元件,透過流量統一收口,支援請求分發到新老Code系統,以確保請求和資料的平滑遷移。

圖5 代理模組

路由模組

  • Shard:記錄倉庫與其所在分片之間的對映關係,是應用層分片架構的“中樞系統”。Shard服務除維護對映關係外,還是容災模組必不可少的“決策者”,透過獲取各個節點當前訪問倉庫的最新版本,從而決定讀寫路由。由於Golang出色的高並發表現,目前路由相關介面的平均響應時間在15ms以內。該模組的主要特性如下:

    1. 建立倉庫和分片的對映關係,為了避免由於倉庫路徑更新造成資料夾複製/移動等行為帶來一定的複雜性,這裡採用了倉庫ID作為唯一標識。
    2. 利用Go Routine獲取節點的資料同步狀態,並透過超時機制保障使用者非無限時等待。
    3. 使用Key-Value Cache儲存倉庫和分片的對映,以降低對映關係的請求時延。

圖6 路由模組

應用模組

應用模組主要包括以下兩點功能:

  • 提供Git相關的業務邏輯介面處理程式碼內容資訊、程式碼評審等複雜性業務請求。
  • 監聽程式碼和分支變更訊息,傳送事件通知打通第三方業務系統和收集度量資訊。

整體模組架構如下圖7所示:

圖7 應用模組

3.1.4 解決思路

規模性解決思路

規模化的主要目標是:具備支撐千萬級請求的系統能力,並支援計算、儲存等資源的水平擴充套件能力,其中路由均衡是必不可少的一環。

a. 路由均衡

Code系統對資料來源可靠性要求較高,而對效能要求相對比較低,因而我們採用了嚴格仲裁的路由模式,具體的邏輯配置如下:

  • 使用版本號判定哪個節點提供的程式碼內容最新:版本號越大,代表資料越新,當版本最大時即為最新的資料。當W+R > N時總能讀到最新的資料(N:總節點個數,W:判斷寫入成功需要的響應節點數,R:讀取資料時至少要成功讀取的個數),當W越小時,寫入的可用性就越高,R越小,讀取的可用性就越高。我們選擇了N=3,R=W=2的常規推薦配置,根據機率推算可達到99.999%的可用性水平。
  • 採用讀修復模式:當讀取資料時,若發現節點資料不一致,此時觸發資料同步邏輯,以修復落後節點的資料。

該功能內建於路由模組的Shard服務,架構如下圖8所示:

圖8 路由邏輯示意圖

相容性解決思路

相容性目標總結為一句話就是:業務使用無感知。因此,我們主要從以下三個方面考慮相容性。

a. 與各系統互動方式及現有基礎設施相容

Code系統的眾多下游系統(多套前端UI、業務研發流程工具平臺等)依賴系統提供的開放API、Hook機制等擴充套件功能,為了減少系統升級對業務方造成影響,需要保證系統互動方式相容。同時還要確保系統運維監控體系正常執行,維持可監測狀態,我們主要做了以下四件事情:

  • 相容核心功能:使用頻度高的功能平移到新系統,而使用中低頻的功能,與業務溝通使用場景,再評估是否相容。
  • 重新設計部分功能:提供更為合理的WebHook配置能力及嶄新的程式碼評審功能。
  • 邊緣功能運營下線:推進廢棄和歷史遺留功能的下線,並提供合理的替代方案。
  • 打通運維體系:保持現有監控埋點及運維介面接入方式,使系統處於可維護、可監測的狀態。

b. 非分散式版本無縫切換到分散式版本

Code系統倉庫眾多,需要有低成本的使用者自主切換方式保障資料逐步遷移,我們主要做了以下三件事情:

  • 視覺化自動切換:透過頁面一鍵遷移按鈕,低成本實現從非分散式版本切換到分散式版本(遷移進度可感知,執行過程中倉庫可讀不可寫,確保資料完整)。
  • Proxy遮蔽底層儲存介質多樣性:透過Proxy保持單一的呼叫方式不變,可兼顧獲取非分散式版本和分散式版本的儲存資料。
  • 特殊資料共享儲存:使用者和SSH Public Key等資料與倉庫資料沒有強制關聯關係,可實現資料共享。

c. 歷史資料平滑遷移

Code系統存在眾多的歷史程式碼資料和業務資料,如何有效、完整地將歷史資料平滑遷移到新的分散式系統,變得尤為重要。為了達成業務使用無感知的目標,主要做了以下兩件事情:

  • 優先遷移“輕量”倉庫:先遷移使用功能單一的倉庫,根據使用者反饋逐步完善遷移能力。
  • 業務維度批次遷移:按照業務線劃分遷移批次,同類使用模式的倉庫同期遷移,以規避反饋問題多樣性。

3.2 可用性目標

3.2.1 技術挑戰

在進行可用性改造時,我們主要面臨資料安全性層面的挑戰。程式碼作為公司的重要資產之一,需達到兩方面的要求:

  1. 程式碼單點丟失可資料恢復。
  2. 使用者視角可以讀到正確的程式碼資料。

3.2.2 方案選型

目前,業界主要有以下三種主流的資料複製模式。

圖9 資料保障方案對比

業界大多數分散式版本控制系統採用的是單主複製模式保障資料安全,隨著美團內部研發流程的逐步完善,對於建立註釋Tag、分支管理等需求逐步增加,讀寫比從最初的10:1縮短到現在的5:1,因此需要較高的寫入效能。

我們權衡了高吞吐量和資料強一致性的雙重目標,在單主複製架構的基礎上,採用以倉庫維度單主複製為核心,節點多活為特性的複製模式(下文簡稱為多活模式),從而保證了資料安全和系統可用性。

3.2.3 方案設計

我們主要透過儲存模組中,對Git的讀、寫及初始化三類不同的請求分別採取相對應的資料處理機制,並結合多活複製模式,達成了高可用性的目標。

儲存模組

Git Server:主要儲存和管理Git倉庫資料,提供Git相關的gRPC介面。該服務基於GitLab生態開源元件二次開發,主要在資料同步機制、容災模組、部分底層命令上做了適配性最佳化,共涉及以下4個邏輯模組:

  1. Replication Manager:資料複製核心模組,根據不同的請求(讀、寫或初始化)執行不同的複製邏輯,從而保障資料一致性。
  2. Code Core:Git Server的核心服務模組,主要提供了Git的gRPC API供上游模組使用。
  3. Git Core:實現擴充套件性和高可用性的重要元件,這裡透過原始碼的方式將GitLab生態開源元件引入到專案中,作為第三方Git API供專案使用。
  4. Git Command Factory:Git命令的中樞控制器,提供控制Git程式數量、傳遞引數上下文,隔離執行環境及格式化輸出資料等功能。

各個邏輯模組間關聯如下圖10所示:

圖10 儲存模組

Git Cluster:又稱為分片,它由三個Git Server節點組成。三個節點間透過各自的Replication Manager模組獲取到叢集中其餘節點的IP等資訊,使用gRPC協議進行資料複製備份,可以保證使用者視角的資料一致性,邏輯架構如下圖11所示:

圖11 Git Cluster

3.2.4 解決思路

資料安全性解決思路

Code系統要解決的問題中,資料安全問題尤為重要,是保證研發流程安全可靠的關鍵。在考慮資料安全性解決思路之前,先要明確資料一致性判別準則,Code採用以下準則評判兩個倉庫資料一致。

資料一致評判準則:若倉庫所在兩個節點儲存的refs資料完全一致,則稱為這兩個節點上的倉庫資料一致。

目前系統資料安全機制主要有以下幾個特點:

a.多活複製

目前Code系統每個分片包含3個節點,即程式碼資料保證三副本,即使出現1~2臺節點故障導致資料不可恢復的情況,也可透過其他節點進行資料恢復。我們採用了多活複製模式,即任何一個滿足必要條件(當前訪問倉庫在該節點的資料均重演至最新版本)的機器節點均可以進行讀寫操作,與單主模式相比提高了寫操作的吞吐量,節省了主備切換的成本,使部署、節點替換及異常恢復更加簡單。多活複製模式約束有以下兩點:

  1. “單寫”機制:在同一時刻,同一個倉庫的寫操作須在同一節點進行。
  2. 資料安全鎖機制:若某倉庫底層Git的操作出現異常錯誤,則在資料未恢復前,其後對該倉庫的所有操作均會在該節點進行,會產生區域性熱點。

多活複製主要由資料儲存和資料壓縮兩個部分組成。

01 資料儲存

  1. Git主要由objects和refs兩類資料組成。objects資料為不可變資料,建立後為只讀模式,以檔案的形式儲存於本地磁碟中;refs資料為可變資料,可以進行更新。兩類資料分別採用不同資料來源進行儲存。
  2. 使用者在訪問倉庫時,如果某個objects沒有在任何一個分支的關聯鏈中,那麼判定為不可達,對於不可達的objects,無需維護其一致性。不可達object的示例如下:

圖12 不可達object示例圖

02 資料壓縮

在Code系統中,需要記錄refs的變更日誌以進行資料回放,保證系統的資料一致性。由於每個倉庫的refs資料變換是比較頻繁的,會產生大量的日誌,從而造成儲存壓力。因而我們採用了日誌壓縮技術,減少不必要的資料開銷,壓縮方式如下圖13所示:

圖13 資料壓縮

例如上圖中的main分支,其初始狀態為main -> a,第4個log為main -> e,第5個log為main -> f,則這3個log可以壓縮為一個log,即main -> f並將其應用於初始狀態,與壓縮前回放觸發的結果是一致的,main都將指向值為f的commit。

03 相關最佳化

在實踐過程中,我們發現採用純Git命令執行資料複製操作無法有效控制資源分配,因而從通訊方式、併發形式及複製粒度等方面做了最佳化,從而提高了整體的資料複製效率。

b. 跨機房備份

Code系統每組分片的3個節點至少來自於兩個不同的機房(目前按照規範化部署,均改造為3機房),若其中一個機房發生故障,仍可提供服務。我們對該架構做了針對性的容災演練,透過演練驗證了節點掉線對系統的影響較小,結合靈活的節點替換能力,可在30分鐘內上線新的節點,達到容災平衡狀態。

圖14 跨機房備份

c. 資料熱備

Code系統提供資料熱備機制,透過資料複製的方式,任何寫入的新資料會立即同步到其餘副本節點,基本“0”延遲,保證了使用者視角的強一致性。資料同步機制是熱備的關鍵,我們主要透過以下步驟實現。

01 寫操作階段

  1. 透過引入倉庫粒度的寫鎖,保證同一個倉庫同時只能在一個節點執行寫入操作,然後透過Git Internal Hook機制觸發object資料的同步,並持久化記錄refs資料。
  2. 副本節點透過讀取持久化的refs資料,重演操作,從而保持了refs資料與寫入節點一致。

圖15 寫操作步驟

02 讀操作階段

  1. 如果當前倉庫持有寫鎖,則直接路由至持有寫鎖的節點讀取資料。
  2. 如果未持有寫鎖,則用各個節點的版本和資料來源儲存的版本資料進行對比,將版本大於等於資料來源儲存的最新版本的所有節點作為候選路由節點,並採用負載均衡演算法進行路由;如果沒有符合條件的節點則需進行同步補償,待補償成功後再進行路由選擇 。

圖16 讀操作步驟

03 相關最佳化

在最初實現中,我們採用了無狀態同步,發現存在同步任務被多次執行的情況,後續透過任務前置檢查等方式避免了不必要的資料同步任務,最終減少了50%的同步任務。

d. 資料巡檢

資料巡檢是保證系統平穩執行,資料安全可靠必不可少的一個環節,它可以及早地發現系統中潛在的隱患。巡檢服務作為Code系統的核心服務,在降低資料風險,提高系統服務的穩定性方面起到了關鍵作用。對於巡檢服務,我們主要從以下幾個方面進行考慮:

  • 透明性:儘可能地避免對使用者的正常請求產生影響,減少不必要的干擾,對於系統訪問可以做到平穩可控。
  • 可靠性:作為資料安全的重要服務,它自身也要做到彈性伸縮,多點容災,具有高可用的特性。
  • 可維護性:對於資料巡檢發現的問題,能夠透過有效手段進行處理。同時要提高巡檢服務的效率,隨著系統架構的迭代出新、模組升級,巡檢服務要隨之更新,從而做到有效的保障。

綜合以上幾點,我們採用了無狀態的服務架構,提供定點巡檢、全量巡檢、定時巡檢等模式保障資料安全。其中巡檢的資料主要分為以下兩類:

  • refs資料:根據資料一致性評判準則,refs資料是Git核心資料,因而它的檢驗是必不可少的。
  • 版本資料:Code系統是基於版本進行讀寫路由的,因而當版本過大時,可能會產生大量的資料同步,為了避免突增同步請求對系統造成一定的IO抖動,監控版本差距是尤為必要的。

巡檢服務的整體架構如下圖17所示:

圖17 巡檢模組

4. 總結

本文系統性地介紹了美團在Code系統演進過程中面臨的擴充套件性和可用性兩大瓶頸,並分別針對上述兩類瓶頸和對應的挑戰,詳細闡述瞭解決方案和落地的實踐經驗。

基於上述的架構改造實踐,目前美團程式碼託管平臺實現了倉庫容量水平擴充套件、負載自主均衡等特性,穩定支撐著研發流程規範的落地。我們未來會在支撐研發效率,保障研發安全方面繼續進行探索和演進,爭取積累更多寶貴的實踐經驗,後續再跟大家分享。

5. 未來展望

  • 自動化運維:目前系統的運維機制自動化程度低,我們希望未來可以自動檢出系統異常並進行恢復,其中包括資料修復,自動擴容及熱點遷移等功能。
  • 提供程式碼領域最佳實踐:依託研發工具平臺,持續推動美團研發流程規範的迭代更新,沉澱最佳實踐並提供有力的工具支撐。
  • 程式碼安全:與資訊保安團隊緊密合作,提供更為完備的安全控制策略,包括程式碼掃描、漏洞自動修復、危險行為預警等功能。

6. 本文作者及團隊簡介

潘陶、費翔、丹丹、毛強等,來自基礎研發平臺-研發質量與效率團隊。

美團研發質量與效率團隊,負責公司研發效能領域平臺和工具的建設(包括研發需求管理工具、CI/CD流水線、分散式程式碼託管平臺、多語言構建工具、釋出平臺、測試環境管理平臺、全鏈路壓測平臺等),致力於不斷推進優秀的研發理念和工程實踐,建設一流的工程基礎設施。

閱讀美團技術團隊更多技術文章合集

前端 | 演算法 | 後端 | 資料 | 安全 | 運維 | iOS | Android | 測試

| 在公眾號選單欄對話方塊回覆【2021年貨】、【2020年貨】、【2019年貨】、【2018年貨】、【2017年貨】等關鍵詞,可檢視美團技術團隊歷年技術文章合集。

| 本文系美團技術團隊出品,著作權歸屬美團。歡迎出於分享和交流等非商業目的轉載或使用本文內容,敬請註明“內容轉載自美團技術團隊”。本文未經許可,不得進行商業性轉載或者使用。任何商用行為,請傳送郵件至tech@meituan.com申請授權。

相關文章