看動畫輕鬆學會 Raft 算法

由於 Paxos 算法過於晦澀難懂且難以實現,Diego Ongaro 提出了一種更易於理解和實現並能等價於 Paxos 算法的共識算法 - Raft 算法。

因爲 Raft 算法清晰易懂越來越多的開源項目嘗試引入 Raft 算法來解決分佈式一致性問題。在分佈式存儲領域基於 Raft 算法構建的項目百花齊放,欣欣向榮。

介紹 Raft 算法的文章早已是汗牛充棟,本文先介紹兩個非常優秀的網站:

The Secret Lives of Data-CN 以圖文方式介紹 Raft 算法,是非常好的入門材料。將其閱讀完後您大概率已經瞭解了 Raft 算法,如果您仍有疑問可以回來繼續閱讀本文。

既然您已經回來繼續閱讀,相信您已經瞭解 Raft 算法中的Leader 選舉、日誌複製等基本概念, 但仍有部分疑惑。沒關係, 接下來我們會解決這些問題。

Raft Scope 是 Raft 官方提供的互動式演示程序,它展示了 Raft 集羣的工作狀態。您可以用它模擬節點宕機、心跳超時等各種情況。有了 Raft Scope 我們可以親自“動手” 觀察 Raft 集羣是如何工作、如何處理各種故障的。

遺憾的是這個程序幾乎沒有任何說明非常難以上手。本文接下來將先介紹如何使用 Raft Scope 然後用它模擬幾種 Raft 集羣工作中會遭遇的典型狀況。

Raft Scope 說明

可以看到 Raft Scope 界面由三部分組成。

最下方有兩個滑塊:上面的是進度條您可以拖動它回看剛剛發生過事件,下面的是變速器滑塊越靠左系統運行越慢。

左上角部分是一個由 5 個節點組成的 Raft 集羣,每個圓圈代表集羣中的一個節點。點擊節點可以看到它的狀態。對話框的右下角有一些按鈕,我們可以點擊按鈕模擬各種狀況。我們直接右鍵點擊節點也可以看到這些按鈕

這些按鈕的功能是:

  • stop: 節點停機
  • resume: 啓動停機的節點
  • restart: 將節點立即重啓
  • time out: 模擬心跳超時,點擊按鈕後相應節點會認爲 Leader 發生了心跳超時。
  • request: 向集羣提交新的數據

節點中間的數字是節點當前的任期號(Term), 節點的顏色似乎同樣是用來表示任期的。
節點可能處於 Follower、Candidate 或者 Leader 狀態。

S2 處於 Candidate 狀態,實心原點表示它現在收到的投票。圖中的兩個原點表示收到了 S2 和 S4 的投票,這 5 個小圓點和集羣中節點的位置是對應的,左下角的小圓點表示 S4, 最上面的小圓點表示 S1。在集羣選舉過程中節點外的動態邊框表示 Election Timeout。

黑色實心邊框表示 S5 是 Leader。Follower 外面的邊框表示 HeartBeat 超時倒計時。

右上角的表格表示各節點的日誌,每行表示一個節點。

表格最上面的數字是日誌的序號(Log Index)。Log Index 是一個自增且連續的 ID,它可以作爲一條日誌唯一標識。節點中最大的 Log Index 也反映了這個節點的狀態機是否與集羣一致。

表格裏的單元格表示日誌項(Entry),其中的數字表示提交日誌的任期(Term)。虛線框表示日誌尚未提交,實線框表示日誌已經提交。

我們可以點擊 leader 節點的 request 按鈕來查看向 Raft 集羣提交數據的過程。

Leader 選舉

Raft Scope 啓動後會立即進行第一次 Leader 選舉,在集羣運行過程任何一個 Follower 出現心跳超時都會引發新一輪選舉。

我們可以點擊任意一個 Follower 的 time out 按鈕模擬心跳超時,隨後此 Follower 會發起新一輪選舉。

或者我們可以點擊 Leader 的 stop、restart 來模擬 Leader 宕機或者重啓,並觀察隨後的集羣選舉過程。

比較奇怪的是, Raft Scope 中的 Leader 節點也可以通過點擊 time out 來模擬心跳超時,在實際的 Raft 集羣中 Leader 節點通常不會對自己進行心跳檢測。

Leader 選舉的更多介紹可以查看:Leader選舉。不過 The Secret Lives of Data 有兩處說的可能不太清楚:

這裏的選舉超時是指新一輪選舉開始時,每個節點隨機思考要不要競選 Leader 的時間,這個時間一般100-到200ms,非常短。

Candidate 發起選舉時會將自身任期(Term)+1並向其它所有節點發出 RequestVote 消息,這條消息中包含新任期和 Candidate 節點的最新 Log Index

收到 RequestVote 的節點會進行判斷:

def onRequestVote(self, request_vote)
    if request_vote.term <= self.term:
        # 若 RequestVote 中的任期小於或等於(<=)當前任期
        # 則繼續 Follow 當前 Leader 並拒絕給 RequestVote投票
        return False
    if request_vote.log_index < self.log_index:
        # 若 request_vote 發送者的 log_index 不如自己新,節點也會拒絕給發送者投票
        # 這種機制確保了已經提交到集羣中的日誌不會丟失,即保證 Raft 算法的安全性
        return False
    if self.voted_for is None:
        # 若在本 term 中當前節點還未投票,則給 request_vote 的發送者投票
        self.voted_for = request_vote.sender
        return True
    else:
        return False

Follower 超時

現在我們研究一下 The Secret Lives of Data 沒有詳細說明的 Follower 超時處理過程。

我們可以點擊任意一個 Follower 的 time out 按鈕模擬心跳超時,隨後此 Follower 會發起新一輪選舉。

根據上文中的 onRequestVote 邏輯,超時的 Follower 的 Log Index 是否與集羣中的大多數節點相同決定了這次選舉的不同結果。

首先來看超時 Follower 的 Log Index 與集羣中大多數相同的情況:

現在我們點擊 S5 的 time out 按鈕,隨後我們看到 S5 發起了一輪投票。因爲 5 個節點的 Log Index 是一致的, 所以包含原 Leader 在內的大多數節點都投票給了 S5。

現在 S5 成爲了新一任 Leader.

接下來我們看另外一種情況。S5 由於網絡問題沒有收到帶有 Log Entry 1 的心跳包並導致心跳超時,S5 隨後會發起一次投票:

由於 S5 的 Log Index 比較小其它節點拒絕投票給他,集羣 Leader 和任期不變:

日誌複製

日誌複製的介紹您可以查看:日誌複製

現在我們進一步探究日誌複製的過程:

  1. 客戶端將更改提交給 Leader, Leader 會在自己的日誌中寫入一條未提交的記錄(Entry)
  2. 在下一次心跳時 Leader 會將更改發送給所有 Follower
  3. 一旦收到過半節點的確認 Leader 就會提交自己日誌中的記錄4
  4. 並向客戶端返回寫入成功
  5. Leader 會在下一次心跳時通知所有節點提交日誌

這裏比較複雜的情況是在第 4 步完成之後 Leader 崩潰。由於此時客戶端已經收到了寫入成功的回覆,所以在選出新的 Leader 之後要繼續完成提交。

在 Leader 提交了自己的日誌後我們立即關掉 Leader:

隨後集羣發起了一次選舉,S3 成爲新任 Leader:

可能是因爲 Raft Scope 存在 Bug, S3 本應該當選後立即完成提交工作。但是實際上需要我們再一次 Request 之後,日誌1 和日誌 2 纔會被一起提交。

腦裂問題

在 Leader 崩潰時可能會有多個節點近乎同時發現心跳超時並轉變爲 Candidate 開始選舉:

其它節點投票情況多種多樣,但只要保證獲只有得到過半投票的候選人才能成爲 Leader。那麼選舉結果只有兩種可能:

  • 有且只有一個候選人獲得過半投票成爲 Leader 並開始新的任期
  • 沒有一個候選人獲得過半投票,沒有選出 Leader 進入下一輪投票

絕對不會選出多個 Leader

網絡分區問題

Raft 甚至可以在網絡分區的情況下正常工作:

在發生網絡分區後可能存在 3 種情況:

  1. 任意分區中的節點數都不超過一半:這種情況只有集羣被分成 3 個或更多分區時纔會出現,十分罕見。因爲 Leader 選舉和 Commit Log 都需要超過一半節點確認纔可以進行,在這種情況下 Raft 集羣不能正常工作。

  2. leader 所在的分區有超過一半的節點:這種情況視作其它分區中的 Follower 宕機,系統仍然可以繼續工作。在分區修復後,Follower 節點會重新與 Leader 同步。

  3. leader 所在分區中節點數不超過一半,但存在節點數超過一半的分區。這種情況最爲複雜:

C、D、E 所在的分區節點數超過一半且與原來的 Leader 無法通信,隨後 C、D、E 在心跳超時後會發起新一輪投票選出新的 Leader 並恢復工作。

原領導者 Node B 仍然會認爲自己是集羣的 Leader,但是由於只能與兩個節點通信(包括自己)無法得到過半節點同意,所以無法完成日誌提交。

在分區修復後 Node B 會收到 Node C 的心跳並發現對方的任期(Term)比自己高,Node B 會放棄 Leader 身份轉爲 Node C 的 Follower 與它保持同步。

總結

經過本文探討我們可以總結一下 Raft 的一些特性:

  • 只要集羣中有超過一半的節點可以正常工作,集羣就可以工作
  • 只要寫入成功的數據就不會再丟失
  • 任意節點上保存的狀態可能會落後於集羣共識但是永遠不會出現錯誤的提交。只要系統仍然在正常工作,節點上的狀態一定會在某個時間後與系統共識達成同步,即保證最終一致性
  • 只要在某個節點上讀到了某個變更, 在此之後這個節點上永遠可以讀到該變更,即保證單調一致性

推薦閱讀:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章