阿里巴巴代碼平臺架構的演進之路
代碼平臺的發展之路
相信很多做后端服務的同學在看到單機、讀寫分離、分片這些字眼一定不會覺得陌生。沒錯,代碼服務在發展的開始階段面臨的問題和其他web服務大體一致,所以使用的解決方案也大體一致。
單機服務
眾所周知,Git是一種分布式的版本控制軟件,每個人的本地都有一份完整的代碼版本數據。但為了解決多人協同開發和流程管控(評審、測試卡點等),需要一個集中式的遠端中央倉庫來完成這些功能,這就是單機服務的來源。
讀寫分離
隨著協同人數的增多,以及提交數量的變多,單機升配也無法解決調用量過高的問題。而通過統計我們發現,對于Git服務讀寫比例大概是20:1,為了保證主鏈路提交的正常,我們做了讀寫分離,來分散壓力。通過主備同步來完成數據的同步,并使用一寫多讀的架構來擴展讀服務的能力。
分片
但無論如何做讀寫分離,所有機器的規格始終是一樣的。雖然倉庫數量的增加、平臺用戶的增加,無論是存儲還是計算都會達到單機所能承載的極限。這個時候我們采用了分片的方式,即將不同的倉庫劃分到不同的片中,而每個片都是一個完整的讀寫分離架構。當一個請求到來時,服務會根據查詢庫特征信息,決定這個倉庫所在的分片,而后又根據接口的讀寫特性,將請求轉發到具體的機器上。有點類似于數據庫中的分庫分表。這樣以來,通過分片+讀寫分離的架構,我們理論上可以支持水平上的無限擴容。
問題及思考
那么分片+讀寫分離是否是解決代碼服務大規模、高并發問題的銀彈呢?在我們看來,答案是否定的。
伴隨著代碼服務體量的發展,我們解決的核心問題一直以來主要是兩個:
集中式的Git存儲服務,既是I/O密集型也是計算密集型(Git的壓縮算法);
文件數量眾多,單個倉庫的文件數量也可能是十萬甚至百萬級,對數據一致性的保證和運維可靠性的挑戰極大。
實際上,代碼平臺架構的發展,就是在這兩個問題之間找平衡,以在一定規模情況下保證整個平臺的穩定性。但一直沒有根本性地解決掉這兩個問題。然而隨著規模逐步上漲,上述的兩個核心問題引發的劣勢又逐步變得明顯起來。
代碼服務主備架構:有狀態服務帶來的問題
對高可用系統比較熟悉的同學,從名字上應該就看出些許端倪。主備架構的讀寫分離方案其天然引入的就是有狀態服務的問題。
整個系統的請求流轉,主要分兩次轉發:
通過統一的代理層,可將用戶的不同客戶端請求轉發到對應的系統上,如Git命令行客戶端的SSH協議和HTTP協議、頁面的訪問及API接口請求等。
然后對接的模塊會將用戶不同協議的請求轉換為內部的RPC調用,并通過統一的RPC代理模塊RPC Proxy和分片服務Shard Config將請求按分片和讀寫轉發到對應的服務上。
讀和寫操作如何處理
如果是一個寫操作(如:push,從頁面上對文件、分支等進行新增、刪除、修改等操作),請求則會落到倉庫對應分片的RW/WO服務器上,在RW/WO服務寫入完成以后,再通過Git協議的同步方式,同步到同分片的其他機器上,這樣一次寫操作就完成了。而對于讀操作,則是在倉庫對應分片中隨機找一個RO的機器進行轉發。
問題分析
當某個分片的主節點發生異常(服務crash或服務器宕機等),分片內的機器狀態會發生變化。原本的RW/WO狀態會置為不可用,Backup的機器會取代原來的RW/WO服務來承接寫操作的請求。
從系統的角度上分析,主備架構存在以下四個問題:
1、可用性:
由于讀寫操作是分離的,所以在寫服務器failover期間,服務的寫功能是無法使用的;
對于單片而言,寫操作是單點的,一臺服務波動則整個分片都波動。
2、性能:
主備機器在同步上需要額外的時間開銷。對于松散文件、文件壓縮的Git倉庫,這個耗時比單文件拷貝耗時更久。
3、安全:
用戶側的短時間內的瞬時操作,對于節點同步來說可能是并發的,無法保證同步中的事務順序。
4、成本:
同分片寫,主備機器要求規格完全一致。但由于接收的請求不同,存在嚴重的資源消耗不均;
由于同步的小文件多,對延時敏感,跨機房異步同步,機器規格一比一復制。
這四個系統上缺陷帶來的問題,在一定使用規模和服務穩定性要求下是可以容忍的。但隨著商業化的深入和用戶規模的增長,這些問題的解決變得迫在眉睫。接下來我將和大家分享在過去的一年中,我們團隊對這些架構上問題的思考和解決思路。
代碼服務多副本架構:消滅有狀態的存儲服務
在上一個小節中,我們已經比較清晰地認識到架構上面臨的四個問題主要是有狀態服務帶來的。那么在新架構的設計中,我們的目標只有一個——消滅有狀態的服務。目標有了,如何去實現?我們首先對業內幾個流行的分布式系統做了深入的了解和學習,比如ETCD、Paxos協議的學習等。同時我們也學習了代碼服務的老大哥——Github開源的寥寥文章,但Github認為分布式架構是他們的核心競爭力,所以可參考的文章較少,但從這些文章中我們依然深受啟發。首先對于任何架構升級,要能做到“開著飛機換引擎”,讓架構軟著陸是架構升級的保底要求。因此在grpc的代理層之上沒有任何的改動。從內部的RPC調用以下則是我們新架構實施的地方。
和前一節的不同,在新的底層設計中:
我們希望設計一個GPRC D-PROXY的模塊,能將gRPC的請求做到寫時復制,從而達到多寫的第一步;
其次在Proxy Config的模塊中來存放倉庫、副本、機器等元數據;
再次通過一個分布式的鎖(D-Lock)來完成對倉庫級別的鎖控制;
最后我們希望有一個快速的算法能計算倉庫的checksum,快速識別倉庫的副本是否一致。
通過這些模塊的組合完成倉庫多副本的并發寫入、隨機讀取,我們就可以去除底層存儲節點的狀態,從而能將倉庫的副本離散到不同機房中。此外得益于機器與副本的解耦,每個服務器都可以有獨立的配置,這也為后續的異構存儲打下了基礎。
代碼服務多副本架構的實現
有了基礎的設計,通過MVP的實現,我們驗證了這個架構的可行性。并通過一年多的時間進行開發和壓測,最終將我們的多副本架構成功上線并逐步開始提供服務。在實現過程中,我們為這個系統起了一個頗有意義的名字——伽利略,因為在我們的多副本架構中,最小的副本數是3,而伽利略在發明了天文望遠鏡觀察到火星的衛星恰好就是3個。我們希望秉承這個不停探索的精神,所以起了這個有意義的名字。在具體的設計中,我們通過對gRPC proxy模塊的改寫,讓來自用戶的一次寫操作完成寫復制,并通過對git的改寫以支持分段提交。我們基于git的數據特性,編寫了checksum的模塊,可以對git倉庫進行快速的全量和增量一致性計算。
伽利略架構在系統架構層面解決了之前主備架構帶來的問題:
1、可用性提升:多寫和隨機讀讓底層的git存儲服務不存在寫單點和failover的切換問題;
2、寫性能提升:副本并發多寫,讓底層的副本間不存在主備復制的時間開銷,性能比肩單盤;
3、安全:寫操作的分段提交和鎖的控制,在解決分布式系統寫安全的基礎上也控制了用戶寫操作的事務性;
4、成本大幅度降低:
每個副本都會承擔讀寫操作,水位平均
副本與機器解耦,釋放機器的規格限制,可以根據倉庫的訪問熱度采用不同涉及機型和存儲介質
說了這么多,當用戶執行push后,在伽利略中會發生什么呢?我們用一個動畫來簡單演示下:
用戶3和用戶4是兩個著急下班的同學,他們本地master分支分別是提交3和提交4;
底部灰色的圓圈代表的是服務端一個倉庫三個副本的裝填,可以看到其中兩個master分支指向的是提交2,而其中有一個可能因為網絡或者其他原因導致其master的指向為1;
當用戶3和用戶4前后提交了代碼,由于用戶3更快達到服務,所以會率先啟動寫的流程;
在一個寫的流程開始,首先會對當前倉庫副本的一致性進行檢查,系統很容易就發現落后的副本1與其他兩個副本不一致,因此會將其標記為unhealthy。對于unhealthy的副本,是不會參與伽利略寫或讀的任何操作;
當用戶4的請求也被受理后,也會觸發一致性檢查,但是由于副本1已經被標記為unhealthy,所以對于用戶4的流程,這個副本就“不可見”了;
在用戶3的進程檢查后,發現多數副本是一致,則認為是可以寫入的,便會將非引用數據傳輸到副本中;同理用戶4的進程也是一樣,且因為非引用數據不會變更分支信息,所以不需要加鎖,可以同時操作;
當用戶3的進程收到非引用的數據傳輸成功后,便要開始進行引用的更新,這時第一步先去搶占這個倉庫的鎖。很幸運鎖是空閑的,用戶3的進程加鎖成功,開始改寫副本的引用指向;
當用戶4的進程也完成了非引用數據傳輸后也開始進行引用的更新。同樣,再更新操作前要去搶鎖,但發現鎖已經被占用,則用戶4的進程進入等待階段(如果等待超時,則直接告訴用戶4提交失敗);
當用戶3的進程改寫引用成功后,會釋放掉倉庫鎖。此時服務端的倉庫的master分支已經被修改為指向3。用戶3可以開心下班了;
當鎖被釋放后,用戶4的進程快速搶占,并嘗試修改引用指向,但發現更改的目標引用master已經和之前不同(之前是2,現在是被用戶3更改的3),此時用戶4的進程會失敗并釋放掉鎖。在用戶4的本地會收到類似“遠端分支已經被更新,請拉取最新提交后再推送”的類似提示。
以上就是多個用戶同時提交時,伽利略系統內部發生的事情。眼力好的同學可能會問,那在系統中被判定unhealthy的副本會怎么辦呢?這個就是剛才在架構實現中提到的運維系統會做的事務補償了,運維系統在收到unhealthy上報和定時掃描后都會觸發對unhealthy副本的修復,修復后的副本在確認一致后會置為healthy并繼續提供服務,當然這個過程在判斷修復是否一致也是加鎖操作的。
代碼托管運維管理平臺
除了上小節提到的副本修復,在系統運行中,還需要很多的運維操作。以往這些都是由運維同學操作的。Git服務涉及數據和同步,比單純的業務系統要運維復雜度要高,對應運維風險也比較高。在過去的一年中我們結合以往的運維經驗,以及新架構的需要,以工具化、自動化、可視化為目標,為伽利略系統打造了對應的運維管理平臺:
通過這個平臺我們大大提升了運維的質量、也充分釋放了運維人員的精力。