對Raft共識算法的一些理解

paper: In Search of an Understandable Consensus Algorithm (Extended Version)

爲什麼需要共識算法

爲了保證服務的穩定性(解決單點故障問題),人們提出了副本技術(replication)。但是副本之間需要一箇中心服務器進行協調(比如GFS的master,MapReduce的),那麼單點故障只是轉移到了中心服務器,並沒有得到徹底解決。

於是人們又提出了使用多箇中心服務器用來容錯,本質上又是副本技術,那麼如何保證一致性呢?這裏沒有使用更高一層的中心服務器,而是提出了RSM (replicated state machine),也就是把單個服務器當做一個狀態機,所有的輸入會引起狀態的轉移,同時需要保證服務器的所有操作都是確定的(比如獲取當前時間、產生隨機數等就是不確定行爲,產生的結果因機器等因素而異),否則不同服務器即使都接受相同的輸入,得到的狀態也會不一致。

一般來說,人們通過維護一個日誌,記錄所有的輸入,對於不確定指令,只在一臺服務器上執行,然後將其結果當做輸入記錄到日誌中,其他服務器按順序執行日誌中的命令,就可以保證所有的服務器保持相同的狀態。

那麼核心問題就是如何保證所有機器都維護相同的日誌,這就是共識算法需要做的事情了。

Paxos

Paxos提出的比較早,在長達十多年的時間裏一直是主流。但是非常難以理解,而且實現方面可能存在問題。
因此Diego Ongaro 和 John Ousterhout提出了Raft。

Raft

在Raft中,節點有三種狀態,LeaderFollowerCandidate,三種狀態之間的轉換關係如下如所示。
在這裏插入圖片描述
在正常情況下,只有一個Leader,其他所有節點都是FollowerCandidate狀態是爲了選舉新的Leader的臨時狀態。Follower只回應LeaderCandidate的消息,Leader處理所有來自client的消息。
在Raft中,時間被分爲了term,有連續的整數標識,每個term開始時都進行選舉,Raft保證每個term最多隻有一個Leader

選舉Leader (Leader election)

election timeout:選舉時間,在Raft中,Leader週期性的向Follower發送心跳包,如果Follower在選舉時間內沒有收到來自Leader或者Candidate的心跳包,那麼自己就變成Candidate去競選Leader,同時term加1。

Candidate狀態會一直持續,直到以下三種情況發生:

  1. Candidate贏了本次選舉:收到了大多數的投票,Candidate變成Leader,開始向其它節點發送心跳包,其它節點變成Follower.
  2. Candidate收到了某節點的心跳包,如果該心跳包包含的term不小於自己的term,那麼該Candidate變成Follower,否則忽略該心跳包。
  3. 沒有任何Candidate贏得選舉,那麼增加term重新開始新一輪的選舉。

爲了避免反覆進入第三種情況,Raft使用了隨機的election timeout(150-300ms),因此各個節點進入Follower的時間不完全相同(發起投票的時間不同),大大降低了進入第三種情況的可能。

日誌副本 (Log replication)

Leader收到client的請求後,先生成一個日誌條目(log entry),該條目都包含一個當前term和一個index(日誌中的位置),然後將該條目發給Follower。當日志條目複製到大多數節點後,日誌被Leader標記爲committed,此時返回結果給client。

在日誌中,Raft保證兩個屬性:

  1. 擁有相同termindex的日誌條目包含相同的請求命令。
  2. 擁有相同termindex的日誌條目之前的日誌條目完全相同。

由於日誌條目只由Leader生成,Leader保證每個term中對於一個index只生成一個日誌條目,因此屬性一顯然滿足。
對於屬性二,當LeaderFollower發送日誌條目時,會附帶前一個日誌條目的termindex,如果Follower的日誌條目與之不一致,就拒絕添加新的日誌條目,並向Leader索要前一個日誌條目,直到滿足termindex,然後將之後的日誌從Leader複製到Follower中。

安全(Safety)

以上還不足以保證所有的節點執行相同的操作,比如當一個Follower掉線重新上線後被選爲了Leader,由於該Leader丟失了很多日誌,後續可能會把前一個Leader標記爲Committed的日誌條目覆蓋掉,導致不同的節點執行了不同的操作。

Raft對Leader選舉添加了一個限制來解決這一問題:保證任何termLeader包含前一term中被標記爲Committed的日誌條目。

選舉限制 (Election restriction)

在投票過程中,Candidate需要附帶上自己的日誌中最後一個條目的termindex,如果投票者的日誌比Candidate的日誌新,那麼就拒絕投票。因此如果某Candidate得到了大多數節點的投票,就可以保證該Candidate的日誌包含了前一個term中被標記爲Committed的所有日誌條目。

這裏需要說明一下,由於只有被大多數節點備份的日誌條目纔會被標記爲Committed,而Candidate又需要得到大多數節點的投票,這兩個大多數節點中至少有一個節點是重複的,因此可以保證滿足該限制:任何termLeader包含前一term中被標記爲Committed的日誌條目

提交前一term的日誌條目 (Committing entries from previous terms)

Raft如何處理前一個term中的日誌條目? 如下圖所示,S1到S5是五臺服務器,每個方格中的數字是term,每個方格代表clients發起的一個請求命令。
在這裏插入圖片描述
在a中,S1是Leader,正在將日誌複製到Follower,此時S1出現了故障;到了b,S5被選爲了Leader,並接受了clients發起的一個命令,還沒開始複製就掛掉了;到了c,S1被重新選爲Leader,繼續複製未完成的term2中的日誌,此時該日誌已經被大多數節點接受,不應該再被撤銷了,然而,在S1準備commit該日誌的時候,S1掛了;到了d,S5被選爲新的Leader,複製日誌過程中將term2的日誌覆蓋了;在e中,如果S1沒有去管term2,直接去複製並commit term4的日誌,那麼當term4被commit之後,term2的日誌也會被間接commit,S5就不會被選爲Leader了,不會導致覆蓋發生。

有點難以理解。。。我的理解是:在c中,term2的命令已經被大多數節點記錄到日誌中,但是此時Leader是否執行、是否返回結果給client是未知的,因此我們只能假設已經執行並且返回了結果(否則會導致不一致),所以term2的日誌絕對不能再被覆蓋。

Raft對於這種問題的解決方案是:Leader不去直接commit前一個term的日誌,而是去commit當前term的日誌,然後通過前面提到的日誌副本的屬性二保證前面term的日誌會被間接commit。

參考資料

  1. https://www.zhihu.com/question/36648084/answer/82332860
  2. https://zhuanlan.zhihu.com/p/46531628

深入閱讀:

  • https://mp.weixin.qq.com/s?__biz=MjM5MDE0Mjc4MA==&mid=2650997287&idx=1&sn=4b3ef76bb90c2e28e259802866dc934e&chksm=bdbefa748ac97362184c485dd93ef821c82662f4d64359f0ba9d3cfbb387a593186d947c7dee&scene=21&key=d3935a5e90e39d9987fe240afa271d26503fbe790939c18bcee015#wechat_redirect
  • https://www.zhihu.com/question/29597104
  • https://www.zhihu.com/question/30026369
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章