Raft算法詳解

轉自 https://zhuanlan.zhihu.com/p/32052223

Paxos算法詳解一文講述了晦澀難懂的Paxos算法,以可理解性和易於實現爲目標的Raft算法極大的幫助了我們的理解,推動了分佈式一致性算法的工程應用,本文試圖以通俗易懂的語言講述Raft算法。

一、Raft算法概述

不同於Paxos算法直接從分佈式一致性問題出發推導出來,Raft算法則是從多副本狀態機的角度提出,用於管理多副本狀態機的日誌複製。Raft實現了和Paxos相同的功能,它將一致性分解爲多個子問題:Leader選舉(Leader election)、日誌同步(Log replication)、安全性(Safety)、日誌壓縮(Log compaction)、成員變更(Membership change)等。同時,Raft算法使用了更強的假設來減少了需要考慮的狀態,使之變的易於理解和實現。

Raft將系統中的角色分爲領導者(Leader)、跟從者(Follower)和候選人(Candidate):

  • Leader:接受客戶端請求,並向Follower同步請求日誌,當日志同步到大多數節點上後告訴Follower提交日誌。
  • Follower:接受並持久化Leader同步的日誌,在Leader告之日誌可以提交之後,提交日誌。
  • Candidate:Leader選舉過程中的臨時角色。

Raft算法角色

Raft要求系統在任意時刻最多隻有一個Leader,正常工作期間只有Leader和Followers。

Raft算法角色狀態轉換如下:

Raft算法角色狀態轉換

Follower只響應其他服務器的請求。如果Follower超時沒有收到Leader的消息,它會成爲一個Candidate並且開始一次Leader選舉。收到大多數服務器投票的Candidate會成爲新的Leader。Leader在宕機之前會一直保持Leader的狀態。

Raft算法將時間分爲一個個的任期(term),每一個term的開始都是Leader選舉。在成功選舉Leader之後,Leader會在整個term內管理整個集羣。如果Leader選舉失敗,該term就會因爲沒有Leader而結束。

 

二、Leader選舉

Raft 使用心跳(heartbeat)觸發Leader選舉。當服務器啓動時,初始化爲Follower。Leader向所有Followers週期性發送heartbeat。如果Follower在選舉超時時間內沒有收到Leader的heartbeat,就會等待一段隨機的時間後發起一次Leader選舉。

Follower將其當前term加一然後轉換爲Candidate。它首先給自己投票並且給集羣中的其他服務器發送 RequestVote RPC (RPC細節參見八、Raft算法總結)。結果有以下三種情況:

  • 贏得了多數的選票,成功選舉爲Leader;
  • 收到了Leader的消息,表示有其它服務器已經搶先當選了Leader;
  • 沒有服務器贏得多數的選票,Leader選舉失敗,等待選舉時間超時後發起下一次選舉。

Leader選舉過程

選舉出Leader後,Leader通過定期向所有Followers發送心跳信息維持其統治。若Follower一段時間未收到Leader的心跳則認爲Leader可能已經掛了,再次發起Leader選舉過程。

Raft保證選舉出的Leader上一定具有最新的已提交的日誌,這一點將在四、安全性中說明。

 

三、日誌同步

Leader選出後,就開始接收客戶端的請求。Leader把請求作爲日誌條目(Log entries)加入到它的日誌中,然後並行的向其他服務器發起 AppendEntries RPC (RPC細節參見八、Raft算法總結)複製日誌條目。當這條日誌被複制到大多數服務器上,Leader將這條日誌應用到它的狀態機並向客戶端返回執行結果。

Raft日誌同步過程

某些Followers可能沒有成功的複製日誌,Leader會無限的重試 AppendEntries RPC直到所有的Followers最終存儲了所有的日誌條目。

日誌由有序編號(log index)的日誌條目組成。每個日誌條目包含它被創建時的任期號(term),和用於狀態機執行的命令。如果一個日誌條目被複制到大多數服務器上,就被認爲可以提交(commit)了。

Raft日誌

Raft日誌同步保證如下兩點:

  • 如果不同日誌中的兩個條目有着相同的索引和任期號,則它們所存儲的命令是相同的。
  • 如果不同日誌中的兩個條目有着相同的索引和任期號,則它們之前的所有條目都是完全一樣的。

第一條特性源於Leader在一個term內在給定的一個log index最多創建一條日誌條目,同時該條目在日誌中的位置也從來不會改變。

第二條特性源於 AppendEntries 的一個簡單的一致性檢查。當發送一個 AppendEntries RPC 時,Leader會把新日誌條目緊接着之前的條目的log index和term都包含在裏面。如果Follower沒有在它的日誌中找到log index和term都相同的日誌,它就會拒絕新的日誌條目。

一般情況下,Leader和Followers的日誌保持一致,因此 AppendEntries 一致性檢查通常不會失敗。然而,Leader崩潰可能會導致日誌不一致:舊的Leader可能沒有完全複製完日誌中的所有條目。

Leader和Followers上日誌不一致

上圖闡述了一些Followers可能和新的Leader日誌不同的情況。一個Follower可能會丟失掉Leader上的一些條目,也有可能包含一些Leader沒有的條目,也有可能兩者都會發生。丟失的或者多出來的條目可能會持續多個任期。

Leader通過強制Followers複製它的日誌來處理日誌的不一致,Followers上的不一致的日誌會被Leader的日誌覆蓋。

Leader爲了使Followers的日誌同自己的一致,Leader需要找到Followers同它的日誌一致的地方,然後覆蓋Followers在該位置之後的條目。

Leader會從後往前試,每次AppendEntries失敗後嘗試前一個日誌條目,直到成功找到每個Follower的日誌一致位點,然後向後逐條覆蓋Followers在該位置之後的條目。

 

四、安全性

Raft增加了如下兩條限制以保證安全性:

  • 擁有最新的已提交的log entry的Follower纔有資格成爲Leader。

這個保證是在RequestVote RPC中做的,Candidate在發送RequestVote RPC時,要帶上自己的最後一條日誌的term和log index,其他節點收到消息時,如果發現自己的日誌比請求中攜帶的更新,則拒絕投票。日誌比較的原則是,如果本地的最後一條log entry的term更大,則term大的更新,如果term一樣大,則log index更大的更新。

  • Leader只能推進commit index來提交當前term的已經複製到大多數服務器上的日誌,舊term日誌的提交要等到提交當前term的日誌來間接提交(log index 小於 commit index的日誌被間接提交)。

之所以要這樣,是因爲可能會出現已提交的日誌又被覆蓋的情況:

已提交的日誌被覆蓋

在階段a,term爲2,S1是Leader,且S1寫入日誌(term, index)爲(2, 2),並且日誌被同步寫入了S2;

在階段b,S1離線,觸發一次新的選主,此時S5被選爲新的Leader,此時系統term爲3,且寫入了日誌(term, index)爲(3, 2);

S5尚未將日誌推送到Followers就離線了,進而觸發了一次新的選主,而之前離線的S1經過重新上線後被選中變成Leader,此時系統term爲4,此時S1會將自己的日誌同步到Followers,按照上圖就是將日誌(2, 2)同步到了S3,而此時由於該日誌已經被同步到了多數節點(S1, S2, S3),因此,此時日誌(2,2)可以被提交了。;

在階段d,S1又下線了,觸發一次選主,而S5有可能被選爲新的Leader(這是因爲S5可以滿足作爲主的一切條件:1. term = 5 > 4,2. 最新的日誌爲(3,2),比大多數節點(如S2/S3/S4的日誌都新),然後S5會將自己的日誌更新到Followers,於是S2、S3中已經被提交的日誌(2,2)被截斷了。

增加上述限制後,即使日誌(2,2)已經被大多數節點(S1、S2、S3)確認了,但是它不能被提交,因爲它是來自之前term(2)的日誌,直到S1在當前term(4)產生的日誌(4, 4)被大多數Followers確認,S1方可提交日誌(4,4)這條日誌,當然,根據Raft定義,(4,4)之前的所有日誌也會被提交。此時即使S1再下線,重新選主時S5不可能成爲Leader,因爲它沒有包含大多數節點已經擁有的日誌(4,4)。

 

五、日誌壓縮

在實際的系統中,不能讓日誌無限增長,否則系統重啓時需要花很長的時間進行回放,從而影響可用性。Raft採用對整個系統進行snapshot來解決,snapshot之前的日誌都可以丟棄。

每個副本獨立的對自己的系統狀態進行snapshot,並且只能對已經提交的日誌記錄進行snapshot。

Snapshot中包含以下內容:

  • 日誌元數據。最後一條已提交的 log entry的 log index和term。這兩個值在snapshot之後的第一條log entry的AppendEntries RPC的完整性檢查的時候會被用上。
  • 系統當前狀態。

當Leader要發給某個日誌落後太多的Follower的log entry被丟棄,Leader會將snapshot發給Follower。或者當新加進一臺機器時,也會發送snapshot給它。發送snapshot使用InstalledSnapshot RPC(RPC細節參見八、Raft算法總結)。

做snapshot既不要做的太頻繁,否則消耗磁盤帶寬, 也不要做的太不頻繁,否則一旦節點重啓需要回放大量日誌,影響可用性。推薦當日志達到某個固定的大小做一次snapshot。

做一次snapshot可能耗時過長,會影響正常日誌同步。可以通過使用copy-on-write技術避免snapshot過程影響正常日誌同步。

 

六、成員變更

成員變更是在集羣運行過程中副本發生變化,如增加/減少副本數、節點替換等。

成員變更也是一個分佈式一致性問題,既所有服務器對新成員達成一致。但是成員變更又有其特殊性,因爲在成員變更的一致性達成的過程中,參與投票的進程會發生變化。

如果將成員變更當成一般的一致性問題,直接向Leader發送成員變更請求,Leader複製成員變更日誌,達成多數派之後提交,各服務器提交成員變更日誌後從舊成員配置(Cold)切換到新成員配置(Cnew)。

因爲各個服務器提交成員變更日誌的時刻可能不同,造成各個服務器從舊成員配置(Cold)切換到新成員配置(Cnew)的時刻不同。

成員變更不能影響服務的可用性,但是成員變更過程的某一時刻,可能出現在Cold和Cnew中同時存在兩個不相交的多數派,進而可能選出兩個Leader,形成不同的決議,破壞安全性。

成員變更的某一時刻Cold和Cnew中同時存在兩個不相交的多數派

由於成員變更的這一特殊性,成員變更不能當成一般的一致性問題去解決。

爲了解決這一問題,Raft提出了兩階段的成員變更方法。集羣先從舊成員配置Cold切換到一個過渡成員配置,稱爲共同一致(joint consensus),共同一致是舊成員配置Cold和新成員配置Cnew的組合Cold U Cnew,一旦共同一致Cold U Cnew被提交,系統再切換到新成員配置Cnew。

Raft兩階段成員變更

Raft兩階段成員變更過程如下:

  1. Leader收到成員變更請求從Cold切成Cnew;
  2. Leader在本地生成一個新的log entry,其內容是Cold∪Cnew,代表當前時刻新舊成員配置共存,寫入本地日誌,同時將該log entry複製至Cold∪Cnew中的所有副本。在此之後新的日誌同步需要保證得到Cold和Cnew兩個多數派的確認;
  3. Follower收到Cold∪Cnew的log entry後更新本地日誌,並且此時就以該配置作爲自己的成員配置;
  4. 如果Cold和Cnew中的兩個多數派確認了Cold U Cnew這條日誌,Leader就提交這條log entry;
  5. 接下來Leader生成一條新的log entry,其內容是新成員配置Cnew,同樣將該log entry寫入本地日誌,同時複製到Follower上;
  6. Follower收到新成員配置Cnew後,將其寫入日誌,並且從此刻起,就以該配置作爲自己的成員配置,並且如果發現自己不在Cnew這個成員配置中會自動退出;
  7. Leader收到Cnew的多數派確認後,表示成員變更成功,後續的日誌只要得到Cnew多數派確認即可。Leader給客戶端回覆成員變更執行成功。

異常分析:

  • 如果Leader的Cold U Cnew尚未推送到Follower,Leader就掛了,此後選出的新Leader並不包含這條日誌,此時新Leader依然使用Cold作爲自己的成員配置。
  • 如果Leader的Cold U Cnew推送到大部分的Follower後就掛了,此後選出的新Leader可能是Cold也可能是Cnew中的某個Follower。
  • 如果Leader在推送Cnew配置的過程中掛了,那麼同樣,新選出來的Leader可能是Cold也可能是Cnew中的某一個,此後客戶端繼續執行一次改變配置的命令即可。
  • 如果大多數的Follower確認了Cnew這個消息後,那麼接下來即使Leader掛了,新選出來的Leader肯定位於Cnew中。

兩階段成員變更比較通用且容易理解,但是實現比較複雜,同時兩階段的變更協議也會在一定程度上影響變更過程中的服務可用性,因此我們期望增強成員變更的限制,以簡化操作流程。

兩階段成員變更,之所以分爲兩個階段,是因爲對Cold與Cnew的關係沒有做任何假設,爲了避免Cold和Cnew各自形成不相交的多數派選出兩個Leader,才引入了兩階段方案。

如果增強成員變更的限制,假設Cold與Cnew任意的多數派交集不爲空,這兩個成員配置就無法各自形成多數派,那麼成員變更方案就可能簡化爲一階段。

那麼如何限制Cold與Cnew,使之任意的多數派交集不爲空呢?方法就是每次成員變更只允許增加或刪除一個成員。

可從數學上嚴格證明,只要每次只允許增加或刪除一個成員,Cold與Cnew不可能形成兩個不相交的多數派。

一階段成員變更:

  • 成員變更限制每次只能增加或刪除一個成員(如果要變更多個成員,連續變更多次)。
  • 成員變更由Leader發起,Cnew得到多數派確認後,返回客戶端成員變更成功。
  • 一次成員變更成功前不允許開始下一次成員變更,因此新任Leader在開始提供服務前要將自己本地保存的最新成員配置重新投票形成多數派確認。
  • Leader只要開始同步新成員配置,即可開始使用新的成員配置進行日誌同步。

 

七、Raft與Multi-Paxos的異同

Raft與Multi-Paxos都是基於領導者的一致性算法,乍一看有很多地方相同,下面總結一下Raft與Multi-Paxos的異同。

Raft與Multi-Paxos中相似的概念:

Raft與Multi-Paxos中相似的概念

Raft與Multi-Paxos的不同:

Raft與Multi-Paxos的不同

 

八、Raft算法總結

Raft算法各節點維護的狀態:

Raft各節點維護的狀態

Leader選舉:

Leader選舉

日誌同步:

日誌同步

Raft狀態機:

Raft狀態機

安裝snapshot:

安裝snapshot

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