Code:美團(tuán)代碼托管平臺(tái)的演進(jìn)與實(shí)踐(美團(tuán)寫代碼)

美團(tuán)代碼托管平臺(tái)經(jīng)過長期的打磨,完成了分布式架構(gòu)的改造落地,托管數(shù)以萬計(jì)的倉庫,日均Git相關(guān)請(qǐng)求達(dá)到千萬級(jí)別。本文主要介紹了美團(tuán)代碼托管平臺(tái)在迭代演進(jìn)過程中面臨的挑戰(zhàn)及解決思路,希望對(duì)大家有所幫助或啟發(fā)。

1. 引言

Code是美團(tuán)自研的代碼托管平臺(tái),其中包括了代碼版本管理、分支管理及代碼評(píng)審等功能,協(xié)同眾多研發(fā)流程工具平臺(tái),支撐內(nèi)部所有工程師的日常研發(fā)工作。經(jīng)過近3年的建設(shè),目前Code托管了數(shù)以萬計(jì)的倉庫,日常處理千萬級(jí)的Git相關(guān)請(qǐng)求,穩(wěn)定支撐著美團(tuán)研發(fā)流程規(guī)范的持續(xù)落地。本文主要介紹美團(tuán)在建設(shè)代碼托管平臺(tái)過程中面臨的一些挑戰(zhàn)和實(shí)踐經(jīng)驗(yàn)。

2. 美團(tuán)代碼托管平臺(tái)建設(shè)之路

2.1 代碼托管平臺(tái)的發(fā)展史

回顧美團(tuán)代碼托管平臺(tái)的發(fā)展史,整個(gè)歷程可以劃分為三個(gè)階段:單機(jī)部署、多機(jī)部署以及自研分布式代碼托管平臺(tái)。

第一階段:單機(jī)部署

美團(tuán)最初的代碼托管平臺(tái),和絕大多數(shù)Web系統(tǒng)一樣,單機(jī)部署即可運(yùn)行,所有用戶的請(qǐng)求均通過Web應(yīng)用進(jìn)行響應(yīng)。由于Git使用基于文件組織形式的存儲(chǔ)模式,無論是通過頁面訪問還是執(zhí)行Git命令操作,最終都會(huì)表現(xiàn)為磁盤的文件讀寫,高IO磁盤尤為重要。整體架構(gòu)如下圖1所示:

Code:美團(tuán)代碼托管平臺(tái)的演進(jìn)與實(shí)踐(美團(tuán)寫代碼)

圖1 單機(jī)部署

第二階段:多機(jī)部署

在訪問規(guī)模不大的情況下,第一階段這種單機(jī)架構(gòu)可以滿足日常的開發(fā)需求。但隨著研發(fā)團(tuán)隊(duì)業(yè)務(wù)需求的不斷增長,測試自動(dòng)化流程的逐步完善,擴(kuò)展性瓶頸也愈發(fā)明顯,主要表現(xiàn)為以下2個(gè)方面:

  • 存儲(chǔ):由于公司資源限制和地域分配不均等因素,代碼托管平臺(tái)部署機(jī)器已配置最大容量的可用SSD磁盤,使用率仍高達(dá)80%,可用空間嚴(yán)重不足。
  • 負(fù)載:隨著研發(fā)人員的不斷增多,在訪問高峰期,CPU和IO負(fù)載高達(dá)95%以上,頁面出現(xiàn)嚴(yán)重的卡頓,僅能通過限流保障系統(tǒng)的持續(xù)服務(wù)。

因而,單機(jī)部署無法再承載高峰期的訪問量,系統(tǒng)擴(kuò)容刻不容緩。于是,我們開始設(shè)計(jì)了一套能夠通過多機(jī)負(fù)載同一倉庫IO的讀寫分離架構(gòu)方案,以解決較為嚴(yán)重的IO負(fù)載問題。在讀寫分離架構(gòu)中,最重要的是要保證用戶視角的數(shù)據(jù)一致性(用戶隨時(shí)可以讀取提交的最新代碼),這里采取了以下措施:

  1. 寫操作僅發(fā)生在主節(jié)點(diǎn)。
  2. 采用懶漢同步模式,在讀取數(shù)據(jù)時(shí)觸發(fā)從節(jié)點(diǎn)同步數(shù)據(jù),若失敗,則路由到主節(jié)點(diǎn)。
  3. 采用獨(dú)主兜底模式,遇遇到突發(fā)情況時(shí)可以迅速禁用從節(jié)點(diǎn),保障數(shù)據(jù)安全。

Code:美團(tuán)代碼托管平臺(tái)的演進(jìn)與實(shí)踐(美團(tuán)寫代碼)

圖2 多機(jī)部署

如圖2所示,我們將倉庫訪問形式按照應(yīng)用層協(xié)議區(qū)分為HTTP和SSH,分別由對(duì)應(yīng)的解析代理模塊進(jìn)行讀寫分發(fā)操作后再下發(fā)到主從節(jié)點(diǎn)(此處采用了Round-Bobin的算法分發(fā)讀請(qǐng)求),使得讀吞吐量整體擴(kuò)大了2倍。對(duì)于從節(jié)點(diǎn),我們部署了Agent,在用戶發(fā)起讀請(qǐng)求時(shí)會(huì)觸發(fā)同步倉庫數(shù)據(jù)的Fetch操作,以保證數(shù)據(jù)的一致性。

第三階段:自研分布式代碼托管平臺(tái)

在第二階段,雖然通過多機(jī)負(fù)載IO的讀寫分離架構(gòu)短暫性地解決了擴(kuò)展性瓶頸問題,但倉庫數(shù)據(jù)仍在持續(xù)不斷地指數(shù)增長。同時(shí),除擴(kuò)展性問題之外,可用性瓶頸也凸顯出來,主要表現(xiàn)在以下2個(gè)方面:

  • 運(yùn)維:無論是日常迭代更新版本還是熱修復(fù)緊急Bug,都需要停服才能部署系統(tǒng),停服期間用戶無法使用代碼托管平臺(tái)。
  • 備份:系統(tǒng)采用冷備份的方式多副本存儲(chǔ)Git數(shù)據(jù),無法保證核心數(shù)據(jù)的實(shí)時(shí)恢復(fù),異常情況下存在數(shù)據(jù)丟失風(fēng)險(xiǎn)。
    因此,搭建具備高可用性和水平擴(kuò)展性的分布式架構(gòu)迫在眉睫。我們調(diào)研了業(yè)界主流代碼托管平臺(tái)的分布式方案,并結(jié)合公司內(nèi)部的業(yè)務(wù)特性,最終選擇了基于應(yīng)用層分片的分布式架構(gòu),該架構(gòu)滿足了以下2個(gè)特性:
  • 高可用:采用三副本多活模式,規(guī)避代碼丟失風(fēng)險(xiǎn),且系統(tǒng)版本更新無需停服,單機(jī)斷電、宕機(jī)均可正常提供服務(wù)。
  • 水平擴(kuò)展:可通過擴(kuò)容分片集群的方式進(jìn)行存儲(chǔ)和負(fù)載擴(kuò)展,實(shí)現(xiàn)廣義下的“無限”容量。

綜上所述,Code基于GitLab生態(tài)開源組件二次開發(fā),并采用了應(yīng)用層分片多活模式的分布式架構(gòu)方案,簡介如下:

  1. 底層存儲(chǔ)服務(wù)基于GitLab生態(tài)開源組件二次開發(fā),有良好的生態(tài)和豐富的功能支持。
  2. 各服務(wù)間均通過gRPC進(jìn)行交互通信,主要考慮點(diǎn)是Git大多數(shù)為二進(jìn)制數(shù)據(jù)通信,gRPC基于HTTP 2.0,有良好的傳輸性能和流式支持。
  3. 通過路由模塊實(shí)現(xiàn)邏輯層與存儲(chǔ)層有效隔離,邏輯層對(duì)物理分片無感知,存儲(chǔ)層如同一個(gè)整體提供服務(wù)。
  4. 采用了多活復(fù)制模式的數(shù)據(jù)保障架構(gòu),提高讀寫吞吐量,滿足日均千萬級(jí)的請(qǐng)求量需求。
  5. 針對(duì)于應(yīng)用層分片的劣勢,在架構(gòu)設(shè)計(jì)時(shí)也做了相應(yīng)的針對(duì)性優(yōu)化,具體如下: 熱點(diǎn)庫:提供了自動(dòng)化的分片遷移能力,在發(fā)現(xiàn)倉庫出現(xiàn)熱點(diǎn)時(shí),可進(jìn)行分片遷移達(dá)到分片均衡。跨分片數(shù)據(jù)交互:通過業(yè)務(wù)層的Git事務(wù)包裝,我們使用共享Object的模式并確保相互關(guān)聯(lián)的倉庫均落在同一分片上,既避免了跨分片通信的問題,也減少了磁盤空間占用和訪問時(shí)延。

Code:美團(tuán)代碼托管平臺(tái)的演進(jìn)與實(shí)踐(美團(tuán)寫代碼)

圖3 Code系統(tǒng)架構(gòu)圖

3. 美團(tuán)代碼托管平臺(tái)架構(gòu)演進(jìn)的落地和挑戰(zhàn)

代碼托管平臺(tái)在架構(gòu)演進(jìn)過程中,最終完成了以下兩個(gè)目標(biāo):

  • 高可用:縮短停機(jī)時(shí)間,提高可用性,系統(tǒng)穩(wěn)定可靠。
  • 高擴(kuò)展:針對(duì)計(jì)算和存儲(chǔ)資源,可以實(shí)現(xiàn)水平擴(kuò)展。

接下來,針對(duì)于每個(gè)目標(biāo),本文分別從技術(shù)挑戰(zhàn)、方案選型、設(shè)計(jì)及解決方案等方面詳細(xì)介紹我們的實(shí)踐經(jīng)驗(yàn)。

3.1 擴(kuò)展性目標(biāo)

3.1.1 技術(shù)挑戰(zhàn)

在進(jìn)行水平擴(kuò)展改造時(shí),主要面臨了以下兩類挑戰(zhàn):

  • 規(guī)模性:在研發(fā)流程自動(dòng)化等背景下,美團(tuán)代碼托管平臺(tái)需要具備千萬級(jí)吞吐、低延遲及高可用的系統(tǒng)性能,以提高研發(fā)效率。
  • 兼容性:技術(shù)改造涉及的場景比較多,主要有兩方面的考量:(1)用戶低感知,新老系統(tǒng)保證現(xiàn)有通信方式及平臺(tái)使用方式不變;(2)兼顧過渡時(shí)期底層存儲(chǔ)介質(zhì)多樣性、運(yùn)維體系兼容等問題,并保障上下游系統(tǒng)的正常運(yùn)行。

3.1.2 方案選型

經(jīng)過對(duì)主流代碼托管平臺(tái)(GitHub、GitLab、Bitbucket等)的調(diào)研分析,我們發(fā)現(xiàn)各大平臺(tái)主要采用了以下兩種架構(gòu)解決擴(kuò)展性問題。

Code:美團(tuán)代碼托管平臺(tái)的演進(jìn)與實(shí)踐(美團(tuán)寫代碼)

通過上述對(duì)比可以發(fā)現(xiàn),如果直接接入共享存儲(chǔ),暫時(shí)無法滿足代碼托管平臺(tái)的穩(wěn)定性和性能要求(若Git機(jī)制進(jìn)行并行優(yōu)化,且使用更高讀寫性能的分布式存儲(chǔ)系統(tǒng),或許是一個(gè)不錯(cuò)的選擇)。在共享存儲(chǔ)優(yōu)化改造成本較高的前提下,我們最終采用了應(yīng)用層分片的分布式架構(gòu),它既滿足擴(kuò)展性的要求,也更加成熟和穩(wěn)定,并表現(xiàn)出不錯(cuò)的性能。

3.1.3 方案設(shè)計(jì)

我們通過代理模塊實(shí)現(xiàn)了請(qǐng)求分發(fā),通過路由模塊實(shí)現(xiàn)了倉庫分片,通過應(yīng)用模塊的無狀態(tài)改造實(shí)現(xiàn)了彈性伸縮,從而達(dá)成了水平擴(kuò)展的架構(gòu)目標(biāo)。下面將對(duì)這些模塊進(jìn)行詳細(xì)的介紹。

代理模塊

  • SSH Proxy:提供Git-SSH操作代理,提供Git-SSH請(qǐng)求代理,通過路由模塊獲取路由信息,到目標(biāo)機(jī)器執(zhí)行SSH操作。SSH Proxy組件基于go-crypto庫開發(fā),實(shí)現(xiàn)了公鑰識(shí)別用戶,流量控制,長連接超時(shí)處理,SSH轉(zhuǎn)gRPC等功能。后續(xù)計(jì)劃引入signature校驗(yàn),以應(yīng)對(duì)不同的使用場景。
  • HTTP Proxy:提供Git-HTTP/Web請(qǐng)求代理,通過路由模塊存儲(chǔ)的倉庫分片映射關(guān)系,決策倉庫路由節(jié)點(diǎn)。HTTP Proxy基于Go-Gin開發(fā),實(shí)現(xiàn)了請(qǐng)求甄別,流量控制,多層代理等功能。最初HTTP Proxy還被作為灰度遷移的核心組件,通過流量統(tǒng)一收口,支持請(qǐng)求分發(fā)到新老Code系統(tǒng),以確保請(qǐng)求和數(shù)據(jù)的平滑遷移。

Code:美團(tuán)代碼托管平臺(tái)的演進(jìn)與實(shí)踐(美團(tuán)寫代碼)

圖5 代理模塊

路由模塊

  • Shard:記錄倉庫與其所在分片之間的映射關(guān)系,是應(yīng)用層分片架構(gòu)的“中樞系統(tǒng)”。Shard服務(wù)除維護(hù)映射關(guān)系外,還是容災(zāi)模塊必不可少的“決策者”,通過獲取各個(gè)節(jié)點(diǎn)當(dāng)前訪問倉庫的最新版本,從而決定讀寫路由。由于Golang出色的高并發(fā)表現(xiàn),目前路由相關(guān)接口的平均響應(yīng)時(shí)間在15ms以內(nèi)。該模塊的主要特性如下: 建立倉庫和分片的映射關(guān)系,為了避免由于倉庫路徑更新造成文件夾拷貝/移動(dòng)等行為帶來一定的復(fù)雜性,這里采用了倉庫ID作為唯一標(biāo)識(shí)。利用Go Routine獲取節(jié)點(diǎn)的數(shù)據(jù)同步狀態(tài),并通過超時(shí)機(jī)制保障用戶非無限時(shí)等待。使用Key-Value Cache存儲(chǔ)倉庫和分片的映射,以降低映射關(guān)系的請(qǐng)求時(shí)延。

Code:美團(tuán)代碼托管平臺(tái)的演進(jìn)與實(shí)踐(美團(tuán)寫代碼)

圖6 路由模塊

應(yīng)用模塊

應(yīng)用模塊主要包括以下兩點(diǎn)功能:

  • 提供Git相關(guān)的業(yè)務(wù)邏輯接口處理代碼內(nèi)容信息、代碼評(píng)審等復(fù)雜性業(yè)務(wù)請(qǐng)求。
  • 監(jiān)聽代碼和分支變更消息,發(fā)送事件通知打通第三方業(yè)務(wù)系統(tǒng)和收集度量信息。

整體模塊架構(gòu)如下圖7所示:

Code:美團(tuán)代碼托管平臺(tái)的演進(jìn)與實(shí)踐(美團(tuán)寫代碼)

圖7 應(yīng)用模塊

3.1.4 解決思路

規(guī)模性解決思路

規(guī)?;闹饕繕?biāo)是:具備支撐千萬級(jí)請(qǐng)求的系統(tǒng)能力,并支持計(jì)算、存儲(chǔ)等資源的水平擴(kuò)展能力,其中路由均衡是必不可少的一環(huán)。

a. 路由均衡

Code系統(tǒng)對(duì)數(shù)據(jù)源可靠性要求較高,而對(duì)性能要求相對(duì)比較低,因而我們采用了嚴(yán)格仲裁路由模式,具體的邏輯配置如下:

  • 使用版本號(hào)判定哪個(gè)節(jié)點(diǎn)提供的代碼內(nèi)容最新:版本號(hào)越大,代表數(shù)據(jù)越新,當(dāng)版本最大時(shí)即為最新的數(shù)據(jù)。當(dāng)W R > N時(shí)總能讀到最新的數(shù)據(jù)(N:總節(jié)點(diǎn)個(gè)數(shù),W:判斷寫入成功需要的響應(yīng)節(jié)點(diǎn)數(shù),R:讀取數(shù)據(jù)時(shí)至少要成功讀取的個(gè)數(shù)),當(dāng)W越小時(shí),寫入的可用性就越高,R越小,讀取的可用性就越高。我們選擇了N=3,R=W=2的常規(guī)推薦配置,根據(jù)概率推算可達(dá)到99.999%的可用性水平。
  • 采用讀修復(fù)模式:當(dāng)讀取數(shù)據(jù)時(shí),若發(fā)現(xiàn)節(jié)點(diǎn)數(shù)據(jù)不一致,此時(shí)觸發(fā)數(shù)據(jù)同步邏輯,以修復(fù)落后節(jié)點(diǎn)的數(shù)據(jù)。

該功能內(nèi)置于路由模塊的Shard服務(wù),架構(gòu)如下圖8所示:

Code:美團(tuán)代碼托管平臺(tái)的演進(jìn)與實(shí)踐(美團(tuán)寫代碼)

圖8 路由邏輯示意圖

兼容性解決思路

兼容性目標(biāo)總結(jié)為一句話就是:業(yè)務(wù)使用無感知。因此,我們主要從以下三個(gè)方面考慮兼容性。

a. 與各系統(tǒng)交互方式及現(xiàn)有基礎(chǔ)設(shè)施兼容

Code系統(tǒng)的眾多下游系統(tǒng)(多套前端UI、業(yè)務(wù)研發(fā)流程工具平臺(tái)等)依賴系統(tǒng)提供的開放API、Hook機(jī)制等擴(kuò)展功能,為了減少系統(tǒng)升級(jí)對(duì)業(yè)務(wù)方造成影響,需要保證系統(tǒng)交互方式兼容。同時(shí)還要確保系統(tǒng)運(yùn)維監(jiān)控體系正常運(yùn)行,維持可監(jiān)測狀態(tài),我們主要做了以下四件事情:

  • 兼容核心功能:使用頻度高的功能平移到新系統(tǒng),而使用中低頻的功能,與業(yè)務(wù)溝通使用場景,再評(píng)估是否兼容。
  • 重新設(shè)計(jì)部分功能:提供更為合理的WebHook配置能力及嶄新的代碼評(píng)審功能。
  • 邊緣功能運(yùn)營下線:推進(jìn)廢棄和歷史遺留功能的下線,并提供合理的替代方案。
  • 打通運(yùn)維體系:保持現(xiàn)有監(jiān)控埋點(diǎn)及運(yùn)維接口接入方式,使系統(tǒng)處于可維護(hù)、可監(jiān)測的狀態(tài)。

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

Code系統(tǒng)倉庫眾多,需要有低成本的用戶自主切換方式保障數(shù)據(jù)逐步遷移,我們主要做了以下三件事情:

  • 可視化自動(dòng)切換:通過頁面一鍵遷移按鈕,低成本實(shí)現(xiàn)從非分布式版本切換到分布式版本(遷移進(jìn)度可感知,執(zhí)行過程中倉庫可讀不可寫,確保數(shù)據(jù)完整)。
  • Proxy屏蔽底層存儲(chǔ)介質(zhì)多樣性:通過Proxy保持單一的調(diào)用方式不變,可兼顧獲取非分布式版本和分布式版本的存儲(chǔ)數(shù)據(jù)。
  • 特殊數(shù)據(jù)共享存儲(chǔ):用戶和SSH Public Key等數(shù)據(jù)與倉庫數(shù)據(jù)沒有強(qiáng)制關(guān)聯(lián)關(guān)系,可實(shí)現(xiàn)數(shù)據(jù)共享。

c. 歷史數(shù)據(jù)平滑遷移

Code系統(tǒng)存在眾多的歷史代碼數(shù)據(jù)和業(yè)務(wù)數(shù)據(jù),如何有效、完整地將歷史數(shù)據(jù)平滑遷移到新的分布式系統(tǒng),變得尤為重要。為了達(dá)成業(yè)務(wù)使用無感知的目標(biāo),主要做了以下兩件事情:

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

3.2 可用性目標(biāo)

3.2.1 技術(shù)挑戰(zhàn)

在進(jìn)行可用性改造時(shí),我們主要面臨數(shù)據(jù)安全性層面的挑戰(zhàn)。代碼作為公司的重要資產(chǎn)之一,需達(dá)到兩方面的要求:

  1. 代碼單點(diǎn)丟失可數(shù)據(jù)恢復(fù)。
  2. 用戶視角可以讀到正確的代碼數(shù)據(jù)。

3.2.2 方案選型

目前,業(yè)界主要有以下三種主流的數(shù)據(jù)復(fù)制模式。

Code:美團(tuán)代碼托管平臺(tái)的演進(jìn)與實(shí)踐(美團(tuán)寫代碼)

圖9 數(shù)據(jù)保障方案對(duì)比

業(yè)界大多數(shù)分布式版本控制系統(tǒng)采用的是單主復(fù)制模式保障數(shù)據(jù)安全,隨著美團(tuán)內(nèi)部研發(fā)流程的逐步完善,對(duì)于創(chuàng)建注釋Tag、分支管理等需求逐步增加,讀寫比從最初的10:1縮短到現(xiàn)在的5:1,因此需要較高的寫入性能。

我們權(quán)衡了高吞吐量和數(shù)據(jù)強(qiáng)一致性的雙重目標(biāo),在單主復(fù)制架構(gòu)的基礎(chǔ)上,采用以倉庫維度單主復(fù)制為核心,節(jié)點(diǎn)多活為特性的復(fù)制模式(下文簡稱為多活模式),從而保證了數(shù)據(jù)安全和系統(tǒng)可用性。

3.2.3 方案設(shè)計(jì)

我們主要通過存儲(chǔ)模塊中,對(duì)Git的讀、寫及初始化三類不同的請(qǐng)求分別采取相對(duì)應(yīng)的數(shù)據(jù)處理機(jī)制,并結(jié)合多活復(fù)制模式,達(dá)成了高可用性的目標(biāo)。

存儲(chǔ)模塊

Git Server:主要存儲(chǔ)和管理Git倉庫數(shù)據(jù),提供Git相關(guān)的gRPC接口。該服務(wù)基于GitLab生態(tài)開源組件二次開發(fā),主要在數(shù)據(jù)同步機(jī)制、容災(zāi)模塊、部分底層命令上做了適配性優(yōu)化,共涉及以下4個(gè)邏輯模塊:

  1. Replication Manager:數(shù)據(jù)復(fù)制核心模塊,根據(jù)不同的請(qǐng)求(讀、寫或初始化)執(zhí)行不同的復(fù)制邏輯,從而保障數(shù)據(jù)一致性。
  2. Code Core:Git Server的核心服務(wù)模塊,主要提供了Git的gRPC API供上游模塊使用。
  3. Git Core:實(shí)現(xiàn)擴(kuò)展性和高可用性的重要組件,這里通過源碼的方式將GitLab生態(tài)開源組件引入到項(xiàng)目中,作為第三方Git API供項(xiàng)目使用。
  4. Git Command Factory:Git命令的中樞控制器,提供控制Git進(jìn)程數(shù)量、傳遞參數(shù)上下文,隔離執(zhí)行環(huán)境及格式化輸出數(shù)據(jù)等功能。

各個(gè)邏輯模塊間關(guān)聯(lián)如下圖10所示:

Code:美團(tuán)代碼托管平臺(tái)的演進(jìn)與實(shí)踐(美團(tuán)寫代碼)

圖10 存儲(chǔ)模塊

Git Cluster:又稱為分片,它由三個(gè)Git Server節(jié)點(diǎn)組成。三個(gè)節(jié)點(diǎn)間通過各自的Replication Manager模塊獲取到集群中其余節(jié)點(diǎn)的IP等信息,使用gRPC協(xié)議進(jìn)行數(shù)據(jù)復(fù)制備份,可以保證用戶視角的數(shù)據(jù)一致性,邏輯架構(gòu)如下圖11所示:

Code:美團(tuán)代碼托管平臺(tái)的演進(jìn)與實(shí)踐(美團(tuán)寫代碼)

圖11 Git Cluster

3.2.4 解決思路

數(shù)據(jù)安全性解決思路

Code系統(tǒng)要解決的問題中,數(shù)據(jù)安全問題尤為重要,是保證研發(fā)流程安全可靠的關(guān)鍵。在考慮數(shù)據(jù)安全性解決思路之前,先要明確數(shù)據(jù)一致性判別準(zhǔn)則,Code采用以下準(zhǔn)則評(píng)判兩個(gè)倉庫數(shù)據(jù)一致。

數(shù)據(jù)一致評(píng)判準(zhǔn)則:若倉庫所在兩個(gè)節(jié)點(diǎn)存儲(chǔ)的refs數(shù)據(jù)完全一致,則稱為這兩個(gè)節(jié)點(diǎn)上的倉庫數(shù)據(jù)一致。

目前系統(tǒng)數(shù)據(jù)安全機(jī)制主要有以下幾個(gè)特點(diǎn):

a.多活復(fù)制

目前Code系統(tǒng)每個(gè)分片包含3個(gè)節(jié)點(diǎn),即代碼數(shù)據(jù)保證三副本,即使出現(xiàn)1~2臺(tái)節(jié)點(diǎn)故障導(dǎo)致數(shù)據(jù)不可恢復(fù)的情況,也可通過其他節(jié)點(diǎn)進(jìn)行數(shù)據(jù)恢復(fù)。我們采用了多活復(fù)制模式,即任何一個(gè)滿足必要條件(當(dāng)前訪問倉庫在該節(jié)點(diǎn)的數(shù)據(jù)均重演至最新版本)的機(jī)器節(jié)點(diǎn)均可以進(jìn)行讀寫操作,與單主模式相比提高了寫操作的吞吐量,節(jié)省了主備切換的成本,使部署、節(jié)點(diǎn)替換及異?;謴?fù)更加簡單。多活復(fù)制模式約束有以下兩點(diǎn):

  1. “單寫”機(jī)制:在同一時(shí)刻,同一個(gè)倉庫的寫操作須在同一節(jié)點(diǎn)進(jìn)行。
  2. 數(shù)據(jù)安全鎖機(jī)制:若某倉庫底層Git的操作出現(xiàn)異常錯(cuò)誤,則在數(shù)據(jù)未恢復(fù)前,其后對(duì)該倉庫的所有操作均會(huì)在該節(jié)點(diǎn)進(jìn)行,會(huì)產(chǎn)生局部熱點(diǎn)。

多活復(fù)制主要由數(shù)據(jù)存儲(chǔ)和數(shù)據(jù)壓縮兩個(gè)部分組成。

01 數(shù)據(jù)存儲(chǔ)

  1. Git主要由objects和refs兩類數(shù)據(jù)組成。objects數(shù)據(jù)為不可變數(shù)據(jù),創(chuàng)建后為只讀模式,以文件的形式存儲(chǔ)于本地磁盤中;refs數(shù)據(jù)為可變數(shù)據(jù),可以進(jìn)行更新。兩類數(shù)據(jù)分別采用不同數(shù)據(jù)源進(jìn)行存儲(chǔ)。
  2. 用戶在訪問倉庫時(shí),如果某個(gè)objects沒有在任何一個(gè)分支的關(guān)聯(lián)鏈中,那么判定為不可達(dá),對(duì)于不可達(dá)的objects,無需維護(hù)其一致性。不可達(dá)object的示例如下:

Code:美團(tuán)代碼托管平臺(tái)的演進(jìn)與實(shí)踐(美團(tuán)寫代碼)

圖12 不可達(dá)object示例圖

02 數(shù)據(jù)壓縮

在Code系統(tǒng)中,需要記錄refs的變更日志以進(jìn)行數(shù)據(jù)回放,保證系統(tǒng)的數(shù)據(jù)一致性。由于每個(gè)倉庫的refs數(shù)據(jù)變換是比較頻繁的,會(huì)產(chǎn)生大量的日志,從而造成存儲(chǔ)壓力。因而我們采用了日志壓縮技術(shù),減少不必要的數(shù)據(jù)開銷,壓縮方式如下圖13所示:

Code:美團(tuán)代碼托管平臺(tái)的演進(jìn)與實(shí)踐(美團(tuán)寫代碼)

圖13 數(shù)據(jù)壓縮

例如上圖中的main分支,其初始狀態(tài)為main -> a,第4個(gè)log為main -> e,第5個(gè)log為main -> f,則這3個(gè)log可以壓縮為一個(gè)log,即main -> f并將其應(yīng)用于初始狀態(tài),與壓縮前回放觸發(fā)的結(jié)果是一致的,main都將指向值為f的commit。

03 相關(guān)優(yōu)化

在實(shí)踐過程中,我們發(fā)現(xiàn)采用純Git命令執(zhí)行數(shù)據(jù)復(fù)制操作無法有效控制資源分配,因而從通信方式、并發(fā)形式及復(fù)制粒度等方面做了優(yōu)化,從而提高了整體的數(shù)據(jù)復(fù)制效率。

b. 跨機(jī)房備份

Code系統(tǒng)每組分片的3個(gè)節(jié)點(diǎn)至少來自于兩個(gè)不同的機(jī)房(目前按照規(guī)范化部署,均改造為3機(jī)房),若其中一個(gè)機(jī)房發(fā)生故障,仍可提供服務(wù)。我們對(duì)該架構(gòu)做了針對(duì)性的容災(zāi)演練,通過演練驗(yàn)證了節(jié)點(diǎn)掉線對(duì)系統(tǒng)的影響較小,結(jié)合靈活的節(jié)點(diǎn)替換能力,可在30分鐘內(nèi)上線新的節(jié)點(diǎn),達(dá)到容災(zāi)平衡狀態(tài)。

Code:美團(tuán)代碼托管平臺(tái)的演進(jìn)與實(shí)踐(美團(tuán)寫代碼)

圖14 跨機(jī)房備份

c. 數(shù)據(jù)熱備

Code系統(tǒng)提供數(shù)據(jù)熱備機(jī)制,通過數(shù)據(jù)復(fù)制的方式,任何寫入的新數(shù)據(jù)會(huì)立即同步到其余副本節(jié)點(diǎn),基本“0”延遲,保證了用戶視角的強(qiáng)一致性。數(shù)據(jù)同步機(jī)制是熱備的關(guān)鍵,我們主要通過以下步驟實(shí)現(xiàn)。

01 寫操作階段

  1. 通過引入倉庫粒度的寫鎖,保證同一個(gè)倉庫同時(shí)只能在一個(gè)節(jié)點(diǎn)執(zhí)行寫入操作,然后通過Git Internal Hook機(jī)制觸發(fā)object數(shù)據(jù)的同步,并持久化記錄refs數(shù)據(jù)。
  2. 副本節(jié)點(diǎn)通過讀取持久化的refs數(shù)據(jù),重演操作,從而保持了refs數(shù)據(jù)與寫入節(jié)點(diǎn)一致。

Code:美團(tuán)代碼托管平臺(tái)的演進(jìn)與實(shí)踐(美團(tuán)寫代碼)

圖15 寫操作步驟

02 讀操作階段

  1. 如果當(dāng)前倉庫持有寫鎖,則直接路由至持有寫鎖的節(jié)點(diǎn)讀取數(shù)據(jù)。
  2. 如果未持有寫鎖,則用各個(gè)節(jié)點(diǎn)的版本和數(shù)據(jù)源存儲(chǔ)的版本數(shù)據(jù)進(jìn)行對(duì)比,將版本大于等于數(shù)據(jù)源存儲(chǔ)的最新版本的所有節(jié)點(diǎn)作為候選路由節(jié)點(diǎn),并采用負(fù)載均衡算法進(jìn)行路由;如果沒有符合條件的節(jié)點(diǎn)則需進(jìn)行同步補(bǔ)償,待補(bǔ)償成功后再進(jìn)行路由選擇 。

Code:美團(tuán)代碼托管平臺(tái)的演進(jìn)與實(shí)踐(美團(tuán)寫代碼)

圖16 讀操作步驟

03 相關(guān)優(yōu)化

在最初實(shí)現(xiàn)中,我們采用了無狀態(tài)同步,發(fā)現(xiàn)存在同步任務(wù)被多次執(zhí)行的情況,后續(xù)通過任務(wù)前置檢查等方式避免了不必要的數(shù)據(jù)同步任務(wù),最終減少了50%的同步任務(wù)。

d. 數(shù)據(jù)巡檢

數(shù)據(jù)巡檢是保證系統(tǒng)平穩(wěn)運(yùn)行,數(shù)據(jù)安全可靠必不可少的一個(gè)環(huán)節(jié),它可以及早地發(fā)現(xiàn)系統(tǒng)中潛在的隱患。巡檢服務(wù)作為Code系統(tǒng)的核心服務(wù),在降低數(shù)據(jù)風(fēng)險(xiǎn),提高系統(tǒng)服務(wù)的穩(wěn)定性方面起到了關(guān)鍵作用。對(duì)于巡檢服務(wù),我們主要從以下幾個(gè)方面進(jìn)行考慮:

  • 透明性:盡可能地避免對(duì)用戶的正常請(qǐng)求產(chǎn)生影響,減少不必要的干擾,對(duì)于系統(tǒng)訪問可以做到平穩(wěn)可控。
  • 可靠性:作為數(shù)據(jù)安全的重要服務(wù),它自身也要做到彈性伸縮,多點(diǎn)容災(zāi),具有高可用的特性。
  • 可維護(hù)性:對(duì)于數(shù)據(jù)巡檢發(fā)現(xiàn)的問題,能夠通過有效手段進(jìn)行處理。同時(shí)要提高巡檢服務(wù)的效率,隨著系統(tǒng)架構(gòu)的迭代出新、模塊升級(jí),巡檢服務(wù)要隨之更新,從而做到有效的保障。

綜合以上幾點(diǎn),我們采用了無狀態(tài)的服務(wù)架構(gòu),提供定點(diǎn)巡檢、全量巡檢、定時(shí)巡檢等模式保障數(shù)據(jù)安全。其中巡檢的數(shù)據(jù)主要分為以下兩類:

  • refs數(shù)據(jù):根據(jù)數(shù)據(jù)一致性評(píng)判準(zhǔn)則,refs數(shù)據(jù)是Git核心數(shù)據(jù),因而它的檢驗(yàn)是必不可少的。
  • 版本數(shù)據(jù):Code系統(tǒng)是基于版本進(jìn)行讀寫路由的,因而當(dāng)版本過大時(shí),可能會(huì)產(chǎn)生大量的數(shù)據(jù)同步,為了避免突增同步請(qǐng)求對(duì)系統(tǒng)造成一定的IO抖動(dòng),監(jiān)控版本差距是尤為必要的。

巡檢服務(wù)的整體架構(gòu)如下圖17所示:

Code:美團(tuán)代碼托管平臺(tái)的演進(jìn)與實(shí)踐(美團(tuán)寫代碼)

圖17 巡檢模塊

4. 總結(jié)

本文系統(tǒng)性地介紹了美團(tuán)在Code系統(tǒng)演進(jìn)過程中面臨的擴(kuò)展性和可用性兩大瓶頸,并分別針對(duì)上述兩類瓶頸和對(duì)應(yīng)的挑戰(zhàn),詳細(xì)闡述了解決方案和落地的實(shí)踐經(jīng)驗(yàn)。

基于上述的架構(gòu)改造實(shí)踐,目前美團(tuán)代碼托管平臺(tái)實(shí)現(xiàn)了倉庫容量水平擴(kuò)展、負(fù)載自主均衡等特性,穩(wěn)定支撐著研發(fā)流程規(guī)范的落地。我們未來會(huì)在支撐研發(fā)效率,保障研發(fā)安全方面繼續(xù)進(jìn)行探索和演進(jìn),爭取積累更多寶貴的實(shí)踐經(jīng)驗(yàn),后續(xù)再跟大家分享。

5. 未來展望

  • 自動(dòng)化運(yùn)維:目前系統(tǒng)的運(yùn)維機(jī)制自動(dòng)化程度低,我們希望未來可以自動(dòng)檢出系統(tǒng)異常并進(jìn)行恢復(fù),其中包括數(shù)據(jù)修復(fù),自動(dòng)擴(kuò)容及熱點(diǎn)遷移等功能。
  • 提供代碼領(lǐng)域最佳實(shí)踐:依托研發(fā)工具平臺(tái),持續(xù)推動(dòng)美團(tuán)研發(fā)流程規(guī)范的迭代更新,沉淀最佳實(shí)踐并提供有力的工具支撐。
  • 代碼安全:與信息安全團(tuán)隊(duì)緊密合作,提供更為完備的安全控制策略,包括代碼掃描、漏洞自動(dòng)修復(fù)、危險(xiǎn)行為預(yù)警等功能。

6. 本文作者及團(tuán)隊(duì)簡介

潘陶、費(fèi)翔、丹丹、毛強(qiáng)等,來自基礎(chǔ)研發(fā)平臺(tái)-研發(fā)質(zhì)量與效率團(tuán)隊(duì)。

美團(tuán)研發(fā)質(zhì)量與效率團(tuán)隊(duì),負(fù)責(zé)公司研發(fā)效能領(lǐng)域平臺(tái)和工具的建設(shè)(包括研發(fā)需求管理工具、CI/CD流水線、分布式代碼托管平臺(tái)、多語言構(gòu)建工具、發(fā)布平臺(tái)、測試環(huán)境管理平臺(tái)、全鏈路壓測平臺(tái)等),致力于不斷推進(jìn)優(yōu)秀的研發(fā)理念和工程實(shí)踐,建設(shè)一流的工程基礎(chǔ)設(shè)施。

| 本文系美團(tuán)技術(shù)團(tuán)隊(duì)出品,著作權(quán)歸屬美團(tuán)。歡迎出于分享和交流等非商業(yè)目的轉(zhuǎn)載或使用本文內(nèi)容,敬請(qǐng)注明“內(nèi)容轉(zhuǎn)載自美團(tuán)技術(shù)團(tuán)隊(duì)”。本文未經(jīng)許可,不得進(jìn)行商業(yè)性轉(zhuǎn)載或者使用。任何商用行為,請(qǐng)發(fā)送郵件至tech@meituan.com申請(qǐng)授權(quán)。

相關(guān)新聞

聯(lián)系我們
聯(lián)系我們
公眾號(hào)
公眾號(hào)
在線咨詢
分享本頁
返回頂部