1. 程式人生 > >解讀Raft(二 選舉和日誌複製)

解讀Raft(二 選舉和日誌複製)

Leader election

Raft採用心跳機制來觸發Leader選舉。Leader週期性的傳送心跳(如果有正常的RPC的請求情況下可以不發心跳)包保持自己Leader的角色(避免叢集中其他節點認為沒有Leader而開始選舉)。

Follower在收到Leader或者Candidate的RPC請求的情況下一直保持Follower狀態。而當一段時間內(election timeout)沒有收到請求則認為沒有Leader節點而出發選舉流程。

選舉流程如下:

  1. Follower遞增自己的任期並設定為Candidate角色
  2. 投票給自己並且併發的給所有節點發送投票請求
  3. 保持Candidate狀態直到:
    • 同一個任期內獲得大多數選票,成為Leader(一個節點在一個任期內只能給一個Candidate投票,任期相同則選票先到先得)並給其他節點發送心跳來保持自己的角色
    • 收到其他節點的RPC請求,如果請求中的任期大於等於Candidate當前的任期,認為其他節點成為了Leader,自身轉換為Follower;如果其他節點的任期小於自身的任期,拒絕RPC請求並保持Candidate角色
    • 一段時間後仍舊沒有Leader(可能是出現了平票的情況),則在選舉超時後重新發起一輪選舉(遞增任期、傳送投票請求)

為了避免平票的問題,同時在出現平票的情況後能快速解決,Raft的選舉超時時間是在一個區間內隨機選擇的(150~300ms)。這樣儘量把伺服器選舉時間分散到不同的時間,保證大多數情況下只有一個節點會發起選舉。在平票的情況下,每個節點也會在一個隨機時間後開始新一輪選舉,避免可能出現的一直處於平票的情況。

Log replication

一旦Leader被選舉出來後,Leader就開始為叢集服務:處理所有的客戶端請求並將資料複製到所有節點。

一旦日誌被“安全”的複製,那麼Leader將這個日誌應用到自己的狀態機並響應客戶端。

如果有節點異常或網路異常,Leader會一直重試直到所有日誌都會正確複製到所有節點(日誌不允許有空洞,所以每個節點上的日誌都是連續的,不能有因為失敗引起的空洞)。

日誌組織形式如上圖,每個日誌條目中包含可執行的指令、和日誌被建立時的任期號,日誌條目也包含了自己在日誌中的位置,即index。一旦一個日誌條目存在於大多數節點,那麼該日誌條目是committed的。

Raft演算法保證所有committed的日誌都是持久化的(日誌需要在大多數節點上持久化之後再響應給客戶端,這意味著每個Follower節點收到AppendEntry請求後需要持久化到日誌之後再響應給Leader),且最終會被所有的狀態機執行。

Raft演算法保證了以下特性:

  • 如果兩個日誌條目有相同的index和term,那麼他們儲存了相同的指令(即index和term相同,那麼可定是同一條指令,就是同一個日誌條目)
  • 如果不同的日誌中有兩個日誌條目,他們的index和term相同,那麼這個條目之前的所有日誌都相同

兩條規則合併起來的含義:兩個日誌LogA、LogB,如果LogA[i].index=Log[i]B.index且LogA[i].term=Log[i].term,那麼LogA[i]=Log[i]B,且對於任何n < i的日誌條目,LogA[n]=LogB[n]都成立。(這個結論顯而易見的可以從日誌複製規則中推匯出來)

一個新Leader被選舉出來時,Follower可能是上圖中的任何一種情況。

  • (a)(b)可能還沒複製到日誌
  • (c)(d)可能曾經是Leader,所有包含了多餘的日誌(這些日誌可能被提交了,也可能沒提交)
  • (e)可能是成為Leader之後增加了一些日誌,但是在Commit之前又程式設計了Follower角色,且還沒有更新日誌條目
  • (f)可能是在任期2稱為了Leader並追加了日誌但是還沒提交就Crash了,恢復之後在任期3又成了Leader並且又追加了日誌

在Raft中,通過使用Leader的日誌覆蓋Follower的日誌的方式來解決出現像上圖的情況(強Leader)。Leader會找到Follower和自己想通的最後一個日誌條目,將該條目之後的日誌全部刪除並複製Leader上的日誌。詳細過程如下:

  • Leader維護了每個Follower節點下一次要接收的日誌的索引,即nextIndex
  • Leader選舉成功後將所有Follower的nextIndex設定為自己的最後一個日誌條目+1
  • Leader將資料推送給Follower,如果Follower驗證失敗(nextIndex不匹配),則在下一次推送日誌時縮小nextIndex,直到nextIndex驗證通過

上面的方式顯然可以通過一些方法進行優化來減少重試的次數,但是在Raft論文中對是否有必要進行優化提出了質疑,因為這種異常的情況很少出現。

歡迎關注公眾號交流:


丞一

丞一

中介軟體技術專家 at 螞蟻金服丞一,目前就職於螞蟻金服,熱衷於研究分散式系統相關的技術;微信公眾號:MessageQueue,歡迎交流;