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

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

兩忠一叛問題:

 

如上圖所示,將軍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的成員發現和數據同步階段,而是當節點發起選舉時,遞增任期編號,在選舉結束後,廣播心跳,直接建立領導者關係,然後向各節點同步日誌,來實現數據副本的一致性。

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