分佈式一致性算法 raft

主節點選舉

每個節點有三種角色:
follower: 從節點,被動回覆leader和candidate的request。
leader: 主節點,處理client的寫request
candidate: leader的候選人

follower 會接受 leader 的心跳包,如果沒有收到 leader 的心跳包,將會在隨機時間之後變成 candidate 開始競選 leader。同時選舉出來的新主節點也是通過發送心跳來讓其餘的節點從 candidate 狀態轉換爲 follower 的。

投票前的準備

  1. 每個節點有一個固定的ID以在多個節點之間進行識別。

  2. 每個節點有一個任期號 term,從零開始,以後逐漸單向往上遞增。服務器重啓後需要知道當前的term 纔可以正確的跟其它節點交流,所以 term 是必須持久化的.

  3. 每個節點都可以向其他節點發起投票,要求其他的節點選擇自己作爲主節點。

  4. 爲了避免所有節點同時發起投票,每個節點會分配一個隨機的選舉超時時間 electionTimeout,也就是一個等待時間,等待時間過了之後纔可以發起投票。所以每個節點發起投票的時間都是不一樣的。這樣降低了提示投票導致選票被瓜分的情況。等待時間超時之後繼續隨機一個等待時間並開始下一輪選舉。

開始投票

那麼當一個節點的等待時間到了之後,該節點會轉換爲 Candidate,

  1. 先給自己投一票,並自增terms, 自增 term 表示當前任期內我已經投過票了(投給自己),所以任期結束。重置選舉超時計時器 electionTimeout。
  2. 通知其他節點,要求他們選擇自己,通知的時候需要帶上自己的 term,ID,最新的日誌 Index 。

投票節點收到投票請求的回覆

有2種情況

  1. 成功了。拿到一個節點的選票。

  2. 失敗了。此時要將自己的 term 更新爲對方回覆的 term。

而每收到一個節點的回覆,要判斷是不是以下情況:

  1. 當前是否已經獲得了半數以上節點的票,是的話贏得選舉,成爲leader;向其他節點發送心跳,宣誓主權。

  2. 沒有獲得半數以上節點的票。electionTimeout 時間到了,在超時之後自增當前任期 terms,重新隨機一個 electionTimeout 並開始下一個任期的選舉;

當然在這個過程中有可能發生:收到其他節點的心跳請求,說明leader已經選出了,那結束選舉,自己變成 follower,選舉結束;

節點收到其他節點的投票請求

  1. 如果對方的 term 小於自己的,則拒絕投票,並將自己的 term 返回給對方,讓投票的人跟上時間(更新自己的term);如果大於,則立刻轉爲 follower,並返回成功,告訴對方我已經成爲你的 follower 了。

  2. 如果1的情況都不符合,說明2者的任期相同。判斷當前任期內是否已經投過票了。是的話也要拒絕投票,因爲同一任期內不能多次投票。

  3. 如果步驟2中還沒有結果,此時再比較對方的數據和自己的哪一個更新(根據日誌的最新時間),如果對方沒有自己新,也要拒絕投票。

  4. 上述條件都滿足,將票投給對方,返回成功。並等待 electionTimeout。

最終一定會有一個節點勝出。
當選出了主節點之後,主節點會跟從節點都保持心跳,從節點每次收到心跳都要重置自己的等待定時器,這樣只要主節點跟從節點之間不失聯,從節點就永遠不會發生選舉。
而一旦失聯,其餘的從節點立刻就根據自己的等待時間 electionTimeout 再次開始選舉了。

上述過程中,主節點就是 leader, 從節點就是 follower,從節點失去主節點的心跳開始競爭選舉的階段就是 candidate。

整個過程中有三種RPC:

  1. RequestVote RPC:投票用的RPC
  2. AppendEntries RPC:主從保活的心跳RPC(同時同步也用這個RPC)
  3. InstallSnapshot RPC:主節點給落後太多的從節點發送快照。

整個過程有三個定時器:

  1. BroadcastTime : 主節點定時發送給從節點的心跳定時時間
  2. Election Timeout :從節點等待進行選舉的超時時間
  3. MTBT : 指的是單個服務器發生故障的間隔時間的平均數

三個定時器的超時時間應該是:
BroadcastTime << ElectionTimeout << MTBF
心跳時間一定要小於從節點等待的超時時間,從節點每次收到心跳都會重置這個等待時間。

一般BroadcastTime大約爲0.5毫秒到20毫秒,ElectionTimeout一般在10ms到500ms之間。大多數服務器的MTBF都在幾個月甚至更長。

主從同步,日誌複製

leader 選出來之後,如何保證主從之間的同步?
複製狀態機通常都是基於複製日誌實現的,每個節點都有各自的日誌文件,比如在Redis 中是 AOF 文件。
在不同服務器上的不同文件中,每條日誌記錄都有一個任期號和遞增的索引。
如果不同文件的索引和任期號都相同,說明這2個日誌一樣。也就說明2臺服務器上的數據是一致的。

那麼會有2種情況:

  1. 正常運行的情況,在主從數據都一致的情況下:
    客戶端每發送一個寫命令給主節點,主節點會將寫命令 append 到主節點的日誌文件最後,然後將命令廣播給所有的從節點。當所有的從節點都收到這條命令之後,將執行結果返回給客戶端。如果這個過程有的從節點因爲網絡原因沒有收到,也會回給客戶端,但是對於失敗的命令會不斷重試。
    因爲主機節點擁有所有從節點的最新的日誌 index 。當從節點回復了之後就更新其日誌 index。

  2. 從節點崩潰很久之後重啓,導致的主從數據差異很大:

  3. 主節點崩潰,導致重新選舉。

同時主從之間的心跳會不斷的發送主節點的log

有一個問題,主節點和從節點之間的日誌不同步,主節點領先從節點N條數據,然後主節點掛掉,新的主節點被選中,此時新主的數據其實不是全局最新的。

安全性和成員變化

如果重新選舉之後的主節點落後於從節點,從節點多餘的數據將會全部抹掉。
如果從節點的日誌比主節點少,主節點會減少日誌索引,直到找到最終的一致的地方

如果一個跟隨者的日誌和領導人不一致,那麼在下一次的附加日誌 RPC 時的一致性檢查就會失敗。在被跟隨者拒絕之後,領導人就會減小 nextIndex 值並進行重試。最終 nextIndex 會在某個位置使得領導人和跟隨者的日誌達成一致。當這種情況發生,附加日誌 RPC 就會成功,這時就會把跟隨者衝突的日誌條目全部刪除並且加上領導人的日誌。一旦附加日誌 RPC 成功,那麼跟隨者的日誌就會和領導人保持一致,並且在接下來的任期裏一直繼續保持

https://github.com/maemual/raft-zh_cn/blob/master/raft-zh_cn.md

http://xiaorui.cc/2016/07/08/技術分享-《分佈式一致性算法實現原理》/

https://www.infoq.cn/article/raft-paper

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