Raft算法之日志復制


上一篇文章:Raft算法之Leader選舉
  之前說完了Raft算法中的Leader選舉過程,本文將在上一篇文章的基礎上說明日志復制。

Raft算法之日志復制

  先看以下日志所包含的基本內容:

  1. 可以被復制狀態機執行的命令
  2. 任期號 :創建該日志時Leader所處的當前任期號
  3. 索引號 :整數,用於標識日志所在的位置

日志的狀態分為兩種:未被提交,已被提交(日志為安全的,不會被刪除或覆蓋)。

1 正常情況

  • Leader接收到由客戶端發送的請求(請求中包含可以被復制狀態機執行的命令)時,Leader將會把該請求作為新的內容添加到日志中(任期號為當前Leader所處的任期號,索引號為當前Leader本地存儲的日志集合中的日志的最高索引號加1)。
    • Leader在當前任期內最多只能創建一個給定索引號的日志(即不可能在一個任期內創建兩個以上的具有相同索引的日志條目)
  • 然后將該日志通過AppendEntries RPC消息發送到網絡中其他的服務器(以下簡稱Follower),從而復制該日志。
  • 在網絡中Follower接收到該日志消息后則會返回復制成功的回復。
  • Leader接收到網絡中大部分的Follower的成功復制的回復之后,Leader便認為該日志可以被提交。此時Leader將會同時做三件事:
  1. 將該日志應用到Leader本地的復制狀態機
  2. 向所有Follower發送消息通知所有接收到該日志的Follower將該日志進行提交,然后應用到各自本地的復制狀態機
  3. 將執行結果通知客戶端

  當該日志消息成功在網絡中大部分Follower本地的復制狀態機執行過后,則可認為該日志已被提交。在當前日志被提交的過程中,如果Leader先前的某些日志還沒有被提交,則將會一同提交。
  而網絡中有些Follower可能由於網絡狀態原因反應緩慢或者崩潰,那么Leader將會無限次地嘗試重復發送AppendEntries RPC消息到該Follower。直到成功為止。

1.1 日志的一致性檢查

  在上面,我們說了Follower在接收到AppendEntries RPC消息后則會返回復制成功的回復。實際上在接收到消息后會首先進行日志的一致性檢查(正常情況下LeaderFollower的日志會保持一致,所以一致性檢查不會失敗),一致性檢查內容如下:

  • Leader創建AppendEntries RPC消息時,消息中將會包含當前日志之前日志條目的任期號與索引號。
  • Follower在接受到AppendEntries RPC消息后,將會檢查之前日志的任期號與索引號是否可以匹配到
    • 如果匹配到則說明和Leader之前的日志是保持一致的。
    • 如果沒有匹配則會拒絕AppendEntries RPC消息.

  一致性檢查是一個歸納的過程。正常情況下,網絡中第一條日志一定滿足日志的一致性檢查,然后第二條日志中包含第一條日志的任期號與索引號,所以只要LeaderFollower的第一條日志保持一致,那么第二條日志也會滿足一致性檢查,從而之后的每一條日志都會滿足一致性檢查。

  從而得出了日志匹配屬性:

  • 如果兩個不同的日志實體具有相同的索引和任期號,那么他們存儲有相同的命令。
  • 如果兩個不同的日志實體具有相同的索引和任期號,則所有先前條目中的日志都相同。(由一致性檢查結果得出)

2 特殊情況

  而網絡不可能一直處於正常情況。因為Leader或者某個Follower有可能會崩潰,從而導致日志不能一直保持一致。因此存在以下三種情況:

  1. Follower缺失當前Leader上存在的日志條目。
  2. Follower存在當前Leader不存在的日志條目。(比如舊的Leader僅僅將AppendEntries RPC消息發送到一部分Follower就崩潰掉,然后新的當選Leader的服務器恰好是沒有收到該AppendEntries RPC消息的服務器)
  3. 或者Follower即缺失當前Leader上存在的日志條目,也存在當前Leader不存在的日志條目
圖

  圖中最上方是日志的索引號(1-12),每個方塊代表一條日志信息,方塊內數字代表該日志所處的任期號。圖中當前Leader(圖中最上方一行日志代表當前Leader日志)處於任期號為8的時刻。以此圖說明以上三種情況存在的原因:

  • Follower a,b(Follower崩潰沒有接收到Leader發送的AppendEntries RPC消息)滿足以上說明的第一種情況。
  • (Followerc在任期為6的時刻,Followerd在任期為7的時刻)為Leader,但沒有完全完成日志的發送便崩潰了.滿足以上說明的第三種情況。
  • Followere在任期為4的時刻,Followerf在任期為2,3的時刻為Leader,,但沒有完全完成日志的發送便崩潰了,同時在其他服務器當選Leader時刻也沒有接收到新的Leader發送的AppendEntries RPC消息,滿足第三種情況。

2.1 日志不一致的解決方案

  Leader通過強迫Follower的日志重復自己的日志來處理不一致之處。這意味着Follower日志中的沖突日志將被Leader日志中的條目覆蓋。因此Leader必須找到與Follower最開始日志發生沖突的位置,然后刪除掉Follower上所有與Leader發生沖突的日志。然后將自己的日志發送給Follower以解決沖突。
Leader不會刪除或覆蓋自己本地的日志條目

  這些步驟從之前說到的日志的一致性檢查開始。

  • 當發生日志沖突時,Follower將會拒絕由Leader發送的AppendEntries RPC消息,並返回一個響應消息告知Leader日志發生了沖突。
  • Leader為每一個Follower維護一個nextIndex值。該值用於確定需要發送給該Follower的下一條日志的位置索引。(該值在當前服務器成功當選Leader后會重置為本地日志的最后一條索引號+1)
  • Leader了解到日志發生沖突之后,便遞減nextIndex值。並重新發送AppendEntries RPC到該Follower。並不斷重復這個過程,一直到Follower接受該消息。
  • 一旦Follower接受了AppendEntries RPC消息,Leader則根據nextIndex值可以確定發生沖突的位置,從而強迫Follower的日志重復自己的日志以解決沖突問題。
圖
  • 情況a: 如圖,服務器S1在任期為2的時刻僅將日志<index:2,term:2>發送到了服務器S2便崩潰掉。
  • 情況b: 服務器S5在任期為3的時刻當選Leader(S5的計時器率先超時,遞增任期號為3因此高於服務器S3,S4,可以當選Leader),但沒來得及發送日志便崩潰掉。
  • 情況c: 服務器S1在任期為4的時刻再次當選Leader(S1重啟時,任期仍然為2,收到新的LeaderS5發送的心跳信息后更新任期為3,而在LeaderS5崩潰后,服務器S1為第一個計時器超時的,因此發起投票,任期更新為4,大於網絡中其他服務器任期,成功當選Leader),同時將日志<index:2,term:2>發送到了服務器S2,S3,但還沒有通知服務器對日志進行提交便崩潰掉。
  • 情況d: 情況(a->d)如果在任期為2時服務器S1作為Leader崩潰掉,S5在任期為3的時刻當選Leader,由於日志<index:2,term:2>還沒有被復制到大部分服務器上,並沒有被提交,所以S5可以通過自己的日志<index:2,term:3>覆蓋掉日志<index:2,term:2>
  • 情況e: 情況(a->e)而如果在任期為2時服務器S1作為Leader,並將<index:2,term:2>發送到S2,S3,成功復制到大多數成員服務器上。並且成功提交了該日志,那么即便S1崩潰掉,S5也無法成功當選Leader,因為S5不具備網絡中最新的已被提交的日志條目(這里說明了上一篇文章Raft算法之Leader選舉中選舉Leader的要求中沒有介紹的那一點要求).

2.2 選舉Leader的對日志的要求

  • Raft使用投票程序來防止Candidate贏得選舉,除非其日志中包含所有已提交的日志條目。
  • Candidate必須聯系集群的大多數才能被選舉,這意味着每個提交的條目都必須存在於其中至少一台服務器中。如果Candidate的日志至少與該多數服務器日志中的日志一樣最新(以下精確定義了最新),則它將保存所有已提交的條目。
  • Raft通過比較日志中最后一個條目的索引和任期來確定兩個日志中哪個是最新的。如果日志中的最后一個條目具有不同的任期,則帶有較新任期的日志是最新的。如果日志以相同的任期結尾,則以索引更大的日志為准。

  解決方案的優化
  在Follower拒絕AppendEntries RPC消息時,可以選擇將發生沖突的日志的任期與該任期內的第一條日志索引包含在拒絕消息中返回給Leader,從而使得Leader可以快速定位到發生沖突的位置。有了這些信息,Leader可以遞減nextIndex來繞過該任期中所有沖突的條目。每個具有沖突日志條目所處的任期都需要一個AppendEntries RPC消息,而不是每個日志條目都需要一個AppendEntries RPC消息。

3 日志復制安全性

Raft保證任何時刻這里的每一條屬性都成立

  • Leader只追加特性:Leader從不覆蓋或刪除它的日志條目,只追加新的。
  • 日志匹配: 如果兩個日志包含的實體具有相同的索引和任期,那么直到給定索引為止,所有條目中的日志都是相同的。
  • Leader完整性:如果一個日志提示在給定的任期內被提交,那么該條目將出現在所有任期更高的領導者的日志中.
  • 狀態機安全:如果服務器應用一條給定索引的日志實體到它的狀態機,那么沒有其他服務器可以應用一條不同的日志到相同的索引位置。

3.1 Leader完整性證明

  假設Leader完整性不成立,然后證明是矛盾的。
  假設任期為TLeader提交了當前任期的日志條目,但是該日志沒有被任期高於T的任期為U的未來的新的Leader所存儲。

  1. 被提交任期為T的日志必須不存在於將要選舉的任期為ULeader的復制狀態機中(因為Leader從不覆蓋或刪除它的日志條目)。
  2. 任期為TLeader將日志復制到集群中的大部分成員本地。並且任期為ULeader在選舉階段接收到集群中大部分成員的投票,因此至少集群中有一個成員(以下稱為投票者)即接收到來自任期為TLeader發送的日志,也為任期為ULeader投了票。所以該投票者是證明矛盾的關鍵所在。
  3. 投票者必須在為任期為ULeader投票之前將任期為TLeader的發送的日志提交。不然投票者將會拒絕任期為TLeaderAppendEntries PRC請求(因為一旦接收到任期為ULeader投票請求,投票者的任期將會高於T)。
  4. 投票者當為任期為ULeader投票時,將會一直存儲該日志條目。假設在任期為TU之間的每一個Leader都包含該日志條目(Leader從不刪除日志條目,而Follower僅在與Leader沖突時才刪除條目)。
  5. 投票者為任期為ULeader投票,所以任期為ULeader日志必須至少和投票者的日志一樣新。這將導致產生兩個矛盾之中的一個矛盾。
  6. 首先,如果投票者和任期為ULeader具有相同的最新的日志任期。那么任期為ULeader的日志至少和投票者的日志一樣長。所以任期為ULeader的日志將包含投票者所有的日志。這是一個矛盾,因為之前假設的投票者包含被提交的任期為T的日志,而任期為ULeader不包含。
  7. 否則,任期為ULeader的最后一條日志的任期號必須大於投票者的最后一條日志的任期號。而且,它比T大,因為投票者的上一個日志任期號至少為T(它包含任期T中的所有已提交的條目)。創建任期為ULeader的最后一個日志條目的較早的Leader必須在其日志中(通過假設)包含已提交的條目。然后,通過日志匹配屬性,任期為ULeader的日志還必須包含已提交的條目,這是矛盾的。
  8. 這樣就證明了矛盾,因此所有任期大於TLeader都必須包含所有任期為T的被提交的日志。
  9. 日志匹配屬性保證未來的Leader還將包含間接提交的日志條目。

下一篇文章:Raft算法之成員關系變化


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM