RDC如何打造支撑百万用户的分布式代码托管平台
一、背景介紹
毋庸置疑,代碼是DevOps流程的起點,是所有研發流程的基礎;代碼托管為代碼“保駕護航”,確保代碼的安全性、可用性,同時提供圍繞代碼的一些基礎服務,如MR、Issues等等。
阿里巴巴集團GitLab是基于GitLab社區版8.3版本開發,目前支撐全集團數萬規模的研發團隊,累計創建數十萬項目,日請求量千萬級別,存儲TB級別,早已超過了GitLab社區版承諾的單機上限能力,且增長速度迅猛。
面對這種情況,順理成章的做法就是——擴容。然而非常不幸,GitLab的設計沒有遵守Heroku推崇的“The Twelve-Factor App”的第四條:“把后端服務當作附加資源”(即對應用程序而言,不管是數據庫、消息隊列還是緩存等,都應該是附加資源,通過一個url或是其他存儲在配置中的服務定位來獲取數據;部署應可以按需加載或卸載資源),具體體現是:
- Git倉庫數據存儲于服務器本地的文件系統之上
- GitLab所依賴的三個重要組件:libgit2、git、grit也都是直接操作在文件系統上GitLab
所以GitLab社區版是基于“單機”模式設計,當存儲容量和單機負載出現瓶頸時,難以擴容!
二、面對挑戰
2015年初,阿里巴巴集團GitLab的單機負載開始呈現居高不下的情況,當時的應對方案是同步所有倉庫信息到多臺機器,將請求合理分配到幾臺機器上面從而降低單機的負載。然而這個方法治標不治本:
- 系統運行過程中的數據同步消耗資源和時間,不可能無限制擴充機器
- 這種方案暫時緩解了單機負載的問題,但對于單機存儲上限的問題束手無策
2015年中,團隊正式啟動了第一次改造嘗試,當時的思路是去掉對本地文件系統的依賴,使用網絡共享存儲,具體思考和方案可以參見RailsConf 2016 - 我們如何為三萬人的公司橫向伸縮 GitLab。
然而由于本地緩存等問題的限制,網絡共享存儲的方案在性能上出現較明顯性能問題,且大都為基于C/C++的底層改動,改造成本出現不收斂情況。而當時集團GitLab服務器在高峰期CPU屢屢突破95%甚至更高的警戒值,而高負載也導致了錯誤請求占比居高不下,無論是對上游應用的穩定性還是對用戶體驗都提出了嚴峻挑戰。
2016年8月起新一輪改造開始。
三、改造方案
既然共享存儲的方案暫時行不通(后續如果網絡存儲的讀寫性能有質的提升,未嘗不是好的方式),首先明確了唯有分布式或者切片才能解決問題的基本思路。
我們注意到,GitLab一個倉庫的特征性名稱是"namespace_path/repo_path",而且幾乎每個請求的URL中都包含著個部分(或者包含ID信息)。那么我們可以通過這個名稱作分片的依據,將不同名稱的倉庫路由到不同的機器上面,同時將對于該倉庫的相關請求也路由到對應機器上,這樣服務就可以做到水平擴展。
下面通過一幅圖介紹一下目前集團GitLab在單個機房內的架構。
3.1 各個組件的功能主要是:
說明
- master在處理完寫請求后,會同步更新此次變更到mirror和backup機器,以確保讀請求的正確性和熱備機器的數據準確
- 之所以沒有采用雙master的模式,是不想造成在臟數據情況下,由于雙向同步而造成的相互覆蓋
3.2 保證方案可用
-
如何確保切片信息準確
Sharding-Proxy-Api基于[martini](https://github.com/go-martini/martini)架構開發,實時接收來自**GitLab**的通知以動態更新倉庫信息,確保在namespace或project增刪改,以及namespace_path變更、倉庫transfer等情況下數據的準確性。備注:這樣的場景下,等于每次請求多了一次甚至多次與Sharding-Proxy-Api的交互,最初我們曾擔心會影響性能。事實上,由于邏輯較為簡單以及golang在高并發下的出色表現,目前Sharding-Proxy-Api的rt大約在5ms以內。 -
如何做到切片合理性
海量數據的情況下,完全可以根據namespace_path的首字母等作為切片依據進行哈希,然而由于某些名稱的特殊性,導致存在熱點庫的情況(即某些namespace存儲量巨大或者相應請求量巨大),為此我們為存儲量和請求量分配相應的權重,根據加權后的結果進行了分片。目前來看,三個節點在負載和存儲資源占用等方面都比較均衡。 -
如何處理需要跨切片的請求
GitLab除了對單namespace及project的操作外,還有很多跨namespace及project的操作,比如transfer project,fork project以及跨project的merge request等,而我們無法保證這些操作所需的namespace或project信息都存儲在同一臺機器上。 為此,我們修改了這些場景下的GitLab代碼,當請求落到一臺機器同時需要另一臺機器上的某個namespace或project信息時,采用ssh或者http請求的方式來獲取這些信息。> 最終的目標是采用rpc調用的方式來滿足這些場景,目前已經在逐步開展。
3.3 提升性能
- ssh協議的替換
目前阿里巴巴集團GitLab提供ssh協議和http協議兩種方式,供用戶對代碼庫進行fetch和push操作。其中,原生的ssh協議是基于操作系統的sshd服務實現,在GitLab高并發ssh請求的場景下,出現了諸如這樣的bug:
- logind randomly loses its dbus connection
- Leak of scope units slowing down "systemctl list-unit-files" and delaying logins
由此產生的問題是:
- ssh協議登陸服務器變慢
- 用戶通過ssh協議fetch和push代碼時速度變慢
為此,我們采用golang重寫了基于ssh協議的代碼數據傳輸功能,分別部署在proxy機器以及各組節點的GitLab服務器上。由此帶來的好處有:
- 機器負載明顯降低
- 消除上述bug
- 在ssh服務發生問題的情況下,仍舊可以通過ssh登陸(使用原生)服務器,以及重啟sshd服務不會對服務器本身造成影響
下圖是proxy機器采用新sshd服務后的cpu使用情況:
- 個別請求的優化和重寫
對于一些請求量較大的請求,例如鑒權、通過ssh key獲取用戶信息等接口,我們目前是通過文本轉md5,加索引等方式進行性能優化,后期我們希望通過golang或java進行重寫。
3.4 確保數據安全
- 一主多備
如上面提到的,目前阿里集團GitLab的每組分片節點包含有三臺機器,也就是相對應的倉庫數據一備三,即使某一臺甚至兩臺機器發生磁盤不可恢復的故障,我們仍舊有辦法找回數據。
-
跨機房備份
剛剛過去的三月份,我們完成了阿里巴巴集團GitLab的跨機房切換演習,模擬機房故障時的應對策略。演習過程順利,在故障發生1min內接到報警,人工干預(DNS切換)后5min內完成機房間流量切換。
多機房容災的架構如下圖所示:
保證準實時的倉庫數據同步是機房切換的基礎,我們的思路按照實際需求,由容災機房機器主動發起數據同步流程,基本步驟是:
- 利用GitLab的system hook,在所有變更倉庫信息的情景下發出消息(包含事件類型及時間包含數據等)
- 同機房內部署有hook接收服務,在接收到hook請求后對數據進行格式化處理,并向阿里云MNS(Message Notify Service)的相關主題發送消息
- 容災機房內,部署有消息消費服務,會訂閱相關的MNS主題以實時獲取online機房發送到主題內的消息,獲取消息后調用部署在容災機房GitLab節點機上的rpc服務,主動發起并實現數據同步的邏輯
hook接收、消息消費以及GitLab節點機上的rpc服務,均由golang實現。其中rpc服務基于grpc-go,采用protobuf來序列化數據。
通過一段時間的運行和演習,已經確定了方案切實可行,在數據校驗相關監控的配合下,容災機房可以準實時同步到online機房的數據,且確保99.9%至99.99%的數據一致性。
3.5 如何提升系統的可用性
-
日志巡檢
面對集團GitLab每天產生的大量日志,我們使用阿里自研的監控工具進行日志監控,對系統產生的5xx請求進行采集和報警,然后定期去排查其中比較集中的錯誤。經過一段時間的排查和處理,5xx錯誤日志大致可以分為:- 分布式改造帶來的跨分片操作的bug
- GitLab本身的bug
- 高并發情況下帶來的系統偶發性故障
- 數據庫相關的錯誤等
由于用戶量大,場景多,阿里巴巴集團GitLab的使用場景基本覆蓋GitLab的所有功能,因此也可以暴露出一些GitLab自有的bug。如Fix bug when system hook for create deploy key(從此,咱也是給GitLab供獻過代碼的人了)。
-
服務器監控
無論系統多少健壯,完備的監控是確保系統平穩運行的基礎,既可以防患于未然,也可以在問題出現時及早發現,并盡可能減小對用戶的影響。目前阿里巴巴集團GitLab的監控主要有:
- 機器cpu,內存、負載等基本指標的監控
- ssh、ping等網絡檢測信息(用于判斷是否宕機,后將詳述)
- 服務器各個端口的健康檢查(80,90xx,70xx等等)
- 異步消息隊列長度的監控
- 數據庫連接的檢查
- 錯誤請求的監控
- Sharding-Proxy-Api與GitLab的數據一致性校驗
很自豪的一點是,我們經常可以根據報警信息,先于用戶發現問題。
-
單機故障的自動切換
雖然監控足夠完備,但誰也不能保證服務器永遠不宕機,因此我們在同一組節點中保有一臺backup機器以應對服務器故障。會有專門的client定期通過API輪詢監控平臺提供的機器監控信息,當確認機器宕機時(ssh和ping同時不通,且持續時間超過2min)自動觸發機器角色的互換,包括master與backup互換,mirror與backup互換等。通過演習,我們已經具備了單機故障時5min內全自動實現機器切換的能力。 -
機房故障時的切換
即前述的跨機房切換。
3.6 單元化部署
單元化架構是從并行計算領域發展而來。在分布式服務設計領域,一個單元(Cell)就是滿足某個分區所有業務操作的自包含的安裝。而一個分區(Shard),則是整體數據集的一個子集,如果你用尾號來劃分用戶,那同樣尾號的那部分用戶就可以認為是一個分區。單元化就是將一個服務設計改造讓其符合單元特征的過程。
為了實現單元化的目標,我們在最初設計時就往這方面考慮。比如跨機房備份中,消息消費應用需要調用Sharding-Proxy-Api獲取rpc服務的地址時,盡可能做到數據在單機房內閉環。這樣在滿足單元化要求的同時,也可以在機房故障時,盡量不影響已進入隊列的消息在消費時出現數據斷流。
現在阿里巴巴集團GitLab在架構上已經基本具備了單元化部署的能力,這樣的情況下,無論是后續與阿里云合作對外提供服務,還是當收購海外公司需要單獨搭建新服務時,都不會遇到問題。
四、未來的改進
4.1 偶發的cache大量釋放
由于GitLab有大量的IO操作,使得系統占用cache的數值巨大,也正是因為cache,系統的性能得到保證。然而成也cache敗也cache,為了確保系統不會發生OOM,我們設定了vm.min_free_kbytes,當cache占用過多且需要繼續申請大片內存時,會觸發cache的釋放,勢必會影響釋放瞬間請求處理能力(通過日志分析得到,此瞬間的處理能力僅為cache存在時的1/2左右),直接后果是該瞬間的請求堵塞,甚至出現部分502。
為此我們咨詢了系統部的同學,根據他們的建議修改了部分內核參數(目前仍沒有根治),后續會嘗試升級內核的方式,也希望遇到過類似問題或對這方面問題有研究的同學,把你的秘籍傳給我們。
4.2 自動化運維
目前集團GitLab的發布主要靠手,在分布式架構下,機器勢必越來越多,全自動化的發布、擴容機制,是我們需要完善的地方。
4.3 rpc方案的最終落地
如前所述,只有最終實現了全局的rpc替換,才能將web服務所消耗的資源與Git本身消耗的資源進行分離,阿里巴巴集團GitLab的分布式改造才能算最終結束。
五、結語
監控及日志數據對比顯示,過去一年中阿里巴巴集團GitLab請求量增長4倍,項目數增長130%,用戶數增長56%,在這樣的增速下,系統調用的正確率卻從99.5%提升到了99.99%以上,這些數字印證了我們方案的可行性和可用性。
接下來的時間里,小伙伴們會為繼續代碼服務的創新而努力。“高擴展、分布式、響應式、大文件、按需下載、流控、安全、數據化、單元化”,有些我們做到了,有些是接下來努力的方向。
很自豪,今天的我們終于可以有底氣地承諾:現在阿里巴巴集團GitLab的架構,已經足夠支撐百萬規模的用戶體量,在滿足集團業務發展的前提下,會逐步會通過剛剛上線的研發協同RDC為更多云上開發者提供服務,共同打造云上的協同研發生態!
總結
以上是生活随笔為你收集整理的RDC如何打造支撑百万用户的分布式代码托管平台的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: POJ NOI MATH-7828 最大
- 下一篇: 解压bzi2文件出错,分析和处理