當前位置:
首頁 > 知識 > 三篇文章了解 TiDB 技術內幕——談調度

三篇文章了解 TiDB 技術內幕——談調度

任何一個複雜的系統,用戶感知到的都只是冰山一角,資料庫也不例外。

前兩篇文章介紹了 TiKV、TiDB 的基本概念以及一些核心功能的實現原理,這兩個組件一個負責 KV 存儲,一個負責 SQL 引擎,都是大家看得見的東西。在這兩個組件的後面,還有一個叫做 PD(Placement Driver)的組件,雖然不直接和業務接觸,但是這個組件是整個集群的核心,負責全局元信息的存儲以及 TiKV 集群負載均衡調度。

本篇文章介紹一下這個神秘的模塊。這部分比較複雜,很多東西大家平時不會想到,也很少在其他文章中見到類似的東西的描述。我們還是按照前兩篇的思路,先講我們需要什麼樣的功能,再講我們如何實現,大家帶著需求去看實現,會更容易的理解我們做這些設計時背後的考量。

為什麼要進行調度

先回憶一下第一篇文章提到的一些信息,TiKV 集群是 TiDB 資料庫的分散式 KV 存儲引擎,數據以 Region 為單位進行複製和管理,每個 Region 會有多個 Replica(副本),這些 Replica 會分布在不同的 TiKV 節點上,其中 Leader 負責讀/寫,Follower 負責同步 Leader 發來的 raft log。了解了這些信息後,請思考下面這些問題:

如何保證同一個 Region 的多個 Replica 分布在不同的節點上?更進一步,如果在一台機器上啟動多個 TiKV 實例,會有什麼問題?

TiKV 集群進行跨機房部署用於容災的時候,如何保證一個機房掉線,不會丟失 Raft Group 的多個 Replica?

添加一個節點進入 TiKV 集群之後,如何將集群中其他節點上的數據搬過來?

當一個節點掉線時,會出現什麼問題?整個集群需要做什麼事情?如果節點只是短暫掉線(重啟服務),那麼如何處理?如果節點是長時間掉線(磁碟故障,數據全部丟失),需要如何處理?

假設集群需要每個 Raft Group 有 N 個副本,那麼對於單個 Raft Group 來說,Replica 數量可能會不夠多(例如節點掉線,失去副本),也可能會 過於多(例如掉線的節點又回復正常,自動加入集群)。那麼如何調節 Replica 個數?

讀/寫都是通過 Leader 進行,如果 Leader 只集中在少量節點上,會對集群有什麼影響?

並不是所有的 Region 都被頻繁的訪問,可能訪問熱點只在少數幾個 Region,這個時候我們需要做什麼?

集群在做負載均衡的時候,往往需要搬遷數據,這種數據的遷移會不會佔用大量的網路帶寬、磁碟 IO 以及 CPU?進而影響在線服務?

這些問題單獨拿出可能都能找到簡單的解決方案,但是混雜在一起,就不太好解決。有的問題貌似只需要考慮單個 Raft Group 內部的情況,比如根據副本數量是否足夠多來決定是否需要添加副本。但是實際上這個副本添加在哪裡,是需要考慮全局的信息。整個系統也是在動態變化,Region 分裂、節點加入、節點失效、訪問熱點變化等情況會不斷發生,整個調度系統也需要在動態中不斷向最優狀態前進,如果沒有一個掌握全局信息,可以對全局進行調度,並且可以配置的組件,就很難滿足這些需求。因此我們需要一個中心節點,來對系統的整體狀況進行把控和調整,所以有了 PD 這個模塊。

調度的需求

上面羅列了一大堆問題,我們先進行分類和整理。總體來看,問題有兩大類:

作為一個分散式高可用存儲系統,必須滿足的需求,包括四種

副本數量不能多也不能少

副本需要分布在不同的機器上

新加節點後,可以將其他節點上的副本遷移過來

節點下線後,需要將該節點的數據遷移走

作為一個良好的分散式系統,需要優化的地方,包括:

維持整個集群的 Leader 分布均勻

維持每個節點的儲存容量均勻

維持訪問熱點分布均勻

控制 Balance 的速度,避免影響在線服務

管理節點狀態,包括手動上線/下線節點,以及自動下線失效節點

滿足第一類需求後,整個系統將具備多副本容錯、動態擴容/縮容、容忍節點掉線以及自動錯誤恢復的功能。滿足第二類需求後,可以使得整體系統的負載更加均勻、且可以方便的管理。

為了滿足這些需求,首先我們需要收集足夠的信息,比如每個節點的狀態、每個 Raft Group 的信息、業務訪問操作的統計等;其次需要設置一些策略,PD 根據這些信息以及調度的策略,制定出盡量滿足前面所述需求的調度計劃;最後需要一些基本的操作,來完成調度計劃。

調度的基本操作

我們先來介紹最簡單的一點,也就是調度的基本操作,也就是為了滿足調度的策略,我們有哪些功能可以用。這是整個調度的基礎,了解了手裡有什麼樣的鎚子,才知道用什麼樣的姿勢去砸釘子。

上述調度需求看似複雜,但是整理下來最終落地的無非是下面三件事:

增加一個 Replica

刪除一個 Replica

將 Leader 角色在一個 Raft Group 的不同 Replica 之間 transfer

剛好 Raft 協議能夠滿足這三種需求,通過 AddReplica、RemoveReplica、TransferLeader 這三個命令,可以支撐上述三種基本操作。

信息收集

調度依賴於整個集群信息的收集,簡單來說,我們需要知道每個 TiKV 節點的狀態以及每個 Region 的狀態。TiKV 集群會向 PD 彙報兩類消息:

每個 TiKV 節點會定期向 PD 彙報節點的整體信息

TiKV 節點(Store)與 PD 之間存在心跳包,一方面 PD 通過心跳包檢測每個 Store 是否存活,以及是否有新加入的 Store;另一方面,心跳包中也會攜帶這個 Store 的狀態信息,主要包括:

總磁碟容量

可用磁碟容量

承載的 Region 數量

數據寫入速度

發送/接受的 Snapshot 數量(Replica 之間可能會通過 Snapshot 同步數據)

是否過載

標籤信息(標籤是具備層級關係的一系列 Tag)

每個 Raft Group 的 Leader 會定期向 PD 彙報信息

每個 Raft Group 的 Leader 和 PD 之間存在心跳包,用於彙報這個 Region 的狀態,主要包括下面幾點信息:

Leader 的位置

Followers 的位置

掉線 Replica 的個數

數據寫入/讀取的速度

PD 不斷的通過這兩類心跳消息收集整個集群的信息,再以這些信息作為決策的依據。除此之外,PD 還可以通過管理介面接受額外的信息,用來做更準確的決策。比如當某個 Store 的心跳包中斷的時候,PD 並不能判斷這個節點是臨時失效還是永久失效,只能經過一段時間的等待(默認是 30 分鐘),如果一直沒有心跳包,就認為是 Store 已經下線,再決定需要將這個 Store 上面的 Region 都調度走。但是有的時候,是運維人員主動將某台機器下線,這個時候,可以通過 PD 的管理介面通知 PD 該 Store 不可用,PD 就可以馬上判斷需要將這個 Store 上面的 Region 都調度走。

調度的策略

PD 收集了這些信息後,還需要一些策略來制定具體的調度計劃。

一個 Region 的 Replica 數量正確

當 PD 通過某個 Region Leader 的心跳包發現這個 Region 的 Replica 數量不滿足要求時,需要通過 Add/Remove Replica 操作調整 Replica 數量。出現這種情況的可能原因是:

某個節點掉線,上面的數據全部丟失,導致一些 Region 的 Replica 數量不足

某個掉線節點又恢復服務,自動接入集群,這樣之前已經補足了 Replica 的 Region 的 Replica 數量多過,需要刪除某個 Replica

管理員調整了副本策略,修改了 max-replicas 的配置

一個 Raft Group 中的多個 Replica 不在同一個位置

注意第二點,『一個 Raft Group 中的多個 Replica 不在同一個位置』,這裡用的是『同一個位置』而不是『同一個節點』。在一般情況下,PD 只會保證多個 Replica 不落在一個節點上,以避免單個節點失效導致多個 Replica 丟失。在實際部署中,還可能出現下面這些需求:

多個節點部署在同一台物理機器上

TiKV 節點分布在多個機架上,希望單個機架掉電時,也能保證系統可用性

TiKV 節點分布在多個 IDC 中,向單個機房掉電時,也能保證系統可用

這些需求本質上都是某一個節點具備共同的位置屬性,構成一個最小的容錯單元,我們希望這個單元內部不會存在一個 Region 的多個 Replica。這個時候,可以給節點配置 lables 並且通過在 PD 上配置 location-labels 來指名哪些 lable 是位置標識,需要在 Replica 分配的時候盡量保證不會有一個 Region 的多個 Replica 所在結點有相同的位置標識。

副本在 Store 之間的分布均勻分配

Leader 數量在 Store 之間均勻分配

Raft 協議要讀取核寫入都通過 Leader 進行,所以計算的負載主要在 Leader 上面,PD 會儘可能將 Leader 在節點間分散開。

訪問熱點數量在 Store 之間均勻分配

每個 Store 以及 Region Leader 在上報信息時攜帶了當前訪問負載的信息,比如 Key 的讀取/寫入速度。PD 會檢測出訪問熱點,且將其在節點之間分散開。

各個 Store 的存儲空間佔用大致相等

每個 Store 啟動的時候都會指定一個 Capacity 參數,表明這個 Store 的存儲空間上限,PD 在做調度的時候,會考慮節點的存儲空間剩餘量。

控制調度速度,避免影響在線服務

調度操作需要耗費 CPU、內存、磁碟 IO 以及網路帶寬,我們需要避免對線上服務造成太大影響。PD 會對當前正在進行的操作數量進行控制,默認的速度控制是比較保守的,如果希望加快調度(比如已經停服務升級,增加新節點,希望儘快調度),那麼可以通過 pd-ctl 手動加快調度速度。

支持手動下線節點

當通過 pd-ctl 手動下線節點後,PD 會在一定的速率控制下,將節點上的數據調度走。當調度完成後,就會將這個節點置為下線狀態。

調度的實現

了解了上面這些信息後,接下來我們看一下整個調度的流程。

PD 不斷的通過 Store 或者 Leader 的心跳包收集信息,獲得整個集群的詳細數據,並且根據這些信息以及調度策略生成調度操作序列,每次收到 Region Leader 發來的心跳包時,PD 都會檢查是否有對這個 Region 待進行的操作,通過心跳包的回復消息,將需要進行的操作返回給 Region Leader,並在後面的心跳包中監測執行結果。注意這裡的操作只是給 Region Leader 的建議,並不保證一定能得到執行,具體是否會執行以及什麼時候執行,由 Region Leader 自己根據當前自身狀態來定。

總結

本篇文章講的東西,大家可能平時很少會在其他文章中看到,每一個設計都有背後的考量,希望大家能了解到一個分散式存儲系統在做調度的時候,需要考慮哪些東西,如何將策略、實現進行解耦,更靈活的支持策略的擴展。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 PingCAP 的精彩文章:

TAG:PingCAP |