Raft共識演算法
一.背景
拜占庭將軍問題是分散式領域最複雜、最嚴格的容錯模型。但在日常工作中使用的分散式系統面對的問題不會那麼複雜,更多的是計算機故障掛掉了,或者網路通訊問題而沒法傳遞資訊,這種情況不考慮計算機之間互相傳送惡意資訊,極大簡化了系統對容錯的要求,最主要的是達到一致性。
所以將拜占庭將軍問題根據常見的工作上的問題進行簡化:假設將軍中沒有叛軍,信使的資訊可靠但有可能被暗殺的情況下,將軍們如何達成一致性決定?
對於這個簡化後的問題,有許多解決方案,第一個被證明的共識演算法是 Paxos,由拜占庭將軍問題的作者 Leslie Lamport 在1990年提出,但因為 Paxos 難懂,難實現,所以史丹佛大學的教授Diego Ongaro 和 John Ousterhout在2014年發表了論文《In Search of an Understandable Consensus Algorithm》,其中提到了新的分散式協議 Raft
。與 Paxos 相比,Raft 有著基本相同執行效率,但是更容易理解,也更容易被用在系統開發上。
二.概述
Raft實現一致性的機制是這樣的:首先選擇一個leader全權負責管理日誌複製,leader從客戶端接收log entries(日誌條目),將它們複製給叢集中的其它機器,然後負責告訴其它機器什麼時候將日誌應用於它們的狀態機。舉個例子,leader可以在無需詢問其它server的情況下決定把新entries放在哪個位置,資料永遠是從leader流向其它機器(leader的強一致性)。一個leader可以fail或者與其他機器失去連線,這種情形下會有新的leader被選舉出來。
在任何時刻,每個server節點有三種狀態:leader
,candidate
,follower
。
- leader:作為客戶端的接收者,接收客戶端傳送的日誌複製請求,並將日誌資訊複製到 follower 節點中,維持網路各個節點的賬本狀態。
- candidate:在leader 選舉階段存在的狀態,通過任期號term和票數進行領導人身份競爭,獲勝者將成為下一任期的領導人。
- follower:作為leader 節點傳送日誌複製請求的接收者,與leader節點通訊,接收賬本資訊,並確認賬本資訊的有效性,完成日誌資訊的提交和儲存。
正常執行時,只有一個leader,其餘全是follower。follower是被動的:它們不主動提出請求,只是響應leader和candidate的請求。leader負責處理所有客戶端請求(如果客戶端先連線某個follower,該follower要負責把它重定向到leader)。candidate狀態用於選舉領導節點。下圖展示了這些狀態以及它們之間的轉化:
Raft將時間分解成任意長度的terms
,如下圖所示:
terms有連續單調遞增的編號,每個term開始於選舉,這一階段每個candidate都試圖成為leader。如果一個candidate選舉成功,它就在該term剩餘週期內履行leader職責。在某種情形下,可能出現選票分散,沒有選出leader的情況,這時新的term立即開始。Raft確保在任何term都只可能存在一個leader。term在Raft用作邏輯時鐘,servers可以利用term判斷一些過時的資訊:比如過時的leader。每臺server都儲存當前term號,它隨時間單調遞增。term號可以在任何server通訊時改變:如果某個server節點的當前term號小於其它servers,那麼這臺server必須更新它的term號,保持一致;如果一個candidate或者leader發現自己的term過期,則降級成follower;如果某個server節點收到一個過時的請求(擁有過時的term號),它會拒絕該請求。
Raft servers使用RPC互動,基本的一致性演算法只需要兩種RPC。RequestVote RPCs
由candidate在選舉階段發起。AppendEntries RPCs
在leader複製資料時發起,leader在和follower做心跳時也用該RPC。servers發起一個RPC,如果沒得到響應,則需要不斷重試。另外,發起RPC是並行的。
三.具體共識流程
raft演算法大致可以劃分為兩個階段,即Leader Selection
和Log Relocation
,同時使用強一致性來減少需要考慮的狀態。
3.1 Leader Selection
Raft使用heartbeat
(心跳機制)來觸發選舉。當server節點啟動時,初始狀態都是follower。每一個server都有一個定時器,超時時間為election timeout
(時間長度一般為150ms~300ms),如果某server沒有超時的情況下收到來自leader或者candidate的任何RPC,則定時器重啟,如果超時,它就開始一次選舉。leader給followers發RPC要麼複製日誌,要麼就是用來告訴followers自己是leader,不用選舉的心跳(告訴followers對狀態機應用日誌的訊息夾雜在心跳中)。如果某個candidate獲得了超過半數節點的選票(自己投了自己),它就贏得了選舉成為新leader。
上述的具體過程如下:
➢ 初始狀態下叢集中的所有節點都處於 follower 狀態。
➢ 某一時刻,其中的一個 follower 由於沒有收到 leader 的 heartbeat 率先發生 election timeout 進而發起選舉。
➢ 只要叢集中超過半數的節點接受投票,candidate 節點將成為即切換 leader 狀態。
➢ 成為 leader 節點之後,leader 將定時向 follower 節點同步日誌併傳送 heartbeat。
如果leader節點出現了故障,那怎麼辦?
下面將說明當叢集中的 leader 節點不可用時,raft 叢集是如何應對的。
➢ 一般情況下,leader 節點定時傳送 heartbeat 到 follower 節點。
➢ 由於某些異常導致 leader 不再傳送 heartbeat ,或 follower 無法收到 heartbeat 。
➢ 當某一 follower 發生election timeout 時,其狀態變更為 candidate,並向其他 follower 發起投票。
➢ 當超過半數的 follower 接受投票後,這一節點將成為新的 leader,leader 的任期號term加1並開始向 follower 同步日誌。
➢ 當一段時間之後,如果之前的 leader 再次加入叢集,則兩個 leader 比較彼此的任期號,任期號低的leader將切換自己的狀態為follower。
➢ 較早前 leader 中不一致的日誌將被清除,並與現有 leader 中的日誌保持一致。
還有第三種可能性就是candidate既沒選舉成功也沒選舉失敗:如果多個follower同時成為candidate去拉選票,導致選票分散,任何candidate都沒拿到大多數選票,這種情況下Raft使用超時機制election timeout
來解決。所以同時出現多個candidate的可能性不大,即使機緣巧合同時出現了多個candidate導致選票分散,那麼它們就等待自己的election timeout超時,重新開始一次新選舉,實驗也證明這個機制在選舉過程中收斂速度很快。
3.2 Log Relocation
在 raft 叢集中,所有日誌都必須首先提交至 leader 節點。leader 在每個 heartbeat 向 follower 傳送AppendEntries RPC同步日誌,follower如果發現沒問題,複製成功後會給leader一個表示成功的ACK,leader收到超過半數的ACK後應用該日誌,返回客戶端執行結果。若 follower 節點當機、執行緩慢或者丟包,則 leader 節點會不斷重試AppendEntries RPC,直到所有 follower 節點最終都複製所有日誌條目。
上述的具體過程如下:
➢ 首先有一條 uncommitted 的日誌條目提交至 leader 節點。
➢ 在下一個 heartbeat,leader 將此條目複製給所有的 follower。
➢ 當大多數節點記錄此條目之後,leader 節點認定此條目有效,將此條目設定為已提交併儲存於本地磁碟。
➢ 在下一個 heartbeat,leader 通知所有 follower 提交這一日誌條目並儲存於各自的磁碟內。
Network Partition 情況下進行復制日誌:
由於網路的隔斷,造成叢集中多數的節點在一段時間內無法訪問到 leader 節點。按照 raft 共識演算法,沒有 leader 的那一組叢集將會通過選舉投票出新的 leader,甚至會在兩個叢集內產生不一致的日誌條目。在叢集重新完整連通之後,原來的 leader 仍會按照 raft 共識演算法從步進數更高的 leader 同步日誌並將自己切換為 follower。
➢ 叢集的理想狀態。
➢ 網路間隔造成大多數的節點無法訪問 leader 節點。
➢ 新的日誌條目新增到 leader 中。
➢ leader 節點將此條日誌同步至能夠訪問到 leader 的節點。
➢ follower 確認日誌被記錄,但是確認記錄日誌的 follower 數量沒有超過叢集節點的半數,leader 節點並不將此條日誌存檔。
➢ 在被隔斷的這部分節點,在 election timeout 之後,followers 中產生 candidate 併發起選舉。
➢ 多數節點接受投票之後,candidate 成為 leader。
➢ 一個日誌條目被新增到新的 leader並複製給新 leader 的 follower。
➢ 多數節點確認之後,leader 將日誌條目提交併儲存。
➢ 在下一個 heartbeat,leader 通知 follower 各自提交併儲存在本地磁碟。
➢ 經過一段時間之後,叢集重新連通到一起,叢集中出現兩個 leader 並且存在不一致的日誌條目。
➢ 新的 leader 在下一次 heartbeat timeout 時向所有的節點傳送一次 heartbeat。
➢ #1 leader 在收到任期號term更高的 #2 leader heartbeat 時放棄 leader 地位並切換到 follower 狀態。
➢ 此時leader同步未被複制的日誌條目給所有的 follower。
通過這種方式,只要叢集中有效連線的節點超過總數的一半,叢集將一直以這種規則執行下去並始終確保各個節點中的資料始終一致。
參考
[1] https://www.jianshu.com/p/8e4bbe7e276c
[2] Ongaro D, Ousterhout J. In search of an understandable consensus algorithm[C]// USENIX Annual Technical Conference. [s.l.]: USENIX. 2014: 305-319.
[3] https://www.cnblogs.com/aibabel/p/10973585.html
[4] Raft原理動畫:http://thesecretlivesofdata.com/raft/