閱讀432 返回首頁    go 阿裏雲 go 技術社區[雲棲]


如何打造支撐百萬用戶的分布式代碼托管平台

一、背景介紹

毋庸置疑,代碼是DevOps流程的起點,是所有研發流程的基礎;代碼托管為代碼“保駕護航”,確保代碼的安全性、可用性,同時提供圍繞代碼的一些基礎服務,如MRIssue等等。

阿裏巴巴集團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在單個機房內的架構。

Architecture.png

3.1 各個組件的功能主要是:

  1. Sharding-Proxy-Api用於記錄倉庫與目標機器之間的對應關係,可以看作切片的大腦
  2. Proxy負責對請求做統一處理,通過Sharding-Proxy-Api獲取信息,從而將請求路由到正確的目標機器
  3. Git Cluster由多組節點構成,每組節點內有三台機器,分別為master,mirror和backup。其中master主要負責處理寫(POST/PUT/DELETE)請求,mirror主要負責讀(GET)請求,backup作為該節點內的熱備機器

說明

  • master在處理完寫請求後,會同步更新此次變更到mirror和backup機器,以確保讀請求的正確性和熱備機器的數據準確
  • 之所以沒有采用雙master的模式,是不想造成在髒數據情況下,由於雙向同步而造成的相互覆蓋

3.2 保證方案可用

  • 如何確保切片信息準確
    Sharding-Proxy-Api基於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:

    由此產生的問題是:

    • ssh協議登陸服務器變慢
    • 用戶通過ssh協議fetch和push代碼時速度變慢

    為此,我們采用golang重寫了基於ssh協議的代碼數據傳輸功能,分別部署在proxy機器以及各組節點的GitLab服務器上。由此帶來的好處有:

    • 機器負載明顯降低
    • 消除上述bug
    • 在ssh服務發生問題的情況下,仍舊可以通過ssh登陸(使用原生)服務器,以及重啟sshd服務不會對服務器本身造成影響

    下圖是proxy機器采用新sshd服務後的cpu使用情況:
    cpu.png

  • 個別請求的優化和重寫

    對於一些請求量較大的請求,例如鑒權、通過ssh key獲取用戶信息等接口,我們目前是通過文本轉md5,加索引等方式進行性能優化,後期我們希望通過golang或java進行重寫。

3.4 確保數據安全

  • 一主多備

    如上麵提到的,目前阿裏集團GitLab的每組分片節點包含有三台機器,也就是相對應的倉庫數據一備三,即使某一台甚至兩台機器發生磁盤不可恢複的故障,我們仍舊有辦法找回數據。

  • 跨機房備份

    剛剛過去的三月份,我們完成了阿裏巴巴集團GitLab的跨機房切換演習,模擬機房故障時的應對策略。演習過程順利,在故障發生1min內接到報警,人工幹預(DNS切換)後5min內完成機房間流量切換。

    多機房容災的架構如下圖所示:
    disaster-recovery.png

    保證準實時的倉庫數據同步是機房切換的基礎,我們的思路按照實際需求,由容災機房機器主動發起數據同步流程,基本步驟是:

    • 利用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的架構,已經足夠支撐百萬規模的用戶體量,在滿足集團業務發展的前提下,會逐步通過阿裏雲code為更多雲上開發者提供服務,共同打造雲上的協同研發生態!**

最後更新:2017-04-07 21:25:10

  上一篇:go 使用Maven構建項目
  下一篇:go 自動駕駛汽車?法律:倫理