容器化RDS|排程策略

沃趣科技發表於2017-11-21

沃趣科技·熊中哲

導 語

前文資料庫容器化|未來已來我們介紹了基於Kubernetes實現的下一代私有 RDS。其中,排程策略是具體實現時至關重要的一環,它關係到RDS 叢集的服務質量和部署密度。那麼,RDS 需要怎樣的排程策略呢?本文通過資料庫的視角結合Kubernetes的原始碼,分享一下我的理解。

It was the best of times, it was the worst of times。

—by Dickens.

人類從爬行到直立用了幾百萬年,但是我們這些碼農從Bare Metal到 Container只花了幾萬分之一的時間。

容器化RDS|排程策略

我有個朋友是維護Mainframe的,他還在使用40年前的系統。

容器化RDS|排程策略

排程策略很重要

看看巨人們在幹什麼,有助於我們更好的理解這個世界。

  • Google Borg

先看看Google是如何看待Borg (Kubernetes 的前身)的核心價值。在Google paper <Large-scale cluster management at Google with Borg>中,開篇就定義了 Borg :

It achieves high utilization by combining admission control, efficient task-packing,over-commitment, and machine sharing with process-level performance isolation.

裡面還專門介紹了基於 CPI (Cycles Per Instruction)測量資源利用率的方式。

  • AWS RDS

再看看公有云的領頭羊, AWS是這樣描述其RDS產品的:

容器化RDS|排程策略

不管是Google Borg還是AWS,除了提供更靈活,更開放,更相容,更安全,可用性更高的系統,都將cost-efficient,high utilization放到了更重要的位置。

提高部署密度,減少硬體的需求量,最終達到降低硬體投入的目標。

同時,

必須滿足業務需求。

本文嘗試以資料庫的視角,從多個角度闡述RDS場景需要怎樣的排程策略。

說明:

  • 為了實現更精細化的排程策略,Kubernetes(版本1.7) 排程器提供了17個排程演算法。這些演算法分為兩類PredicatePriority,通俗的描述是過濾和打分。設計思路大致如下:

1.通過過濾演算法,從叢集中出滿足條件的節點;

2.通過打分演算法,對過濾出來的節點打分並排名;

3.挑出分數最高的節點,如果有分數相同的,隨機挑一個。

  • 本文將基於Kubernetes的實現,結合RDS場景展開,並不會把所有的演算法流水賬似的寫一遍,相關資料很多,有興趣的同學可以去看文件。具體實現見:

① kubernetes/plugin/pkg/scheduler/algorithm/priorities

②kubernetes/plugin/pkg/scheduler/algorithm/predicates

下面進入主題。

排程策略

視角一 : 計算資源排程策略

這裡討論的計算資源僅包含 CPU,Memory

容器化RDS|排程策略

需要特別說明,畢竟Kubernetes已經支援GPU

看上去很簡單,挑選出一個滿足資源要求的節點即可,但是考慮到整合密度和資料庫的業務特點並不簡單,我們還需要考慮到以下幾點:

  • 峰值和均值:

資料庫的負載隨著業務、時間、週期不斷變化,到底是基於峰值排程還是均值排程呢?這是一個有關部署密度的問題,最好的辦法就像Linux裡面限定資源的方式,讓我們設定Soft Limit 和Hard Limit,以Soft Limit分配資源,同時Hard Limit又能限定使用的最大資源。Kubernetes也是這麼做的,它會通過 Request 和 Limit 兩個閾值來進行管理容器的資源使用。

容器化RDS|排程策略

Pod是Kubernetes的排程單位

Requst作為Pod初始分配值,Limit 限定了Pod能使用的最大值。分配時採用Requst值進行排程,這裡有個假設:

同一節點上執行的容器不會同時達到 Limit 閾值

有效的實現了計算資源利用率的high utilization,非常適合資料庫開發或測試場景。

如果假設不成立,

當某節點執行的所有容器同時接近Limit,並有將節點資源用完的趨勢或者事實(在執行的過程中,排程器會定期收集所有節點的資源使用情況,“蒐集”用詞不太準確,但便於理解),建立 Pod的請求也不會再排程到該節點。

容器化RDS|排程策略

以記憶體為例, 當Pod的請求超出Node可以提供的記憶體, 會以異常的方式告知排程器, 記憶體資源不足

同時,基於優先順序,部分容器將會被驅逐到其他節點(例如通過重啟 Pod 的方式),所以並不適合生產環境。

  • 資源的平衡:

對於長期執行的叢集,在滿足資源的同時還要考慮到叢集中各節點資源分配的平衡性。

類似Linux Buddy System,僅僅分配程式需要的記憶體是不夠的,還要保障作業系統記憶體的連續性。

舉個例子,RDS叢集有兩個節點,使用者向RDS申請 2顆CPU和4GB記憶體 以建立 MySQL例項,兩節點資源使用情況如下:

容器化RDS|排程策略

在資源同時滿足的情況下,排程會通過兩個公式對節點打分。

基於已使用資源比率(Balanced Resource)打分,實現如下:

容器化RDS|排程策略

將節點資源輸入公式,可簡化成:

NodeA 分數 = int(1-math.Abs(8/16 - 8/32)) * float64(10) = 30/4

NodeB 分數 = int(1-math.Abs(8/32 - 16/64)) * float64(10) = 10

基於該演算法Node B的分數更高。

再通過未使用資源(calculateUnused)持續打分。

容器化RDS|排程策略

該演算法可簡化成:

cpu((capacity - sum(requested)) * 10 / capacity) + memory((capacity - sum(requested)) * 10 / capacity) / 2

有興趣的同學可以算一下,不再贅述。

資料庫會被排程到綜合打分最高的節點。

視角二 : 儲存資源排程策略

儲存資源是有狀態服務中至關重要的一環,也讓有狀態服務的實現難度遠超無狀態服務。

除了滿足請求資料庫的儲存資源的容量要求,排程策略必須要能夠識別底層的儲存架構和儲存負載,在提供儲存資源的同時,滿足資料庫的業務需求(比如資料零丟失和高可用)。

從2017年年初開始,基於分散式儲存技術,我們的RDS已經實現了計算和儲存分離的架構。

容器化RDS|排程策略

計算儲存分離

在實現資料庫的資料零丟失,高可用的同時,架構變得更通用,更簡單。但對企業級使用者,還遠遠不夠,cost-efficient 是考量產品成熟度的重要因素。

所以從一開始,我們就以3種維度的儲存QoS來思考這個問題:

  • 從功能角度 :

儲存資源分成兩大類

distribution,基於分散式儲存技術實現,對 Flash 裝置做了專門的 優化,提供資料冗餘和彈性擴容功能;

local,使用計算節點本地儲存。

對於生產環境,我們會申請distribution資源。而那些不太重要的或者臨時性的,譬如有的客戶需要經常生成臨時性的克隆庫進行測試,或者擴充套件臨時備庫以應對突發的業務高峰,我們會申請 local資源。

  • 從效能角度:

我們又將distribution分成了兩類highmedium,以應業務不同的IOPS,Through put,Latency需求。

IO密集型業務,我們會分配high型別。對於計算密集型或者重要值很高的備庫,我們會分配medium型別。

  • 從資料庫角度:

比如, 不同的資料庫物理卷的掛載引數也不同;

如果排程器能夠實現, 將極大的提高儲存資源的 cost-efficient。

這些特性帶有明顯的資料庫業務特性,原生的Kubernetes排程器並不支援。但是,我們通過二次開發,Out of Cluster的方式實現了外接的Kubernetes storage provisoner,並通過自定義的引數和程式碼實現和排程器的互動。

容器化RDS|排程策略

Kubernetes 會使用我們提供的storage provisoner建立儲存資源.

這樣Kubernetes的排程器就可以基於RDS的業務需求,感知底層儲存架構,提供滿足業務需求的排程服務。

除去需要的容量資訊,需要傳遞給排程器如下資訊(就像請CPU,Memory資源一樣):

volume.beta.kubernetes.io/mount-options: sync

volume.orain.com/storage-type: "distribution"

volume.orain.com/storage-qos: "high"

volume.orain.com/dc-id: "278"

通過這四個引數將會告知。

  • 從功能角度:

volume.orain.com/storage-type: "distribution", 使用 distribution 型別儲存資源。

  • 從效能角度:

volume.orain.com/storage-qos: "high", 從高效能儲存池獲取 Volume

  • 從資料庫角度:

volume.beta.kubernetes.io/mount-options: sync, 使用特定 mount 引數

volume.orain.com/dc-id: "278", 使用編號為278的 Volume

視角三 : 關係型資料庫

關係型資料庫是有狀態服務,但要求更加複雜。比如我們提供了MySQLRead Write Cluster (讀寫分離叢集) 和Sharding Cluster (分庫分表叢集),每個資料庫例項都有自己的角色。排程器必須感知叢集角色以實現業務特點:

比如, 基於資料庫角色, 我們有如下排程需求:

  1. ReadWrite ClusterMasterSlave不能排程到同一節點

  2. Master的多個Slave不能排程到同一節點

  3. Sharding Cluster的每個分片不能排程到同一節點

  4. 某些備份任務須排程到指定Slave所在的節點

  5. …..

帶有明顯的業務(RDS)特點,原生Kuberentes的排程策略並不能識別這些角色和關係。

與此同時,容器的執行狀態和RDS叢集還在動態變化:

容器化RDS|排程策略

因 Failover遷移到其他節點

容器化RDS|排程策略

RDS 叢集 Scale Out

以上具體的問題抽象成:

親和性(Affinity), 反親和性(Anti-Affinity)和分佈度(Spread Width)

再通過我們的二次開發,將資料庫的角色和業務流程整合到排程器中,以滿足全部需求。

  • 親和性(Affinity)

排程需求4可以歸納到這裡

需求4 : 某些備份任務須排程到指定 Slave 所在的節點

在所有節點中找到指定 Slave 所在節點, 以確定待排程備份任務排程到哪個節點. 該需求必須滿足, 不然備份任務無法成功.

建立已執行資料庫和節點的關係,在通過Affinity和Anti-Affinity公式對所有節點打分,以此決定待排程資料庫是否要排程到該節點。

查詢該節點所有資料庫例項:

容器化RDS|排程策略

確定該節點是否有指定 Slave:

容器化RDS|排程策略

  • 反親和性(Anti-Affinity)

需求1 : ReadWrite Cluster 的 Master 和 Slave 不能排程到同一節點

以待排程資料庫的角色為輸入,建立已執行資料庫和節點的關係,再通過 Anti-Affinity 公式對所有節點打分,以此決定待排程資料庫是否要排程到該節點。

以需求1為例,統計叢集成員的分佈情況,該節點上同一資料庫叢集的成員越多,分數越低。

容器化RDS|排程策略

反親和性(Anti-Affinity)公式

容器化RDS|排程策略

對所有節點打分

  • 分佈度(Spread Width)

有種更時髦的叫法散射度(scatter width)

需求2,3可以歸納到這裡。

以需求2為例, 統計叢集成員的分佈情況, 該節點上同一資料庫叢集的成員越多, 分數越低。

容器化RDS|排程策略

然後對所有節點打分,公式如下:

float64(schedulerapi.MaxPriority) * ((maxCountByNodeName -countsByNodeName[node.Name]) / maxCountByNodeName)

需要特別說明的是, 在RDS進行排程時:

  • 需求1,4必須滿足;

  • 需求2,3儘量滿足既可以。

必須儘量也需要作為排程引數,讓排程器知曉。

結 語

本文僅以RDS的視角,從三個層級講述了對排程器的要求。

真實的世界會更加複雜,比如針對Read Write ClusterSlave 必須等待Master建立完畢,而Sharding Cluster,所有分片可以併發建立……

在設計產品和完成編碼的過程中,踩坑無數。不能否認的是,站在巨人的肩膀上可以讓我們看的更遠。不知道Ending怎麼寫, 就這樣吧。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/28218939/viewspace-2147570/,如需轉載,請註明出處,否則將追究法律責任。

相關文章