Golang 在阿里集團調度&集羣管理系統 Sigma 中的實踐


阿里巴巴 9 年雙 11 經歷下來,交易額增長了 280 倍、交易峯值增長 800 多倍、系統數呈現爆發式增長。系統在支撐雙 11 過程中的複雜度和支撐難度以指數級形式上升。雙 11 峯值的本質是用有限的成本最大化提升用戶體驗和集羣吞吐能力,用合理的代價解決峯值。始於 2011 年建設的調度&集羣管理系統 Sigma 就是爲了支撐如此龐大的體系而建立的。


本文作者李雨前(花名鷹緣) ,阿里巴巴技術專家  2015 年開始參入調度系統的建設,參與和推動調度系統的多個版本的演進以及支持雙11大促資源的分配和管理。


如果您是資深架構師、Docker/Kubernetes/CloudNative 技術專家,對阿里系統軟件事業部感興趣,歡迎投遞簡歷到 [email protected]

Sigma 有 Alikenel、SigmaSlave、SigmaMaster 三層大腦聯動協作,Alikenel 部署在每一臺物理機上,對內核進行增強,在資源分配、時間片分配上進行靈活的按優先級和策略調整,對任務的時延,任務時間片的搶佔、不合理搶佔的驅逐都能通過上層的規則配置自行決策。SigmaSlave 可以在本機進行容器 CPU 分配、應急場景處理等。通過本機 Slave 對時延敏感任務的干擾快速做出決策和響應,避免因全局決策處理時間長帶來的業務損失。SigmaMaster 是一個最強的中心大腦,可以統攬全局,爲大量物理機的容器部署進行資源調度分配和算法優化決策。

整個架構是面向終態的設計理念,收到請求後把數據存儲到持久化存儲層,調度器識別調度需求分配資源位置,Slave識別狀態變化推進本地分配部署。系統整體的協調性和最終一致性非常好。我們在 2011 年開始做調度系統,2016 年用 Go 語言重寫,2017 年兼容了 kubernetes API,希望結合生態的力量,共同建設和發展。

Sigma 總覽

Sigma 的業務有兩塊,一塊是對內,一塊是對外,對內是所有的 BU 都接入了Sigma 的系統,我們的規模會有百萬級別。Sigma 的內部有一個 logo,很容易理解,就是數學求和,Sigma 是很大的生態系統,需要很多人和系統配合完成。

這個業務分了幾個層次,上層業務偏運維,下層業務偏系統,從接入層,到中心的 Master,到Slave,到最底會有一個 PouchContainer(阿里開源的富容器引擎)。

這是

Sigma 的架構

可以看出來,顏色有三塊,一塊是 Sigma 的,一個是 0 層,還有一塊是關於 Fuxi 的,通俗的理解就是 Sigma 管在線,然後是 Fuxi 管離線的,中間是一個協調層,這樣理解起來就跟 Mesos 比較接近一點。這個架構肯定不是最優的,但是它存在有它客觀的原因。

我會從 Sigma 這邊抽象的四個案例。我會給大家展示是什麼的同時,講背後我們的故事。

案例1

首先看一下 APIServer

我們平常寫代碼經常會接觸到這些事情,在業務領域、調度領域稍稍不同,背後要做發佈、擴容、銷燬、啓停、升級,還有云化,特別是雙十一到阿里雲買服務器,所以會有混合雲需求。我們規模很大,就會要求所有簡單的事情,怎麼樣讓它在規模化的場景下面依然能夠工作。我們調度系統跟運維有關,核心的內容就是怎麼樣做到運維友好。還有就是我們做在線的容器服務,肯定要做到高可用,還有一致性。

解決思路

1.數據一致性

數據的一致性上面我們是用 etcd/redis,我們會用一個實時+全量的方式做到數據的一致性

2.狀態的一致性

想做到很好是很難的,但是我們把狀態的一致性轉爲存儲一致性來做,就會降低處理問題的難度。

3.簡單的

我們沒有追求技術看起來非常完美的方案,先把業務推起來能夠用就好了。

4.高可用-無狀態

我們要做到前面說的高可用,有幾種方案,一個是多 Master 結構,還有無狀態,還有就是快速的 failover,我們希望做到無狀態。

5.降級-搶佔

規模大了以後就會有一個問題,很多人都要資源,這個時候肯定會有一些稀缺的資源,系統要支持降級搶佔。

6.內外兼容:一個團隊兩塊牌子

要做到上雲,所有的思考都要考慮到對內對外是一班人馬,一個團隊會有兩個牌子,這是我們整個出發點的思路。

之後我們選擇由 APIServer 把前面的發佈、擴容等都放在 task,丟到 Redis 裏,底層的 Worker 消費一些任務,做到無狀態,得出來的結論就是架構整體的設計大於語言的選擇。

數據一致性裏面,如果能做到了實時,數據應該就是一致性的,爲什麼還要加全量來彌補這個過程?背後一個重要的原因就是物理機的硬件或者是軟件會經常出故障,系統多了之後,很難保證故障的事件跟整個鏈路 100 閉環起來,就導致總有一些地方的數據不知道爲什麼不一致,有很多硬件、軟件的故障導致實踐的層面做不到一致性,就只好加一個全量彌補。還有另外一種方案不要全量,我們定時同步,但是有一個問題,時間窗口怎麼定,定幾分鐘。同步的一瞬間,數據會有一些比對,這個時候加鎖,成本維護會多一點。

狀態一致性問題轉爲存儲一致性的問題最好的就是轉爲存儲一致性,我們爲什麼說要面向 etcd,在很大的系統裏面有很多的設備系統,沒有辦法面向事務去做,最好是分佈式的存儲把這些問題兜掉。

爲什麼說簡單夠用,後面會有數據給大家看一下,要承載的量很大,沒有辦法做到 95%以上才上線,大家等不及,可能做到 85%就要上線了。

還有就是降級,前面我提到了阿里這麼多的 BU,每一個 BU 都提了很多的預算,去採購,這就浪費很多資源,這中間有一個共享的 buffer,肯定會有搶佔,這個在開源裏面也會有一些策略。

內外兼容,如果是創業公司會有體會,一部分東西是基於內部的服務器,還有一些是購買上雲的服務,這個時候管理起來,不可能上雲的是一套管理方式,自己內部又是一套方式,至少在開發理解上是一模一樣的,在設計和架構的時候要屏蔽很多的差異。基於這樣的思考,我們覺得這種方式相對來說會比較好一點,不然嘗試換其他的方式,問題解決可能會稍微麻煩一點。

前面講了設計的架構,接下來講的案例是一個真實的場景。

我們在阿里要做一次發佈,比如說一開始要拉鏡像,就要關閉告警,可能有一些應用要下線了,還要停止應用。然後開始容器升級,還要做業務邏輯的檢查,檢查完之後才把告警打開。不管是上雲還是內部,這套流程肯定是公共的,具體對接的時候又不一樣,內部和雲上不一樣,雲上提供一個 SDK,雲上的接口的異步性的流轉和內部也不一樣,所以要抽出來。

內部和阿里雲上面都是這樣的事情,阿里雲比較特別的是什麼呢?在 before 會做一些處理,我們核心的架構是很固定的,在阿里主流的語言是 JAVA,我們討論架構的時候,沒有侷限於上來就用 JAVA,Golang,或者別的,我們把架構想清楚,之後才選擇用什麼樣的開發語言。

這是我們數據的表現:

這個表現是任務量,數字隱去了,發佈的量非常大,平常的量比較小,可以看到它有週期性的規則,因爲阿里的體量有發佈窗口期,那一天整個的發佈量大了,會有一個全局的管控。

案例2

調度裏面核心的模塊之一是 Scheduler,在很多物理機上面選最佳的物理機,把容器布上去,常規的做法就是要有過濾鏈,過濾完之後會有一個權重的鏈,最後拿到我想要的機器上去創建這個容器。因爲規模大,一個請求在一個機房裏面去掃,這一個機房可能就是萬級別的節點,肯定要考慮到性能的優化,所以篩選的規模比較大一點。它是一個鏈式的,首先要選擇哪些服務器作爲點,在這一塊,我們的想法就是要並發起來,怎麼樣從工程上面做一個實踐。綠色的框是順序過程,紅色的框是併發的階段,或者是關鍵資源的鎖的過程。每一個併發的粒度看成是一個物理機,相當於每一個物理機去篩選這個是否要,這是一種模式。

另外一種模式是說在整個機器的頂層加一個 glock,是順序還是並行都不用管,這個鎖加在上面,前面的鎖是加在下面,兩者看起來好像沒有太大的區別,測試下來發現性能不一樣。有幾個因素,一個是下面要做過濾鏈或者做 weight,看開銷;還有每一次篩選的規模,比如一天下來有十萬的請求,每個請求背景物理機是好幾萬的規模還是幾百的規模,最後對整體的性能要求是不一樣的。

粗粒度併發的性能比較好,我們選擇了第二種場景。深入的思考的時候,我們發現第一種場景行不通,原因是有些全局的資源沒有在一個協程裏面更改,另外一個協程不能立即可見。

案例3

Golang 最近幾年非常的火爆,但是在阿里大的氛圍裏面更強調的是 JAVA 語言,我們引入 Golang 不可能一上來就大規模,需要有一個成功的案例,或者是小規模實踐的過程。在這種環境下面,我們想讓 Golang 有一席之地,首選的方式就是如何做到快速的打磨,跑得很慢的時候,語言構架系統可能就會被淘汰掉了。這是我們上一個版本,有大概五個鏈路,有一個架子,每一條鏈路是做的過程當中,一步一步完善出來。今天我們把鏈路搬出來跟 K8S 做比較的時候,很多地方都是相通的,但是具體框架的編碼實踐上面來說是有很大的差異。我們早些時候摸索的這些東西,可能就是業務驅動或者概念驅動,沒有真正做到工程或是回饋社區的驅動。未來我們換了一種方式,我們可能是以工程的方式驅動,就是回饋社區。

案例4

我講一下怎麼解讀這個圖。分幾層,上面這一層更強調的怎麼樣編排任務,中間的這一個層是講整個容器的;縱向有三個部分,最左邊是講怎麼樣調度的,中間是一個容器的引擎,再後面是容器的運行時。

在 PouchContainer 裏面,官方寫了很多的 Features,這些 Features 是源於阿里真實的實踐。爲什麼叫富容器,容器經典的代表大家可能想到 Docker,那 Docker 之前呢?比如阿里的虛擬機比較流行,從虛擬機過渡到容器,這麼大的規模,需要適應運維的習慣,要有這樣的感知、理念,這個時候容器的技術肯定要做很厚。然後再編排,現在我們瞭解的有 K8S,大家覺得很完美,但是那在 K8S 之前是什麼,阿里的體量並不是 2015 年那一刻才長大的,2015 年之前就很大了。

我們有一個強隔離,爲什麼說強隔離?我們平時和別人探討問題的思路有兩種思路,第一種思路是一上去就排查,把問題解決掉。還有就看自己的系統,拼命的證明不是我的問題。在這個時候你強隔離就好說了,問題排查或者是黑盒子,特別是規模很大的時候就需要隔離很強,每個人在我的領域範圍內很容易定位。從去年到今年,阿里做了很多混部的宣傳,混部沒有強隔離也有問題的。

還有就是爲什麼要引入 P2P?最早 P2P 是在流媒體裏面,爲什麼又跟容器關聯起來了,就是因爲互聯網裏面有很強的思維,如果你慢的話,別人就會忍無可忍的。量少的話比工作更快一點。但是規模大的時候沒有辦法快,在鏈路上面來,包括業界也是一樣,連路瓶頸已經在拉鏡像,由此阿里很自然的就推了 P2P 加速。

還有內核的兼容,外界有一個說法——CTO 說阿里的商業成功,掩蓋了阿里的技術成功,這個話確實有道理。有些業務我們是在 2011 年的時候拿到 2.6 版本,現在業務最新的到了 4.10,那些老的業務每天服務的人羣量很小,不能說這個業務不賺錢或者沒有前景。把它下掉了也不行,要給一個緩衝期,該升級了,這個沒有辦法一步到位。這個時候要做規模化的升級,或者技術的換代,內核必須要做兼容。

前面的內容只是把阿里巴巴自己的問題解決了,並沒有把這些賦能給社區,所以要做標準化的思考,這個是爲了未來把好東西回饋社區,所以必須要做標準化的兼容,再反過頭來比這張圖,就知道這個架構怎麼思考,爲什麼要兼容 CRI。

代碼1

前面我說最難調的 bug 是低級錯誤造成的,這個低級錯誤什麼意思呢?

Golang 一開始很多人用 map 循環的時候,就受到了指針變量的誤用,典型的表現就是,一個對象循環之後就會發現中間的所有的值一模一樣,大家首先想到的是業務邏輯是不是不對,根本不會想到 for 循環是不是出了問題。後來我們發現我們對語言本身的理解還不是特別的到位,犯了低級的錯誤,這個 PPT 下面有詳細的代碼案例。

代碼2

還有 map 對象的異步序列化,現在看來本質上是我們用法不對,當時理解跟業務場景有關,我們每次篩選服務器有上萬的規模,爲什麼選擇這條服務器,要用日誌把它記下來。後面的優化、迭代要進行消息分析。我們就想到異步做這個事情,把對象存在異步裏面去刷盤。但是我們犯了致命的錯誤,就是 Goroute 對同一個 map 執行了讀寫併發,這樣就出現了 map 讀寫的衝突。如果知道對象是共享的資源,我們就會加鎖,但是這種場景下面我們沒有考慮到這個資源也會導致問題。業務場景,特別是異步對象持久化的,要有這樣一個意識,規模大的時候要考慮這樣一個問題。

代碼3

我們做的過程當中發現有一個資源泄露的問題,是場景導致的,我們起了很多 Goroute,我們有個主任務,主任務會起很多子任務,子任務做一些循環的操作。到阿里雲買服務器,阿里雲是異步接口,現在起很多任務去買服務器,買完之後把請求發貨去,它返還我一個異步的 ID,我再請求他狀態執行結果。比如說我先請求,完了之後它分配我一些資源,啓動這些資源。比如發起申請,然後 start,看看這個 start 是不是完成,start 也是有一個過程。比如阿里 90 分鐘再建一個淘寶,要拿到資源,肯定要很快。

我們爲什麼會遇到泄露呢?我們主任務要申請 200 個實例,我會發起 200 或 50 個併發的請求到阿里雲,但是不可能等 200 個請求全部完成了,200 個來了 15 個就已經向業務交付,用戶的體驗就很好。但是這種滾動式的交付,不可能無休止的等待,你得一分鐘之內完成。

除了滾動式的交付,還有總體任務時間的控制,這就涉及到兩級的 Goroute,第一級 Goroute 是總任務,子任務也是 Goroute,這就會涉及到泄露的問題。假設主任務超時了,就不管子任務,子任務一直在跑就會把資源耗完。

如果大家對 K8S 裏面的代碼,關於定時任務的框架有所瞭解的話,就會有更強的體會。當時我們如果看到這個的話就會借鑑他的方式,就不會採取我們自己造輪子的方式。這個案例最重要的就是以後寫 Goroute 的時候要注意是不是有泄露。

代碼4

我們爲什麼要做 Pod 打散?Pod 概念在阿里有幾級,第一級是物理層級,第二級是機框打散,往上還有核心交換機的打散。比如我有兩臺服務器,其中有一臺服務器掛掉以後,可用性一下子降到了 50%,其實創業公司還是可以接受的。但是在阿里,如果今天掛了 1%,就有非常多人投訴,所以要求非常高,所以哪怕是 1%的流量受到損失了,客服的量就一下子多很多。硬件故障和軟件故障是天然的,我們無法規避怎麼辦呢?所有的服務不要扎堆,物理機,核心交換機上面也不要扎堆,這就是 Pod 打散。

Pod 打散背後還有一層思考,比如說這臺機器已經有了相同的 Pod,另外一臺 Pod 一定不會往裏面部署,有很強的隔離,要麼是 0,要麼是 1,這個打散是強制的。但是這裏講的 Pod 打散不是強制的,是盡力交付。默認情況下儘可能地打散,如果打散不了,只能在一起,但是一旦有打散的機會,一定給你打散,我們的打散是在得分之後再做的事情,這個跟 K8S 不一樣,K8S 是強硬的,要打散就一定打散,要不然交付不成功。

阿里的服務量很大,服務器各個地方都有,其實它可以有一定的容忍度。假設有一萬個實例,可以允許 1%的實例有波動偏差,這個時候 Pod 就可以滿足它。之所以能夠接受就是因爲它的體量太大了,有扛波動的能力,這個跟我們接觸到的 K8S 完全不一樣,這是規模化的視角看到的東西。

3.總結

設計層面:整體架構層面優先語言選擇

性能層面:任務粒度選擇

數據驅動:狀態一致性轉移爲存儲一致性

語言理解:Map 異步序列化,Map 循環指針

多層併發:可控超時

調度算法:規模下 pod 打散

Pouch Container:擁抱開源,迴歸社區

【提問環節】

提問1:我想請教一下,你是利用 redis 實現數據一致性,數據一致性代替狀態移植,這個什麼意思?

李雨前:比如說在 K8S 裏面是面向結果交付的,我要三個實例我把數字改了你就可以擴出來,但是在 K8S 沒有出來之前,阿里已經存在了這些需求,那個時候是面向過程的,什麼意思呢?我要拉鏡像,我要關報警,這一系列的動作從容器交付週期來說,就是事務的一部分,這個時候是有狀態的。比如說,你想一種方式是每一個狀態都帶一個 ID,一連串的串回去,不可能是併發的。每一個狀態都是併發的往前做,到一個階段再併發做另外一個階段的事情,這個我們理解爲是有狀態的,這些狀態我們做的時候,做不出來很好效果。

爲什麼要放在 redis,我們做之前嘗試過一點一點去做,最後我們怎麼做呢?每一個狀態做完了就丟到 redis 裏面去。下一個任務做的時候就知道上一個任務是什麼,只要狀態滿足了就做下一個事情,最後你後面寫很多的任務,做的事情就很簡單,上一個狀態任務是什麼就完了,中間做什麼事情,也不需要看到全局的交互關係,這個就放到存儲,包括維護的一致性。

提問2:其實 Sigma 的思路好像跟 K8S 有點像,我不知道 Sigma 有沒有像 K8S 這種封裝嗎?是怎麼暴露服務的?

李雨前:你覺得他們兩個很像,那說明你對 K8S 或者 Sigma 有不錯的理解。實際上包括 Sigma,包括 mesos,包括 Omega,包括 yarn,這些東西你比較的時候,很多地方有相似之處,從整體上架構來理解,他們確實是相似的,但是我前面提到了Sigma 肯定是在 K8S 出來之前,那個時候就有自己演進的思路和方式,解決的問題都是一樣的。第二個問題,我們有沒有社區裏面大家看到的方式方法,比如說前面講的 PouchContainer 最後一項就是標準化的兼容,這已經反映了我們跟社區一些好的東西已經要開始吸收,把社區好的理念拿到阿里的場景進行實踐,實踐好了就回饋給社區,從這社區裏面學到的東西有很多很多,我們也努力向社區做一些反饋。

提問3:Sigma 底層可以用 PouchContainer,Docker 這種嗎?

李雨前:可以的,前面我說了現在出了一個 CRI,那個已經兼容了,實現了 CRI 的協議,你用起來是一樣的,你把 PouchContainer 起來的話,你用 Remoteruntime 配一下就可以了,我推薦你看一下 PouchContainer 官方文檔,裏面有一些案例。







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章