Raft論文《 In Search of an Understandable Consensus Algorithm (Extended Version) 》研讀

Raft 論文研讀

說明:本文爲論文 《 In Search of an Understandable Consensus Algorithm (Extended Version) 》 的我的理解,不免有理解不到位之處,歡迎交流與指正 。git

論文地址Raft Papergithub


1. 複製狀態機

複製狀態機 (Replicated state machine) 方法在分佈式系統中被用於解決 容錯問題 ,這種方法中,一個集羣中各服務器有相同狀態的副本,而且在一些服務器宕機的狀況下也能夠正常運行 。算法

如上圖所示,每臺服務器都存儲一個包含一系列命令的 日誌 ,而且按照日誌的順序執行。每臺服務器都順序執行相同日誌上的命令,所以它們能夠保證相同的狀態 。數據庫

一致性算法 的工做就是保證複製日誌的相同 。一臺服務器上,一致性模塊接收 client 的請求命令並將其寫入到本身的日誌中,它和其餘服務器上一致性模塊通訊來保證集羣中服務器的日誌都相同 。命令被正確地複製後,每個服務器的狀態機按照日誌順序執行它們,最後將輸出結果返回給 client安全

所以,服務器集羣看起來就是一個高可用的狀態機,只要集羣中大多數機器能夠正常運行,就能夠保證可用性 。服務器

關於複製狀態機的更詳細內容,能夠閱讀 VM-FT ,不過 Raft 應用到的複製狀態機通常是應用級複製,沒必要達到像 VM-FT 那樣的機器級複製 。網絡

可將複製狀態機應用於 MapReducemasterGFSmaster 以及 VM-FT 的存儲服務器 。分佈式


2. Raft 簡介

Raft 是一種爲了管理複製日誌的一致性算法 。爲了提升 可理解性優化

  • 將問題分解爲:領導人選舉、日誌複製、安全性和角色轉變等部分
  • 經過減小狀態的數量來簡化須要考慮的狀態空間

Raft 算法在許多方面都和現有的一致性算法很類似,但也有獨特的特性:spa

  • 強領導性:和其餘一致性算法相比,Raft 使用一種更強的領導能力形式。好比,日誌條目只從領導者發送給其餘的服務器。
  • 領導選舉:Raft 算法使用一個隨機計時器來選舉領導者,可有效解決選舉時候的衝突問題 。
  • 成員關係調整:Raft 使用一種共同一致的方法來處理集羣成員變換的問題,在這種方法下,處於調整過程當中的兩種不一樣的配置集羣中大多數機器會有重疊,這就使得集羣在成員變換的時候依然能夠繼續工做。

一個 Raft 集羣必須包含奇數個服務器,若包含 2f+1 臺服務器,則能夠容忍 f 臺服務器的故障 。( 爲了保留多數服務器在線,以正常完成日誌提交和 leader 選舉 )


3. 領導人選舉

Raft 經過選舉一個 leader ,而後給予它所有的管理日誌的權限,以此來實現一致性 。

一個服務器處於三種狀態之一:leaderfollowercandidate

  • leader:系統中只能有一個 leader ,它接收 client 的請求( 若 clientfollower 聯繫,follower 會把請求重定向給 leader ),並將日誌複製到 followerleader 宕機或與其餘服務器斷開鏈接後會選舉新的 leader
  • followerfollower 不會發送任何請求,只會響應來自 leadercandidate 的請求 。
  • candidate:一個 follower 在選舉超時後就會變成 candidate ,此後它將進行選舉,得到多於半數票數即成爲 leader

一次選舉開始對應這一個新的 term (任期)term 使用連續的整數標記 ,一個 term 最多有一個 leader

Raft 會使用一種 心跳機制 來觸發領導人選舉,當 leader 在位時,它週期性地發送心跳包(不含 log entryAppendEntries RPC 請求) 給 follower ,若 follower 在一段時間內未接收到心跳包( 選舉超時 ),則認爲系統中沒有 leader ,此時該 follower 會發起選舉 。

當系統啓動時,全部服務器都是 follower ,第一個選舉超時的發起選舉 。

3.1 選舉過程

  • candidate
    • 選舉超時的 follower 增長當前 term 號,轉換到 candidate 狀態
    • candidate 並行地向其餘服務器發送 RequestVote RPC 請求
    • candidate 保持狀態一直到:
      • candidate 獲取了超過半數服務器的選票,則成爲 leader ,並開始向全部 follower 發送心跳包
      • candidate 在等候投票過程當中接收到來自其餘服務器的心跳包,表示已有 leader 被選舉。若心跳包 term 不小於此 candidate 當前 term,則此 candidate 認可新 leader 合法並回到 follower 狀態;不然此 candidate 拒絕這次心跳包並保持 candidate 狀態
      • 如有同時有多個 candidate ,它們可能都沒法得到超過半數的投票( split vote 問題 ),則全部 candidate 都超時,並增長 term 號,開始新一輪的選舉
  • 投票方服務器
    • 每一個 candidate 只爲本身投票
    • 每個服務器最多對一個 term 號投出一張票,按照先來先服務的規則
    • RequestVote RPC 請求中包含了 candidate 的日誌信息,投票方服務器會拒絕日誌沒有本身新的投票請求
      • 若是兩份日誌最後 entryterm 號不一樣,則 term 號大的日誌更新
      • 若是兩份日誌最後 entryterm 號相同,則比較長的日誌更新

3.2 split vote 問題:

  • 上文有提到,若同時有多個 candidate ,則它們可能都沒法得到超過半數的選票。此時它們會所有超時,並增長 term 號,開始新一輪的選舉。因爲它們會同時超時,因而 split vote 問題可能會一直重複 。
  • 爲解決此問題,使用 隨機選舉超時時間 。這樣能夠把服務器超時時間點分散開,一個 candidate 超時後,它能夠贏得選舉並在其餘服務器超時以前發送心跳包 。即便出現了一個 split vote 狀況,多個 candidate 會重置隨機選舉超時時間,能夠避免下一次選舉也出現 split vote 問題 。
  • 每次重置選舉計時器的時候,要選擇一個不一樣的新的隨機數,不能在服務器第一次建立的時候肯定一個隨機數,並在將來的選舉中重複使用該數字。不然可能有兩個服務器選擇相同的隨機值 。

3.3 日誌條目完整性保證

( 保證以前 term 中已提交的 entry 在選舉時都出如今新的 leader 的日誌中 ):

  • 由於 leader 提交一個 entry 須要此 entry 存在於大多數服務器
  • 又由於 candidate 爲贏得選舉須要得到集羣中大多數服務器的投票
  • 可知每個已提交的 entry 確定存在於 爲 candidate 投票的服務器 中的至少一個
  • 由於投票方服務器會拒絕日誌沒有本身新的投票請求,即新 leader 包含的日誌至少和全部投票方服務器的日誌都同樣新
  • 則新的 leader 必定包含了過去全部 term 已提交的全部 entry

3.4 時間和可用性

  • Raft 要求 安全性不能依賴時間:整個系統不能由於某些事件運行的比預期快一點或慢一點就產生錯誤的結果

  • 爲選舉並維持一個穩定的領導人,系統需知足:

    廣播時間(broadcastTime) << 選舉超時時間(electionTimeout) << 平均故障時間(MTBF)
    • 廣播時間 指從一個服務器並行發送 RPC 給集羣中其餘服務器並接收響應的平均時間(0.5~20ms)
    • 選舉超時時間 即上文介紹的選舉的超時時間限制(10~500ms)
    • 平均故障間隔時間 指對於一臺服務器而言,兩次故障之間的平均時間(幾個月甚至更長)
    • 廣播時間遠小於選舉超時時間,是爲了使 leader 可以發送穩定的心跳包維持管理
    • 選舉超時時間遠小於平均故障時間,是爲了使整個系統穩定運行( leader 崩潰後,系統不可用時間爲選舉超時時間,這個時間應在合理範圍內儘可能小 )

4. 日誌複製

4.1 日誌

每一個 log entry 存儲一個 狀態機命令 和從 leader 收到這條命令的 term ,每一條 log entry 都有一個整數的 log index 來代表它在日誌中的位置 。

committed log entry 指能夠安全應用到狀態機的命令。當由 leader 建立的 log entry 複製到大多數的服務器上時,log entry 就會被提交 。同時,leader 日誌中以前的 log entry 也被提交 (包含其餘 leader 建立的 log entry )。

日誌的做用

  • 使得 follower 以相同的順序執行與 leader 相同的命令
  • 使得 leader 確認 follower 與本身的日誌是一致的
  • 能夠保留 leader 須要從新發送給 follower 的命令
  • 服務器重啓後能夠經過日誌中的命令重放

4.2 複製流程

  • leader 接收到來自 client 的請求

  • leader 將請求中的命令做爲一個 log entry 寫入本服務器的日誌

  • leader 並行地發起 AppendEntries RPC 請求給其餘服務器,讓它們複製這條 log entry

  • 當這條 log entry 被複制到集羣中的大多數服務器( 即成功提交 ),leader 將這條 log entry 應用到狀態機( 即執行對應命令 )

  • leader 執行命令後響應 client

  • leader 會記錄最後提交的 log entryindex ,並在後續 AppendEntries RPC 請求( 包含心跳包 )中包含該 indexfollower 將此 index 指向的 log entry 應用到狀態機

  • follower 崩潰或運行緩慢或有網絡丟包,leader 會不斷重複嘗試 AppendEntries RPC ,直到全部 follower 都最終存儲了全部 log entry

  • leader 在某條 log entry 提交前崩潰,則 leader 不會對 client 響應,因此 client 會從新發送包含此命令的請求

4.3 一致性保證

Raft 使用 日誌機制 來維護不一樣服務器之間的一致性,它維護如下的特性:

  1. 若是在不一樣的日誌中的兩個 entry 擁有相同的 indexterm,那麼他們存儲了相同的命令
  2. 若是在不一樣的日誌中的兩個 entry 擁有相同的 indexterm,那麼他們以前的全部 log entry 也所有相同

對於 特性一leader 最多在一個 term 裏,在指定的一個 index 位置建立 log entry ,同時 log entry 在日誌中的位置不會改變 。

對於 特性二AppendEntries RPC 包含了 一致性檢驗 。發送 AppendEntries RPC 時,leader 會將新的 log entry 以及以前的一條 log entryindexterm 包含進去,若 follower 在它的日誌中找不到相同的 indexterm ,則拒絕此 RPC 請求 。因此,每當 AppendEntries RPC 返回成功時,leader 就知道 follower 的日誌與本身的相同了 。

4.4 衝突解決

上圖體現了一些 leaderfollower 不一致的狀況 。a~bfollower 有缺失的 log entryc~dfollower 有多出的未提交的 log entrye~f 爲兩種問題並存的 。

Raft 算法中,leader 處理不一致是經過:強制 follower 直接複製本身的日誌

  • leader 對每個 follower 都維護了一個 nextIndex ,表示下一個須要發送給 followerlog entryindex
  • leader 剛上任後,會將全部 followernextIndex 初始化爲本身的最後一條 log entryindex 加一( 上圖中的 11
  • 若是一個 AppendEntries RPCfollower 拒絕後( leaderfollower 不一致 ),leader 減少 nextIndex 值重試( prevLogIndexprevLogTerm 也會對應改變 )
  • 最終 nextIndex 會在某個位置使得 leaderfollower 達成一致,此時,AppendEntries RPC 成功,將 follower 中的衝突 log entry 刪除並加上 leaderlog entiry

衝突解決示例

10 11 12 13
S1 3
S2 3 3 4
S3 3 3 5
  • S3 被選爲 leaderterm 爲 6,S3 要添加新的 entry 13S1S2
  • S3 發送 AppendEntries RPC ,包含 entry 13nextIndex[S2]=13prevLogIndex=12preLogTerm=5
  • S2S3index=12 上不匹配,S2 返回 false
  • S3 減少 nextIndex[S2] 到 12
  • S3 從新發送 AppendEntries RPC ,包含 entry 12+13nextIndex[S2]=12preLogIndex=11preLogTerm=3
  • S2S3index=11 上匹配,S2 刪除 entry 12 ,增長 leaderentry 12entry 13
  • S1 流程同理

一種 優化方法

AppendEntries RPC 被拒絕時返回衝突 log entryterm 和 屬於該 termlog entry 的最先 indexleader 從新發送請求時減少 nextIndex 越過那個 term 衝突的全部 log entry 。這樣就變成了每一個 term 須要一次 RPC 請求,而非每一個 log entry 一次 。

減少 nextIndex 跨過 term 的不一樣狀況

follower 返回失敗時包含:

  • XTerm:衝突的 log entry 所在的 term
  • XIndex:衝突 term 的第一個 log entryindex
  • XLenfollower 日誌的長度

follower 返回失敗時的狀況:

其中 S2leader ,它向 S1 發送新的 entryAppendEntries RPCprevLogTerm=6

對於 Case 1nextIndex = XIndex

對於 Case 2nextIndex = leaderXTerm 的最後一個 index

對於 Case 3nextIndex = XLen

( 論文中對此處未做詳述,只要知足要求的設計都可 )


5. follower 和 candidate 崩潰

若是 followercandidate 崩潰了,那麼後續發送給它們的 RPC 就會失敗 。

Raft 處理這種失敗就是無限地重試;若是機器重啓了,那麼這些 RPC 就會成功 。

若是一個服務器完成一個 RPC 請求,在響應前崩潰,則它重啓後會收到一樣的請求 。這種重試不會產生什麼問題,由於 RaftRPC 都具備冪等性 。( 如:一個 follower 若是收到附加日誌請求可是它已經包含了這一日誌,那麼它就會直接忽略這個新的請求 )


6. 日誌壓縮

日誌不斷增加帶來了兩個問題:空間佔用太大、服務器重啓時重放日誌會花費太多時間 。

使用 快照 來解決這個問題,在快照系統中,整個系統狀態都以快照的形式寫入到持久化存儲中,而後到那個時間點以前的日誌所有丟棄 。

每一個服務器獨立地建立快照,快照中只包括被提交的日誌 。當日志大小達到一個固定大小時就建立一次快照 。使用 寫時複製 技術來提升效率 。

上圖爲快照內容示例,可見快照中包含了:

  • 狀態機狀態(以數據庫舉例,則快照記錄的是數據庫中的表)
  • 最後被包含的 index :被快照取代的最後一個 log entryindex
  • 最後被包含的 term:被快照取代的最後一個 log entryterm

保存 last included indexlast included term 是爲了支持快照後複製第一個 log entryAppendEntries RPC 的一致性檢查 。

爲支持集羣成員更新,快照也將最後一次配置做爲最後一個條目存下來 。

一般由每一個服務器獨立地建立快照,但 leader 會偶爾發送快照給一些落後的 follower 。這一般發生在當 leader 已經丟棄了下一條須要發送給 followerlog entry 時 。

leader 使用 InstallSnapShot RPC 來發送快照給落後的 follower


7. 客戶端交互

客戶端交互過程

  • client 啓動時,會隨機挑選一個服務器進行通訊

  • 若該服務器是 follower ,則拒絕請求並提供它最近接收到的 leader 的信息給 client

  • client 發送請求給 leader

  • leader 已崩潰,client 請求會超時,以後再隨機挑選服務器進行通訊

Raft 可能接收到一條命令屢次client 對於每一條命令都賦予一個惟一序列號,狀態機追蹤每條命令的序列號以及相應的響應,若序列號已被執行,則當即返回響應結果,再也不重複執行。

只讀的操做能夠直接處理而無需記錄日誌 ,可是爲防止髒讀( leader 響應客戶端時可能已被做廢 ),須要有兩個保證措施:

  • leader 在任期開始時提交一個空的 log entry 以肯定日誌中的已有數據是已提交的( 根據領導人徹底特性 )
  • leader 在處理只讀的請求前檢查本身是否已被做廢
    • 能夠在處理只讀請求前發送心跳包檢測本身是否已被做廢
    • 也可使用 lease 機制,在 leader 收到 AppendEntries RPC 的多數回覆後的一段時間內,它能夠響應 client 的只讀請求

8. 集羣成員變化

8.1 共同一致

集羣的 配置 能夠被改變,如替換宕機的機器、加入新機器或改變複製級別 。

配置轉換期間,整個集羣會被分爲兩個獨立的羣體,在同一個時間點,兩個不一樣的 leader 可能在同一個 term 被選舉成功,一個經過舊的配置,一個經過新的配置 。

Raft 引入了一種過渡的配置 —— 共同一致 ,它容許服務器在不一樣時間轉換配置、可讓集羣在轉換配置過程當中依然響應客戶端請求 。

共同一致是新配置和老配置的結合:

  • log entry 被複制給集羣中新、老配置的全部服務器
  • 新、舊配置的服務器均可以成爲 leader
  • 達成一致( 針對選舉和提交 )須要分別在兩種配置上得到大多數的支持

8.2 配置轉換過程

保證配置變化安全性的關鍵 是:防止 \(C_{old}\)\(C_{new}\) 同時作出決定( 即同時選舉出各自的 leader )。

集羣配置在複製日誌中以特殊的 log entry 來存儲 。下文中的 某配置作單方面決定 指的是在該配置內部選出 leader

  • leader 接收到改變配置從 \(C_{old}\)\(C_{new}\) 的請求,初始只容許 \(C_{old}\) 作單方面決定
  • leader\(C_{o,n}\) log entry 寫入日誌當中,並向其餘服務器中複製這個 log entry ( 服務器老是使用最新的配置,不管對應 entry 是否已提交 )
  • \(C_{o,n}\) log entry 提交之前,若 leader 崩潰從新選舉,新 leader 多是 \(C_{old}\) 也多是 \(C_{o,n}\)(取決於新 leader 是否含有此 $C_{o,n} $ log entry ) 。這一階段, \(C_{new}\) 不被容許作單方面的決定。
  • \(C_{o,n}\) log entry 提交以後( \(C_{old}\)\(C_{new}\) 中大多數都有該 entry ),若從新選舉,只有 \(C_{o,n}\) 的服務器可被選爲 leader 。——這一階段,由於 \(C_{old}\)\(C_{new}\) 各自的大多數都處於 \(C_{o,n}\),因此 \(C_{old}\)\(C_{new}\) 都沒法作單方面的決定 。
  • 此時,leader 能夠開始在日誌中寫入 \(C_{new}\) log entry 並將其複製到 \(C_{new}\) 的服務器。
  • \(C_{new}\) log entry 提交以前,若從新選舉,新 leader 多是 \(C_{new}\) 也多是 \(C_{o,n}\) 。——這一階段,\(C_{new}\) 能夠作出單方面決定 。
  • \(C_{new}\) log entry 提交以後,配置改變完成。——這一階段,\(C_{new}\) 能夠作出單方面決定 。

能夠看到,整個過程當中,\(C_{old}\)\(C_{new}\) 都不會在同時作出決定(即在同時選出各自的 leader ),所以配置轉換過程是安全的 。

示例

\(C_{old}\)S1S2S3\(C_{new}\)S4S5S6

  • 開始只容許 \(C_{old}\) 中有 leader
  • S1S2S4S5 被寫入 \(C_{o,n}\) log entry 後, \(C_{o,n}\) log entry 變爲已提交,此時 \(C_{old}\)\(C_{new}\) 中的大多數都已經是 \(C_{o,n}\) ,它們沒法各自選出 leader
  • 此後 leader\(C_{new}\) log entry 開始複製到 S4S5S6
  • \(C_{new}\) log entry 提交後,S1S2S3 退出集羣,若 leader 是三者之一,則退位,在 S4S5S6 中從新選舉
  • 配置轉換完成

事實上,實際 Raft 的配置轉換實現通常都採用 Single Cluser MemberShip Change 機制,這種機制在 Diego博士論文 中有介紹 。關於論文中敘述的使用共同一致的成員變動方法,Diego 建議僅在學術中討論它,實際實現使用 Single Cluster MemberShip Change 更合適。至於該機制的內容,以後抽空看了再補充吧 /(ㄒoㄒ)/~~


9. Raft 壓縮總結

相關文章
相關標籤/搜索