深入剖析分散式一致性共識演算法

HarvardFly發表於2021-02-17

一、共識演算法 -- 拜占庭問題

兩忠一叛問題:

 

如上圖所示,將軍A、B、C約定同時進攻或者撤退,假如將軍C叛變了,被中間人擷取訊息併傳送進攻給A、撤退給B,當所有將軍訊息都收到後結果如下:
A:2票進攻1票撤退;
B:2票撤退1票進攻;
導致最終A獨自去攻打敵軍,B撤退,最終會任務失敗。

 

口信訊息型拜占庭問題之解

如上圖所示,經過2輪協商可以解決上述的兩忠一叛問題;第一輪協商由leader發起,向其餘3個將軍傳送進攻的指令訊息,如果未收到訊息則預設撤退指令;第二輪協商為3個將軍之間的互通訊息,假如將軍C叛變為干擾資訊向將軍A、B傳送撤退訊息,則最終結果:
A:2進攻1撤退;
B:2進攻1撤退;
最終會執行進攻指令,這樣解決了兩忠一叛問題。

如果叛將人數為m,將軍總人數不能少於3m + 1 。叛將數m決定遞迴迴圈的次數(將軍們要進行多少輪作戰資訊協商),即m+1輪,n位將軍,最多能容忍(n - 1) / 3位叛將。

  

二、分散式一致性演算法前奏之Quorum NWR演算法

Quorum選舉演算法
在N個副本中,一次更新成功的如果有W個,那麼在讀取資料時是要從大於N-W個副本中讀取,這樣就能至少讀到一個更新的資料了。
如:維護了10個副本,一次成功更新了三個,那麼至少需要讀取八個副本的資料,可以保證讀到最新的資料。

 

WARO演算法(Write All Read one)
只有當所有的副本都更新成功之後,這次寫操作才算成功,否則視為失敗。WARO 優先保證讀服務,因為所有的副本更新成功,才能視為更新成功,從而保證了所有的副本一致,這樣的話,只需要讀任何一個副本上的資料即可。寫服務的可用性較低,因為只要有一個副本更新失敗,此次寫操作就視為失敗了。假設有 N 個副本,N-1 個都當機了,剩下的那個副本仍能提供讀服務;但是隻要有一個副本當機了,寫服務就不會成功。WARO 犧牲了更新服務的可用性,最大程度地增強了讀服務的可用性,而 Quorum 就是在更新服務和讀服務之間進行的一個折衷。

 

Quorum的應用
Quorum機制無法保證強一致性,也就是無法實現任何時刻任何使用者或節點都可以讀到最近一次成功提交的副本資料。
Quorum機制的使用需要配合一個獲取最新成功提交的版本號的metadata服務,這樣可以確定最新已經成功提交的版本號,然後從已經讀到的資料中就可以確認最新寫入的資料。
Quorum是分散式系統中常用的一種機制,用來保證資料冗餘和最終一致性的投票演算法,在Paxos、Raft和ZooKeeper的Zab等演算法中,都可以看到Quorum機制的應用。

 

如上圖所示,DATA-1有2個副本,DATA-2有3個副本,DATA-3 有1個副本,副本的數量即表示N;對DATA-2執行寫操作時,完成了2個副本的更新(節點B、C),才完成寫操作,即W此時為2;對DATA-2執行讀操作,客戶端讀取DATA-2的資料時,需要讀取2個副本中的資料,然後返回最新的那份資料,即讀副本R為2。

無論客戶端如何執行讀操作,即使訪問寫操作未強制更新副本節點B,執行讀操作時,因為要讀2份資料副本,所以除了節點B上的DATA-2,還會讀取節點C上的DATA-2,而節點C的DATA-2資料副本是強制更新成功的,這個時候返回給客戶端肯定是最新的那份資料。

對於Quorum:

當W+R>N的時候,對於客戶端來講,整個系統能保證強一致性,一定能返回更新後的那份資料。
當W+R<=N的時候,對於客戶端來講,整個系統只能保證最終一致性,可能會返回舊資料。

 

三、paxos演算法

paxos演算法由Proposer、Acceptor和Learner組成:

提議者(Proposer):提議一個值,用於投票表決。在絕大多數場景中,叢集中收到客戶端請求的節點,才是提議者。這樣做的好處是,對業務程式碼沒有入侵性,也就是說,我們不需要在業務程式碼中實現演算法邏輯,就可以像使用資料庫一樣訪問後端的資料。
接受者(Acceptor):對每個提議的值進行投票,並儲存接受的值,比如A、B、C三個節點。 一般來說,叢集中的所有節點都在扮演接受者的角色,參與共識協商,並接受和儲存資料。
學習者(Learner):被告知投票的結果,接受達成共識的值,儲存儲存,不參與投票的過程。一般來說,學習者是資料備份節點,比如“Master-Slave”模型中的Slave,被動地接受資料,容災備份。
 

 

Basic Paxos

在Basic Paxos中,使用提案代表一個提議,在提案中除了提案編號,還包含了提議值。使用[n, v]表示一個提案,其中n為提案編號,v為提議值。

 

整個共識協商是分2個階段進行的(二階段提交)。假設客戶端1的提案編號為1,客戶端2的提案編號為5,節點A、B先收到來自客戶端1的準備請求,節點C先收到來自客戶端2的準備請求。
prepare階段

客戶端1、2作為提議者,分別向所有接受者傳送包含提案編號的準備請求:
對於客戶端1的提案,由於之前沒有通過任何提案,所以節點A、B以後將不再響應提案編號小於等於1的prepare請求,即不會通過編號小於1的提案,節點C以後不再響應提案編號小於等於5的準備請求,即不會通過編號小於5的提案;
 
對於客戶端2的提案,當節點A、B收到提案編號為5的準備請求的時候,因為提案編號5大於它們之前響應的準備請求的提案編號1,而且兩個節點都沒有通過任何提案,所以它將返回一個 “尚無提案”的響應,所以節點A、B將不再響應提案編號小於等於5的準備請求,即不會通過編號小於5的提案,當節點C收到提案編號為1的準備請求的時候,由於提案編號1小於它之前響應的準備請求的提案編號5,所以丟棄該準備請求不做響應;
 
Accept階段
首先客戶端1、2在收到大多數節點的準備響應之後,會分別傳送接受請求:
當客戶端1收到大多數的接受者(節點A、B)的準備響應後,根據響應中提案編號最大的提案的值設定接受請求中的值。因為節點A、B的準備響應中都為空,所以就把自己的提議值3作為提案的值,傳送接受請求[1, 3];
當客戶端2收到大多數的接受者的準備響應後(節點A、B和節點C),根據響應中提案編號最大的提案的值,來設定接受請求中的值。因為該值在來自節點A、B、C的準備響應中都為空,所以就把自己的提議值7作為提案的值,傳送接受請求[5, 7];
 
當三個節點收到2個客戶端的接受請求時:
當節點A、B、C收到接受請求[1, 3]的時候,由於提案的提案編號1小於三個節點承諾能通過的提案的最小提案編號5,所以提案[1, 3]將被拒絕;
當節點A、B、C收到接受請求[5, 7]的時候,由於提案的提案編號5不小於三個節點承諾能通過的提案的最小提案編號5,所以就通過提案[5, 7],也就是接受了值7,三個節點就X值為7達成了共識;

 

Multi-Paxos

Basic Paxos只能就單個值(Value)達成共識,Multi-Paxos是通過多個Basic Paxos例項實現一系列值的共識的演算法。
Multi-Paxos通過引入Leader節點,將Leader節點作為唯一提議者,避免了多個提議者同時提交提案的情況,解決了提案衝突的問題,Leader節點是通過執行Basic Paxos演算法,進行投票選舉產生的。
優化Basic Paxos執行可以採用“當領導者處於穩定狀態時,省掉準備階段,直接進入接受階段”這個優化機制。在Leader節點上,序列中的命令是最新的,不再需要通過準備請求來發現之前被大多數節點通過的提案,Leader可以獨立指定提案中的值。Leader節點在提交命令時,可以省掉準備階段,直接進入到接受階段:
和重複執行Basic Paxos相比,Multi-Paxos引入領導者節點之後,因為只有領導者節點一個提議者,所以就不存在提案衝突。另外,當主節點處於穩定狀態時,就省掉準備階段,直接進入接受階段,所以在很大程度上減少了往返的訊息數,提升了效能,降低了延遲。
 

四、一致hash演算法

使用雜湊演算法的問題?
通過雜湊演算法,每個key都可以定址到對應的伺服器,比如,查詢key是key-01,計算公式為hash(key-01) %3 ,經過計算定址到了編號為1的伺服器節點A;如果伺服器數量發生變化,基於新的伺服器數量來執行雜湊演算法的時候,就會出現路由定址失敗的情況,無法找到之前定址到的那個伺服器節點;假如增加了一個節點,節點的數量從3變化為4,那麼之前的hash(key-01) %3 = 1,就變成了hash(key-01) %4 =X,因為取模運算髮生了變化,所以這個X大概率不是1,這時再查詢就會找不到資料了,因為key-01對應的資料並非儲存在節點X。

通過上圖可以看出,當擴容增加一個節點時會出現hash定址失敗的情況;同理,如果需要下線1個伺服器節點,也會存在類似的可能查詢不到資料的問題。

 

一致雜湊實現雜湊定址
一致雜湊演算法也用了取模運算,但與雜湊演算法不同的是,雜湊演算法是對節點的數量進行取模運算,而一致雜湊演算法是對2^32進行取模運算。在一致雜湊中,可以通過執行雜湊演算法將節點對映到雜湊環上,如選擇節點的主機名作為引數執行hash(),那麼每個節點就能確定其在雜湊環上的位置了。
當需要對指定key的值進行讀寫的時候,可以通過下面2步進行定址:
首先,將key作為引數執行hash()計算雜湊值,並確定此key在環上的位置;
然後,從這個位置沿著雜湊環順時針“行走”,遇到的第一節點就是key對應的節點。
 
根據一致雜湊演算法,key-01將定址到節點A,key-02將定址到節點B,key-03將定址到節點C。假設現在節點C故障了,key-01和key-02不會受到影響,只有key-03的定址被重定位到A。在一致雜湊演算法中,如果某個節點當機不可用了,那麼受影響的資料僅僅是會定址到此節點和前一節點之間的資料。比如當節點C當機了,受影響的資料是會定址到節點B和節點C之間的資料(例如key-03),定址到其他雜湊環空間的資料不會受到影響。同理,如果叢集擴容一個節點,在一致雜湊演算法中,如果增加一個節點,受影響的資料僅僅是會定址到新節點和前一節點之間的資料,其它資料也不會受到影響。
在雜湊定址中常出現這樣的問題:客戶端訪問請求集中在少數的節點上,出現了有些機器高負載,有些機器低負載的情況,在一致雜湊中可以使用虛擬節點讓資料訪問分佈的比較均勻。
使用虛擬節點解決冷熱不均的問題:
對每一個伺服器節點計算多個雜湊值,在每個計算結果位置上,都放置一個虛擬節點,並將虛擬節點對映到實際節點。
比如,可以在主機名的後面增加編號,分別計算 “Node-A-01”“Node-A-02”“Node-B-01”“Node-B-02”“Node-C-01”“Node-C-02”的雜湊值,於是形成6個虛擬節點;增加了節點後,節點在雜湊環上的分佈就相對均勻了。如果有訪問請求定址到“Node-A-01”這個虛擬節點,將被重定位到節點A。
因此,當節點數越多的時候,使用雜湊演算法時,需要遷移的資料就越多,使用一致雜湊時,需要遷移的資料就越少。所以相比hash演算法,一致雜湊演算法具有較好的容錯性和可擴充套件性。

  

五、zab協議

Multi-Paxos解決的是一系列值如何達成共識的問題,不關心最終達成共識的值是什麼,不關心各值的順序,即它不關心操作的順序性。
ZAB協議基於主備模式的原子廣播,最終實現了操作的順序性。Master-Slave的主備模型,主節點採用二階段提交,向備份節點同步資料,如果主節點發生故障,資料最完備的節點將當選主節點;原子廣播協議,廣播一組訊息,訊息的順序是固定的。
ZAB支援3種成員身份(領導者、跟隨者、觀察者)。
領導者(Leader): 作為主節點,在同一時間叢集只會有一個領導者,所有的寫請求都必須在領導者節點上執行。
跟隨者(Follower):作為備份節點, 叢集可以有多個跟隨者,它們會響應領導者的心跳,並參與領導者選舉和提案提交的投票,跟隨者可以直接處理並響應來自客戶端的讀請求,但對於寫請求,跟隨者需要將它轉發給領導者處理。
觀察者(Observer):作為備份節點,類似跟隨者,但是沒有投票權,觀察者不參與領導者選舉和提案提交的投票。
ZAB在Multi-Paxos的基礎上做了優化,為了實現分割槽容錯能力,將資料複製到大多數節點後,領導者就會進入提交執行階段,通知備份節點執行提交操作。
ZAB定義了4種成員狀態:
LOOKING:選舉狀態,該狀態下的節點認為當前叢集中沒有領導者,會發起領導者選舉。
FOLLOWING :跟隨者狀態,意味著當前節點是跟隨者。
LEADING :領導者狀態,意味著當前節點是領導者。
OBSERVING: 觀察者狀態,意味著當前節點是觀察者。
 
如上圖所示,首先,當跟隨者檢測到連線領導者節點的讀操作等待超時了,跟隨者會變更節點狀態,將自己的節點狀態變更成LOOKING,然後發起領導者選舉;接著,每個節點會建立一張選票,這張選票是投給自己的,然後各自將選票傳送給叢集中所有節點,一般而言,節點會先接收到自己傳送給自己的選票(因為不需要跨節點通訊,傳輸更快);叢集的各節點收到選票後,為了選舉出資料最完整的節點,對於每一張接收到選票,節點都需要進行領導者PK,也就將選票提議的領導者和自己提議的領導者進行比較,找出更適合作為領導者的節點,約定的規則如下:
優先檢查任期編號(Epoch),任期編號大的節點作為領導者;
如果任期編號相同,比較事務識別符號的最大值,值大的節點作為領導者;
如果事務識別符號的最大值相同,比較叢集ID,叢集ID大的節點作為領導者。
如果選票提議的領導者,比自己提議的領導者,更適合作為領導者,那麼節點將調整選票內容,推薦選票提議的領導者作為領導者。
 
zab故障恢復是由成員發現和資料同步兩個階段完成的,成員發現是通過跟隨者和領導者互動來完成的,目標是確保大多數節點對領導者的領導關係沒有異議,也就是確立領導者的領導地位:
成員發現,是為了建立跟隨者和領導者之間的領導者關係,並通過任期編號來確認這個領導者是否為最合適的領導者。當跟隨者和領導者設定ZAB狀態為資料同步,它們也就是進入了資料同步階段,資料同步也是通過跟隨者和領導者互動來完成的,目標是確保跟隨者節點上的資料與領導者節點上資料是一致的。
資料同步,是通過以領導者的資料為準的方式,來實現各節點資料副本的一致,需要你注意的是,基於“大多數”的提交原則和選舉原則,能確保被複制到大多數節點並提交的提案,就不再改變。
對於zab處理寫請求:
由於寫請求只能在領導者節點上處理,所以ZooKeeper叢集寫效能約等於單機。而讀請求是可以在所有的節點上處理的,所以讀效能是能水平擴充套件的。可以通過分叢集的方式來突破寫效能的限制,並通過增加更多節點,來擴充套件叢集的讀效能。
首先,ZAB實現了主備模式,也就是所有的資料都以主節點為準;
其次,ZAB實現了FIFO佇列,保證訊息處理的順序性。
另外,ZAB還實現了當主節點崩潰後,只有日誌最完備的節點才能當選主節點,因為日誌最完備的節點包含了所有已經提交的日誌,所以這樣就能保證提交的日誌不會再改變。
 

六、raft演算法

Raft演算法是分散式系統開發首選的共識演算法 ,從本質上說,Raft演算法是通過一切以領導者為準的方式,實現一系列值的共識和各節點日誌的一致。
Raft演算法支援領導者(Leader)、跟隨者(Follower)和候選人(Candidate)3種狀態:
跟隨者:就相當於普通群眾,默默地接收和處理來自領導者的訊息,當等待領導者心跳資訊超時的時候,就主動站出來,推薦自己當候選人。
候選人:候選人將向其他節點傳送請求投票(RequestVote)RPC訊息,通知其他節點來投票,如果贏得了大多數選票,就晉升當領導者。
領導者:主要工作內容就是3部分,處理寫請求、管理日誌複製和不斷地傳送心跳資訊。

 選舉領導者的過程:

 

首先,在初始狀態下,叢集中所有的節點都是跟隨者的狀態,每個節點等待領導者節點心跳資訊的超時時間間隔是隨機的。叢集中沒有領導者,而節點A的等待超時時間最小(150ms),它會最先因為沒有等到領導者的心跳資訊而發生超時。節點A就增加自己的任期編號,並推舉自己為候選人,先給自己投上一張選票,然後向其他節點傳送請求投票RPC訊息,請求它們選舉自己為領導者。當候選人節點A在選舉超時時間內贏得了大多數的選票,那麼它就會成為本屆任期內新的領導者。

 
節點A當選領導者後,將週期性地傳送心跳訊息,通知其他伺服器以阻止跟隨者發起新的選舉。
Raft演算法中約定的選舉規則:
1. 領導者週期性地向所有跟隨者傳送心跳訊息,阻止跟隨者發起新的選舉。
2. 如果在指定時間內,跟隨者沒有接收到來自領導者的訊息,那麼它就認為當前沒有領導者,推舉自己為候選人,發起領導者選舉。
3. 在一次選舉中,贏得大多數選票的候選人,將晉升為領導者。
4. 在一個任期內,領導者一直都會是領導者,直到它自身出現問題(比如當機),或者因為網路延遲,其它節點發起一輪新的選舉。
5. 在一次選舉中,每一個伺服器節點會按照“先來先服務”的原則進行投票。
6. 當任期編號相同時,日誌完整性高的跟隨者(最後一條日誌項對應的任期編號值更大,索引號更大),拒絕投票給日誌完整性低的候選人。比如節點B、C的任期編號都是3,節點B的最後一條日誌項對應的任期編號為3,而節點C為2,那麼當節點C請求節點B投票給自己時,節點B將拒絕投票。
 
Raft演算法日誌複製流程:
Raft演算法中,副本資料是以日誌的形式存在的,領導者接收到來自客戶端寫請求後,處理寫請求的過程就是一個複製和應用日誌項到狀態機的過程。
首先,領導者進入第一階段,通過日誌複製(AppendEntries)RPC訊息,將日誌項複製到叢集其他節點上。
接著,如果領導者接收到大多數的“複製成功”響應後,它將日誌項應用到它的狀態機,並返回成功給客戶端。如果領導者沒有接收到大多數的“複製成功”響應,那麼就返回錯誤給客戶端。
1. 接收到客戶端請求後,領導者基於客戶端請求中的指令,建立一個新日誌項,並附加到本地日誌中。
2. 領導者通過日誌複製RPC,將新的日誌項複製到其他的伺服器。
3. 當領導者將日誌項成功複製到大多數的伺服器上的時候,領導者會將這條日誌項應用到它的狀態機中。
4. 領導者將執行的結果返回給客戶端。
5. 當跟隨者接收到心跳資訊,或者新的日誌複製RPC訊息後,如果跟隨者發現領導者已經提交了某條日誌項,而它還沒應用,那麼跟隨者就將這條日誌項應用到本地的狀態機中。
 

七、總結 

ZAB協議在Multi-Paxos達成共識的基礎上實現了操作的順序性。

Raft演算法和Multi-Paxos不同之處:
1. 在Raft中,不是所有節點都能當選領導者,只有日誌最完整的節點,才能當選領導者;
2. 日誌必須是連續的;
 
Raft演算法與ZAB協議的異同點:
1. Raft採用的是“先到先得”的自定義投票演算法。Raft的領導者選舉,需要通訊的訊息數更少,選舉也更快。
2. 對於日誌複製,Raft和ZAB相同,都是以領導者的日誌為準來實現日誌一致,而且日誌必須是連續的,也必須按照順序提交。
3. 對於讀操作和一致性,ZAB的設計目標是操作的順序性,在ZooKeeper中預設實現的是最終一致性,讀操作可以在任何節點上執行;而Raft的設計目標是強一致性(也就是線性一致性),所以Raft更靈活,Raft系統既可以提供強一致性,也可以提供最終一致性。
4. 對於寫操作,Raft和ZAB相同,寫操作都必須在領導者節點上處理。
5. 成員變更,ZAB不支援成員變更,當需要節點變更(比如擴容)時,必須重啟整個ZooKeeper叢集。Raft支援成員變更,不需要重啟機器,叢集是一直執行的,服務也不會中斷。
相比ZAB,Raft的設計更為簡潔,Raft沒有引入類似ZAB的成員發現和資料同步階段,而是當節點發起選舉時,遞增任期編號,在選舉結束後,廣播心跳,直接建立領導者關係,然後向各節點同步日誌,來實現資料副本的一致性。

相關文章