Paxos算法和Raft算法---经典的分布式系统一致性问题解决算法
一.Paxos 算法
Paxos 算法誕生于 1990 年,這是一種解決分布式系統一致性的經典算法 。但是,由于 Paxos 算法在國際上被公認的非常難以理解和實現,因此不斷有人嘗試簡化這一算法。到了2013 年才誕生了一個比 Paxos 算法更易理解和實現的分布式一致性算法--Raft算法(下文會有介紹)
Paxos?算法是基于消息傳遞且具有高度容錯特性的一致性算法,是目前公認的解決分布式一致性問題最有效的算法之一,其解決的問題就是在分布式系統中如何就某個值(決議)達成一致?。
在?Paxos?中主要有三個角色,分別為?Proposer提案者、Acceptor表決者、Learner學習者。Paxos?算法和?2PC?一樣,也有兩個階段,分別為?Prepare?和?accept?階段。
1. prepare 階段
- Proposer提案者:負責提出?proposal,每個提案者在提出提案時都會首先獲取到一個?具有全局唯一性的、遞增的提案編號N,即在整個集群中是唯一的編號 N,然后將該編號賦予其要提出的提案,在第一階段是只將提案編號發送給所有的表決者。
- Acceptor表決者:每個表決者在?accept?某提案后,會將該提案編號N記錄在本地,這樣每個表決者中保存的已經被 accept 的提案中會存在一個編號最大的提案,其編號假設為?maxN。每個表決者僅會?accept?編號大于自己本地?maxN?的提案,在批準提案時表決者會將以前接受過的最大編號的提案作為響應反饋給?Proposer?。
下面是?prepare?階段的流程圖,你可以對照著參考一下。
2. accept 階段
當一個提案被?Proposer?提出后,如果?Proposer?收到了超過半數的?Acceptor?的批準(Proposer?本身同意),那么此時?Proposer?會給所有的?Acceptor?發送真正的提案(你可以理解為第一階段為試探),這個時候?Proposer?就會發送提案的內容和提案編號。
表決者收到提案請求后會再次比較本身已經批準過的最大提案編號和該提案編號,如果該提案編號?大于等于?已經批準過的最大提案編號,那么就?accept?該提案(此時執行提案內容但不提交),隨后將情況返回給?Proposer?。如果不滿足則不回應或者返回 NO 。
當?Proposer?收到超過半數的?accept?,那么它這個時候會向所有的?acceptor?發送提案的提交請求。需要注意的是,因為上述僅僅是超過半數的?acceptor?批準執行了該提案內容,其他沒有批準的并沒有執行該提案內容,所以這個時候需要向未批準的?acceptor?發送提案內容和提案編號并讓它無條件執行和提交,而對于前面已經批準過該提案的?acceptor?來說?僅僅需要發送該提案的編號?,讓?acceptor?執行提交就行了。
而如果?Proposer?如果沒有收到超過半數的?accept?那么它將會將?遞增?該?Proposal?的編號,然后?重新進入?Prepare?階段?。
對于?Learner?來說如何去學習?Acceptor?批準的提案內容,這有很多方式,讀者可以自己去了解一下,這里不做過多解釋。
二.Raft算法?
1 背景
當今的數據中心和應用程序在高度動態的環境中運行,為了應對高度動態的環境,它們通過額外的服務器進行橫向擴展,并且根據需求進行擴展和收縮。同時,服務器和網絡故障也很常見。
因此,系統必須在正常操作期間處理服務器的上下線。它們必須對變故做出反應并在幾秒鐘內自動適應;對客戶來說的話,明顯的中斷通常是不可接受的。
幸運的是,分布式共識可以幫助應對這些挑戰。
1.1 拜占庭將軍問題
在介紹共識算法之前,先介紹一個簡化版拜占庭將軍的例子來幫助理解共識算法。
假設多位拜占庭將軍中沒有叛軍,信使的信息可靠但有可能被暗殺的情況下,將軍們如何達成是否要進攻的一致性決定?
解決方案大致可以理解成:先在所有的將軍中選出一個大將軍,用來做出所有的決定。
舉例如下:假如現在一共有 3 個將軍 A,B 和 C,每個將軍都有一個隨機時間的倒計時器,倒計時一結束,這個將軍就把自己當成大將軍候選人,然后派信使傳遞選舉投票的信息給將軍 B 和 C,如果將軍 B 和 C 還沒有把自己當作候選人(自己的倒計時還沒有結束),并且沒有把選舉票投給其他人,它們就會把票投給將軍 A,信使回到將軍 A 時,將軍 A 知道自己收到了足夠的票數,成為大將軍。在有了大將軍之后,是否需要進攻就由大將軍 A 決定,然后再去派信使通知另外兩個將軍,自己已經成為了大將軍。如果一段時間還沒收到將軍 B 和 C 的回復(信使可能會被暗殺),那就再重派一個信使,直到收到回復。
1.2 共識算法
共識是可容錯系統中的一個基本問題:即使面對故障,服務器也可以在共享狀態上達成一致。
共識算法允許一組節點像一個整體一樣一起工作,即使其中的一些節點出現故障也能夠繼續工作下去,其正確性主要是源于復制狀態機的性質:一組Server的狀態機計算相同狀態的副本,即使有一部分的Server宕機了它們仍然能夠繼續運行。
一般通過使用復制日志來實現復制狀態機。每個Server存儲著一份包括命令序列的日志文件,狀態機會按順序執行這些命令。因為每個日志包含相同的命令,并且順序也相同,所以每個狀態機處理相同的命令序列。由于狀態機是確定性的,所以處理相同的狀態,得到相同的輸出。
因此共識算法的工作就是保持復制日志的一致性。服務器上的共識模塊從客戶端接收命令并將它們添加到日志中。它與其他服務器上的共識模塊通信,以確保即使某些服務器發生故障。每個日志最終包含相同順序的請求。一旦命令被正確地復制,它們就被稱為已提交。每個服務器的狀態機按照日志順序處理已提交的命令,并將輸出返回給客戶端,因此,這些服務器形成了一個單一的、高度可靠的狀態機。
適用于實際系統的共識算法通常具有以下特性:
-
安全。確保在非拜占庭條件(也就是上文中提到的簡易版拜占庭)下的安全性,包括網絡延遲、分區、包丟失、復制和重新排序。
-
高可用。只要大多數服務器都是可操作的,并且可以相互通信,也可以與客戶端進行通信,那么這些服務器就可以看作完全功能可用的。因此,一個典型的由五臺服務器組成的集群可以容忍任何兩臺服務器端故障。假設服務器因停止而發生故障;它們稍后可能會從穩定存儲上的狀態中恢復并重新加入集群。
-
一致性不依賴時序。錯誤的時鐘和極端的消息延遲,在最壞的情況下也只會造成可用性問題,而不會產生一致性問題。
-
在集群中大多數服務器響應,命令就可以完成,不會被少數運行緩慢的服務器來影響整體系統性能。
2 基礎
2.1 節點類型
一個 Raft 集群包括若干服務器,以典型的 5 服務器集群舉例。在任意的時間,每個服務器一定會處于以下三個狀態中的一個:
- Leader:負責發起心跳,響應客戶端,創建日志,同步日志。
- Candidate:Leader 選舉過程中的臨時角色,由 Follower 轉化而來,發起投票參與競選。
- Follower:接受 Leader 的心跳和日志同步數據,投票給 Candidate。
在正常的情況下,只有一個服務器是 Leader,剩下的服務器是 Follower。Follower 是被動的,它們不會發送任何請求,只是響應來自 Leader 和 Candidate 的請求。
2.2 任期
如圖 3 所示,raft 算法將時間劃分為任意長度的任期(term),任期用連續的數字表示,看作當前 term 號。每一個任期的開始都是一次選舉,在選舉開始時,一個或多個 Candidate 會嘗試成為 Leader。如果一個 Candidate 贏得了選舉,它就會在該任期內擔任 Leader。如果沒有選出 Leader,將會開啟另一個任期,并立刻開始下一次選舉。raft 算法保證在給定的一個任期最少要有一個 Leader。
每個節點都會存儲當前的 term 號,當服務器之間進行通信時會交換當前的 term 號;如果有服務器發現自己的 term 號比其他人小,那么他會更新到較大的 term 值。如果一個 Candidate 或者 Leader 發現自己的 term 過期了,他會立即退回成 Follower。如果一臺服務器收到的請求的 term 號是過期的,那么它會拒絕此次請求。
2.3 日志
- entry:每一個事件成為 entry,只有 Leader 可以創建 entry。entry 的內容為<term,index,cmd>其中 cmd 是可以應用到狀態機的操作。
- log:由 entry 構成的數組,每一個 entry 都有一個表明自己在 log 中的 index。只有 Leader 才可以改變其他節點的 log。entry 總是先被 Leader 添加到自己的 log 數組中,然后再發起共識請求,獲得同意后才會被 Leader 提交給狀態機。Follower 只能從 Leader 獲取新日志和當前的 commitIndex,然后把對應的 entry 應用到自己的狀態機中。
3 領導人選舉
raft 使用心跳機制來觸發 Leader 的選舉。
如果一臺服務器能夠收到來自 Leader 或者 Candidate 的有效信息,那么它會一直保持為 Follower 狀態,并且刷新自己的 electionElapsed,重新計時。
Leader 會向所有的 Follower 周期性發送心跳來保證自己的 Leader 地位。如果一個 Follower 在一個周期內沒有收到心跳信息,就叫做選舉超時,然后它就會認為此時沒有可用的 Leader,并且開始進行一次選舉以選出一個新的 Leader。
為了開始新的選舉,Follower 會自增自己的 term 號并且轉換狀態為 Candidate。然后他會向所有節點發起 RequestVoteRPC 請求, Candidate 的狀態會持續到以下情況發生:
- 贏得選舉
- 其他節點贏得選舉
- 一輪選舉結束,無人勝出
贏得選舉的條件是:一個 Candidate 在一個任期內收到了來自集群內的多數選票(N/2+1),就可以成為 Leader。
在 Candidate 等待選票的時候,它可能收到其他節點聲明自己是 Leader 的心跳,此時有兩種情況:
- 該 Leader 的 term 號大于等于自己的 term 號,說明對方已經成為 Leader,則自己回退為 Follower。
- 該 Leader 的 term 號小于自己的 term 號,那么會拒絕該請求并讓該節點更新 term。
由于可能同一時刻出現多個 Candidate,導致沒有 Candidate 獲得大多數選票,如果沒有其他手段來重新分配選票的話,那么可能會無限重復下去。
raft 使用了隨機的選舉超時時間來避免上述情況。每一個 Candidate 在發起選舉后,都會隨機化一個新的枚舉超時時間,這種機制使得各個服務器能夠分散開來,在大多數情況下只有一個服務器會率先超時;它會在其他服務器超時之前贏得選舉。
4 日志復制
一旦選出了 Leader,它就開始接受客戶端的請求。每一個客戶端的請求都包含一條需要被復制狀態機(Replicated State Mechine)執行的命令。
Leader 收到客戶端請求后,會生成一個 entry,包含<index,term,cmd>,再將這個 entry 添加到自己的日志末尾后,向所有的節點廣播該 entry,要求其他服務器復制這條 entry。
如果 Follower 接受該 entry,則會將 entry 添加到自己的日志后面,同時返回給 Leader 同意。
如果 Leader 收到了多數的成功響應,Leader 會將這個 entry 應用到自己的狀態機中,之后可以成為這個 entry 是 committed 的,并且向客戶端返回執行結果。
raft 保證以下兩個性質:
- 在兩個日志里,有兩個 entry 擁有相同的 index 和 term,那么它們一定有相同的 cmd
- 在兩個日志里,有兩個 entry 擁有相同的 index 和 term,那么它們前面的 entry 也一定相同
通過“僅有 Leader 可以生存 entry”來保證第一個性質,第二個性質需要一致性檢查來進行保證。
一般情況下,Leader 和 Follower 的日志保持一致,然后,Leader 的崩潰會導致日志不一樣,這樣一致性檢查會產生失敗。Leader 通過強制 Follower 復制自己的日志來處理日志的不一致。這就意味著,在 Follower 上的沖突日志會被領導者的日志覆蓋。
為了使得 Follower 的日志和自己的日志一致,Leader 需要找到 Follower 與它日志一致的地方,然后刪除 Follower 在該位置之后的日志,接著把這之后的日志發送給 Follower。
Leader?給每一個Follower?維護了一個?nextIndex,它表示?Leader?將要發送給該追隨者的下一條日志條目的索引。當一個?Leader?開始掌權時,它會將?nextIndex?初始化為它的最新的日志條目索引數+1。如果一個?Follower?的日志和?Leader?的不一致,AppendEntries?一致性檢查會在下一次?AppendEntries RPC?時返回失敗。在失敗之后,Leader?會將?nextIndex?遞減然后重試?AppendEntries RPC。最終?nextIndex?會達到一個?Leader?和?Follower?日志一致的地方。這時,AppendEntries?會返回成功,Follower?中沖突的日志條目都被移除了,并且添加所缺少的上了?Leader?的日志條目。一旦?AppendEntries?返回成功,Follower?和?Leader?的日志就一致了,這樣的狀態會保持到該任期結束。
5 安全性
5.1 選舉限制
Leader 需要保證自己存儲全部已經提交的日志條目。這樣才可以使日志條目只有一個流向:從 Leader 流向 Follower,Leader 永遠不會覆蓋已經存在的日志條目。
每個 Candidate 發送 RequestVoteRPC 時,都會帶上最后一個 entry 的信息。所有節點收到投票信息時,會對該 entry 進行比較,如果發現自己的更新,則拒絕投票給該 Candidate。
判斷日志新舊的方式:如果兩個日志的 term 不同,term 大的更新;如果 term 相同,更長的 index 更新。
5.2 節點崩潰
如果 Leader 崩潰,集群中的節點在 electionTimeout 時間內沒有收到 Leader 的心跳信息就會觸發新一輪的選主,在選主期間整個集群對外是不可用的。
如果 Follower 和 Candidate 崩潰,處理方式會簡單很多。之后發送給它的 RequestVoteRPC 和 AppendEntriesRPC 會失敗。由于 raft 的所有請求都是冪等的,所以失敗的話會無限的重試。如果崩潰恢復后,就可以收到新的請求,然后選擇追加或者拒絕 entry。
5.3 時間與可用性
raft 的要求之一就是安全性不依賴于時間:系統不能僅僅因為一些事件發生的比預想的快一些或者慢一些就產生錯誤。為了保證上述要求,最好能滿足以下的時間條件:
broadcastTime << electionTimeout << MTBF
- broadcastTime:向其他節點并發發送消息的平均響應時間;
- electionTimeout:選舉超時時間;
- MTBF(mean time between failures):單臺機器的平均健康時間;
broadcastTime應該比electionTimeout小一個數量級,為的是使Leader能夠持續發送心跳信息(heartbeat)來阻止Follower開始選舉;
electionTimeout也要比MTBF小幾個數量級,為的是使得系統穩定運行。當Leader崩潰時,大約會在整個electionTimeout的時間內不可用;我們希望這種情況僅占全部時間的很小一部分。
由于broadcastTime和MTBF是由系統決定的屬性,因此需要決定electionTimeout的時間。
一般來說,broadcastTime 一般為?0.5~20ms,electionTimeout 可以設置為?10~500ms,MTBF 一般為一兩個月。
?參考文章:https://github.com/OneSizeFitsQuorum/raft-thesis-zh_cn/blob/master/raft-thesis-zh_cn.md
總結
以上是生活随笔為你收集整理的Paxos算法和Raft算法---经典的分布式系统一致性问题解决算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分布式系统中的CAP理论和BASE理论详
- 下一篇: 网购秒杀系统架构设计