【轉】Raft 理論基礎

 

原文: https://www.zhihu.com/people/chapin666/posts?page=3

-----------------

 

Raft 理論基礎

chapin666
Talk is cheap, show me the code.
 
32 人贊同了該文章
 
Raft 理論基礎

一、從“拜占庭將軍問題”開始

1.1 問題描述

拜占庭將軍問題(Byzantine failures),是由萊斯利·蘭伯特提出的點對點通信中的基本問題。問題內容如下:

拜占庭位於如今的土耳其的伊斯坦布爾,是東羅馬帝國的首都。由於當時拜占庭羅馬帝國國土遼闊,爲了達到防禦目的,每個軍隊都分隔很遠,將軍與將軍之間只能靠信差傳消息。 在戰爭的時候,拜占庭軍隊內所有將軍和副官必須達成一致的共識,決定是否有贏的機會纔去攻打敵人的陣營。但是,在軍隊內有可能存有叛徒和敵軍的間諜,左右將軍們的決定又擾亂整體軍隊的秩序。在進行共識時,結果並不代表大多數人的意見。這時候,在已知有成員謀反的情況下,其餘忠誠的將軍在不受叛徒的影響下如何達成一致的協議,拜占庭問題就此形成。

問題:如果這其中有人叛變,傳遞錯誤的信息,那麼怎麼同時進攻來取得勝利呢?

1.2 “拜占庭將軍問題” 化簡

拜占庭將軍問題是分佈式領域最複雜、最嚴格的容錯模型。但在日常工作中使用的分佈式系統面對的問題不會那麼複雜,更多的是計算機故障掛掉了,或者網絡通信問題而沒法傳遞信息,這種情況不考慮計算機之間互相發送惡意信息,極大簡化了系統對容錯的要求,最主要的是達到一致性。

所以將拜占庭將軍問題根據常見的工作問題進行化簡:假設將軍中沒有叛軍,信使的信息可靠但有可能被暗殺的情況下,將軍們如何達成一致性決定?

1.3 Raft 如何解決簡化版“拜占庭將軍問題”

對於這個簡化後的問題,有許多解決方案,第一個被證明的共識算法是 Paxos,由拜占庭將軍問題的作者 Leslie Lamport 在1990年提出,最初以論文難懂而出名,後來這哥們在2001重新發了一篇簡單版的論文 Paxos Made Simple,然而還是挺難懂的。

因爲 Paxos 難懂,難實現,所以斯坦福大學的教授在2014年發表了新的分佈式協議 Raft。與 Paxos 相比,Raft 有着基本相同運行效率,但是更容易理解,也更容易被用在系統開發上。

Raft 解決方案是 在所有將軍中選出一個大將軍,所有決定由大將軍來做。大致流程如下:

  1. 比如說現在一共有3個將軍 A, B, C,每個將軍都有一個隨機時間的倒計時器(假設將軍A的隨機時間最短)。倒計時一結束,將軍A就會把自己當成大將軍候選人,然後派信使去問其他幾個將軍,能不能選我爲總將軍
  2. 假設現在將軍A倒計時結束了,他派信使傳遞選舉投票的信息給將軍B和C
  3. 當將軍B和C收到信使傳遞的選舉信息後,如果將軍B和C還沒把自己當成候選人(倒計時還沒有結束),並且沒有把選舉票投給其他人,他們把票投給將軍A
  4. 信使回到將軍A後,將軍A知道自己收到了足夠的票數,成爲了大將軍
  5. 在這之後,是否要進攻就由大將軍決定,然後派信使去通知另外兩個將軍(將軍B和C)
  6. 如果在一段時間後還沒有收到回覆(可能信使被暗殺),那就再重派一個信使,直到收到回覆

二、Raft基礎

2.1 Raft 定義

首先,Raft是一種“算法”;其次,Raft 是一種爲了管理“複製日誌”算法;最後,Raft 是一種爲了管理複製日誌“一致性”算法。

那麼問題來了,什麼是一致性?

一致性是分佈式系統容錯的基本問題。一組機器像一個整體一樣工作,即使其中小半部分機器(不大於N/2)出現故障也能夠繼續工作下去, 一旦他們就狀態做出決定,該決定就是最終決定。 例如,即使2臺服務器發生故障,5臺服務器的集羣也可以繼續運行。 如果更多服務器失敗,它們將停止進展(但永遠不會返回錯誤的結果)

2.2 Raft 三種角色

根據 “拜占庭將軍問題” ,我們可以提取出三種狀態的角色:

1. 追隨者:將軍B和C願意投票給將軍A, 將軍B和C成爲將軍A的跟隨者

2. 候選者:將軍A倒計時結束,成爲大將軍候選者

3. 大將軍:將軍A收到大多數將軍的投票後,成爲大將軍

在包含若干節點Raft集羣中,其實也存在着相似的角色:Leader、Candidate、Follower。每種角色負責的任務也不一樣,正常情況下,集羣中的節點只存在 Leader 與 Follower 兩種角色。

1. Leader(領導者):處理所有客戶端交互,日誌複製等,一般一次只有一個Leader;

2. Follower(追隨者):響應 Leader 的日誌同步請求,響應 Candidate 的邀票請求,以及把客戶端請求到 Follower 的事務轉發(重定向)給 Leader;

3. Candidate(候選者):負責選舉投票,集羣剛啓動或者 Leader 宕機時,角色爲 Follower 的節點將轉爲 Candidate 併發起選舉,選舉勝出(獲得超過半數節點的投票)後,從 Candidate 轉爲 Leader 角色;

2.3 Raft 算法問題分解

根據上面的介紹,我們知道通常Raft集羣中只有一個Leader,其它節點都是Follower。Follower都是被動的:他們不會發送任何請求,只是簡單的響應來自Leader或者Candidate的請求。Leader負責處理所有的客戶端請求(如果一個客戶端和Follower聯繫,Follower會把請求重定向給Leader)。爲了簡化邏輯和實現,Raft將一致性問題分解成三個獨立的子問題:

1. Leader election:當leader宕機或者集羣創建時,需要選舉一個新的Leader

2. Log replication:Leader接收來自客戶端的請求並將其以日誌的形式複製到集羣中的其它節點,並且強制要求其它節點的日誌和自己保持一致

3. Safety:如果有任何節點已經應用了一個確定的日誌條目到它的狀態機中,那麼其它服務節點不能在同一個日誌索引位置應用一個不用的指令

三、Raft 算法原理

上面講了這麼多,其實都是伏筆 。接下來,本文的核心知識點來了。

3.1 Raft 角色選舉

根據 Raft 協議,一個應用 Raft 協議的集羣在剛啓動時,所有節點狀態都是Follower態,由於沒有Leader,Follower 無法與 Leader 保持心跳(heart beat),Follower等待心跳超時(每個Follower的心跳超時時間不一樣),Followers 會認爲 Leader 已經 down 掉。最先超時的Follower進而轉爲 Candidate 狀態,然後,Candidate 將向集羣中的其它節點請求投票,同意自己升級爲 Leader,如果 Candidate 收到超過半數節點的投票(N/2+1),它將獲勝成爲 Leader。

角色選舉詳細流程如下:

第一階段:都是 Follower 狀態

一個應用Raft協議的集羣在剛開始啓動時(或者 Leader 宕機重啓時),所有的節點都是 Follower 狀態,初始任期(Term,即某次選舉的唯一標識)都是0。同時啓動選舉定時器,每個節點的選舉定時器都不一致且都在100~500ms之間(避免同時發起選舉)。

第二階段:從 Follower 狀態轉換爲Candidate,併發起投票

由於沒有 Leader,Followers 無法與 Leader 保持心跳(heart beat),節點啓動後在一個選舉定時器週期內未收到心跳和投票請求,則狀態轉變爲 Candidate 狀態、Term 自增,並向集羣中所有節點發送投票請求並且重置選舉定時器。

注意:每個節點選舉定時器超時時間都在 100 ~ 500 ms之內,且不一致。因此,可以避免所有的Follower同時轉化爲 Candidate狀態,換言之,最先轉爲 Candidate 併發起投票請求的節點將具有成爲 Leader 的先發優勢。

第三階段:投票策略

Follower 節點收到投票請求後會根據以下情況決定是否接受投票請求:

  • 請求節點的 Term 大於自己的 Term,且自己尚未投票給其它節點,則接受請求,把票投給 Candidate 節點;
  • 請求節點的 Term 小於自己的 Term,且自己尚未投票,則拒絕請求,將票投給自己。

第四階段:Candidate 轉換爲 Leader

經過一輪選舉後,正常情況,會有一個 Candidate 節點收到超過半數(N/2+1)其它節點的投票,那麼它將勝出並升級爲 Leader 節點,然後定時發送心跳給其它節點,其它節點會轉化爲 Follower 節點並與 Leader 保持同步,如此,本輪選舉結束。如果一輪選舉中,Candidate 節點收到的投票沒有超過半數,那麼將進行下一輪選舉。

3.2 Raft 日誌同步

一個 Raft 集羣中只有 Leader 節點能夠處理客戶端的請求(如果客戶端的請求發到了 Follower 節點,Follower 將會把請求重定向到 Leader),客戶端的每一個請求都包含一條被複制到狀態機執行的指令。Leader 把這條指令作爲一條新的日誌條目(Entry)附加到日誌中去,然後並行的將附加條目發送給 Followers,讓它們複製這條日誌條目。當這條日誌條目被 Followers 安全的複製,Leader 會應用這條日誌條目到它的狀態機中,然後把執行的結果返回給客戶端。如果 Follower 崩潰或者運行緩慢,再或者是網絡丟包,Leader 會不斷的重複嘗試附加日誌條目(儘管已經回覆了客戶端)直到所有的 Follower 最終都存儲了所有的日誌條目,確保強一致性。

日誌複製詳細流程如下:

第一階段:客戶端請求提交到 Leader

Leader 收到客戶端請求:如存儲一個數據:5;Leader 收到請求後,會將它作爲日誌條目(Entry)寫入本地日誌中。此時該 Entry 是未提交狀態(uncommitted),Leader並不會更新本地數據,因此它是不可讀的。

第二階段:Leader 將 Entry 發送到其它Follower

Leader 與 Followers 之間保持心跳聯繫,跟心跳 Leader 將追加的 Entry(AppendEntries) 並行的發送到其它 Follower 節點,並讓它們複製這條日誌條目,這一過程我們稱爲:複製(Replication)。

  1. 爲什麼 Leader 向 Follower 發送的 Entry 是 AppendEntries 呢?

因爲 Leader 與 Follower 的心跳是週期性的,而一個週期 Leader 可能接收到客戶端的多個請求,因此,隨 心跳向 Followers 發送的大概率是多個 Entry,即 AppendEntries。在本例中爲了簡單,只有一條請求,自然 只有一個 Enrety。

2. Leader 向 Followers 發送的不僅僅是追加的 Entry (AppendEntries)

在發送追加日誌條目的時候,Leader 會把新日誌條目之前的條目索引(前一個日誌條目)位置(prevLogIndex)和Leader任期號(term)包含在裏邊。如果 Follower 在它的日誌中找不到包含相同索引位置和任期號的條目,那麼它會拒接這個新的日誌條目。因爲出現這種情況說明 Follower 和 Leader 是不一致的。

3. 如何解決 Leader 和 Follower 不一致的問題?

在正常情況下,Leader 和 Follower 的日誌保持一致,所以追加日誌的一致性從來不會失敗。然後,Leader 和 Follower 的一系列崩潰情況下會使它們的日誌處於不一致的狀態。Follower 可能會丟失一些在新的 Leader 中有的日誌條目,它也可能擁有一些 Leader 沒有的日誌條目,或者兩者都有發生。丟失或者多出的日誌條目可能會持續多個任期。

要使 Follower 的日誌與 Leader 恢復一致,Leader 必須找到最後兩者達成一致的地方,然後刪除從那個節點之後的所有日誌,發送自己的日誌給 Follower。所有的這些操作都在進行附加日誌一致性檢查時完成。

Leader 節點針對每個 Follower 節點維護了一個 nextIndex,這表示下一個需要發送給 Follower 的日誌條目的索引地址。當一個 Leader 剛獲得權力的時候,它初始化所有的 nextIndex 值爲自己的最後一條日誌的 index + 1。如果一個 Follower 日誌和 Leader 不一致,那麼在下一次附加日誌的時候就會檢查失敗。在被 Follower 拒絕之後,Leader 就會減小該 Follower 對應的 nextIndex 值並進行重試(即回溯)。

最終 nextIndex 會在某個位置使得 Leader 和 Follower 的日誌達成一致。當這種情況發生,附加日誌就會成功,這時就會把 Follower 衝突的日誌條目全部刪除並且附加上 Leader 的日誌。一旦附加成功,那麼 Follower 的日誌就會和 Leader 保持一致,並且在接下來的任期裏一致繼續保持。

第三階段:Leader 等待 Followers 迴應

Followers 接收到 Leader 發來的複製請求後,有兩種可能的迴應:

  • 寫入本地日誌,返回 Success
  • 一致性檢查失敗,拒絕寫入,返回 false。原因和解決辦法上面已經詳細說明。

注:此時該 Entry 的狀態也是未提交(uncommitted)。完成上述步驟後,Followers 會向 Leader 發出迴應 - success,當 Leader 收到大多數 Followers 的迴應後,會將第一階段寫入的 Entry 標記爲提交狀態(committed),並把這條日誌條目應用到它的狀態機中。

第四階段:Leader迴應客戶端

完成前三個階段後,Leader 會迴應客戶端 - OK,寫操作成功。

第五階段:Leader 通知 Followers Entry 已提交

Leader 迴應客戶端後,將隨着下一個心跳通知 Followers,Followers 收到通知後也會將 Entry 標記爲提交狀態。至此,Raft 集羣超過半數節點已經達到一致狀態,可以確保強一致性。需要注意的是,由於網絡、性能、故障等各種原因導致的“反應慢”、“不一致”等問題的節點,也會最終與 Leader 達成一致。

3.3 Raft 安全性保證

前面的章節裏描述了 Raft 算法是如何選舉 Leader 和 日誌複製。然而,到目前爲止描述的機制並不能充分保證每一個狀態機會按照相同的順序執行相同的指令。例如:一個 Follower 可能處於不可用的狀態,同時 Leader 已經提交了若干的日誌條目;然後這個 Follower 恢復(尚未與 Leader 達成一致)而 Leader 故障,如果該 Follower 被選舉爲 Leader 並且覆蓋這些日誌條目,就會出現問題:不同的狀態機執行不同的指令序列。

鑑於此,在 Leader 選舉的時候需要增加一些限制來完善 Raft 算法。這些限制可保證任何的 Leader 對於給定的任期號(Term),都擁有之前任期的所有被提交的日誌條目(所謂 Leader 的完整特性)。

3.3.1 選舉限制

對於所有基於 Leader 機制一致性算法,Leader 都必須存儲所有已經提交的日誌條目。爲了保障這一點,Raft 使用了一種簡單而有效的辦法,以保證之前任期號中已提交的日誌條目在選舉的時候都會出現在新的Leader中。換言之,日誌條目的傳送是單向的,只從 Leader 傳給 Follower, 並且 Leader 從不會覆蓋自身本地日誌中已經存在的條目。

Raft 使用投票的方式來阻止一個 Candidate 贏得選舉,除非這個 Candidate 包含了所有已經提交的日誌條目。Candiate 爲了贏得選舉必須聯繫集羣中的大部分節點,這意味着每一個已經提交的日誌條目都在這些服務器節點中肯定存在於至少一個節點上。如果 Candidate 的日誌至少和大多數的服務器節點一樣新,那麼它一定持有了所有已經提交的日誌條目。投票請求的限制:請求中包含了 Candidate 的日誌信息,然後投票人會拒絕那些日誌沒有自己日誌新的投票請求。

Raft 通過比較兩份日誌中最後一條日誌條目的索引值和任期號,確定誰的日誌比較新。如果兩份日誌最後的條目和任期號不同,那麼任期號大的日誌更加新一些。如果兩份日誌最後的任期號相同,那麼日誌比較長的那個就更加新。

3.3.2 提交之前任期內的日誌條目

Leader 知道一條當前任期內的日誌記錄是可以被提交的,只要它被複制到了大多數 Follower 節點上。如果一個Leader 在提交日誌條目之前崩潰了,繼任的 Leader 會繼續嘗試複製這條日誌記錄。然而,一個 Leader 並不能斷定之前任期裏的日誌條目被保存到大多數 Follower 上就一定已經提交了。很明顯,從日誌複製的過程可以看出。

鑑於上述情況,Raft 算法不會通過計算副本數的方式去提交一個之前任期內的日誌條目。只有 Leader 當前任期裏的日誌條目通過計算副本數目可以被提交;一旦當前任期的日誌條目以這種方式提交,由於日誌匹配特性,之前的日誌條目也都會被間接提交。在某些情況下, Leader 可以安全的知道一個老的日誌條目是否已經被提交(只需判斷該條目是否存儲到所有的節點上),但是 Raft 爲了簡化問題使用一種更加保守的方式。

當 Leader 複製之前任期裏的日誌時,Raft 會爲所有的日誌保留原始任期號,這在提交規則上產生了額外的複雜性。但是,這種策略更加容易辨別出日誌,因爲它可以隨着時間和日誌變化對日誌維護着同一個任期號。此外,該策略使得新 Leader 只需要發送較少的日誌條目。

四、擴展與總結

4.1 誰在使用 Raft

使用 Raft 協議的產品可多了,如 Kubernetes,Docker Swarm,Cloud Foundry Diego,CockroachDB,TiDB, etcd,Project Calico,Flannel 等等。只要你的服務有一致性高可用需求,都可以考慮使用 Raft 協議

4.2 Raft 小結

Raft算法具備強一致、高可靠、高可用等優點,具體體現在:

強一致性:雖然所有節點的數據並非實時一致,但 Raft 算法保證 Leader 節點的數據最全,同時所有請求都由Leader 處理,所以在客戶端角度看是強一致性的。

高可靠性:Raft算法保證了Committed的日誌不會被修改,狀態機只應用 Committed 的日誌,所以當客戶端收到請求成功即代表數據不再改變。Committed 日誌在大多數節點上冗餘存儲,少於一半的磁盤故障數據不會丟失。

高可用性:從Raft算法原理可以看出,選舉和日誌同步都只需要大多數的節點正常互聯即可,所以少量節點故障或網絡異常不會影響系統的可用性。即使 Leader 故障,在選舉超時到期後,集羣自發選舉新 Leader,無需人工干預,不可用時間極小。但 Leader 故障時存在重複數據問題,需要業務去重或冪等性保證。

高性能:與必須將數據寫到所有節點才能返回客戶端成功的算法相比,Raft 算法只需要大多數節點成功即可,少量節點處理緩慢不會延緩整體系統運行。

 

參考文獻:

尋找一種易於理解的一致性算法:

thesecretlivesofdata: 

raftscope:

raft: 

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