淺談分散式共識演算法raft
前言:在分散式的系統中,存在很多的節點,節點之間如何進行協作執行、高效流轉、主節點掛了怎麼辦、如何選主、各節點之間如何保持一致,這都是不可不面對的問題,此時raft演算法應運而生,專門 用來解決上述問題。對於分散式的一致性演算法,著名的有paxos,zookeeper基於paxos提出了zab協議, paxos是出名的晦澀難懂.而raft的設計初衷就是容易理解和簡單、高效,本篇部落格我們就來循序漸進的看看raft到底是什麼?它的執行原理是什麼樣的?
本篇部落格的目錄:
一:raft的狀態
二:選主過程
三:如何保證叢集一致性
四:如何處理腦裂問題
五:總結
一:raft的狀態
raft的叢集角色分為3種,不同的節點在執行環境中處於不同的角色,任何節點的任何一個時刻都處於以下三種角色之一,不同的角色具有不同的功能,所承擔的職責也不一樣:
①:follower
follwer是叢集的初始狀態,所有的節點在剛開始加入到叢集中,預設是follower的角色,也就是從節點~
②:candidate
candidate的含義可以理解為候選人,這是專門用於在follower在進行選舉的時候,被投票者的稱謂,這是一箇中間角色,followerA會發起投票到followerB,此時followerA的角色就是candidate
③:leader
有了從節點之後就必須有主節點了,所以接下來伴隨的是選主的過程,選主之後的節點稱之為leader,也就是主節點,主節點只有一個,由leader接收使用者的請求,每一次請求都被被記錄到log entry中
三個角色可以這樣理解:試想新學期開學第一天,所有的同學都是普通學生的身份(follower),新學期開始需要挑選出班長(leader),需要進行投票,大家先選出幾個候選人,候選人可以自己投票給自己,此時要選中成為班長的這幾個人就是candidate,最後經過選舉出來的班長就是leader
任期:
任期簡稱Term,是raft裡面非常重要的概念,每個任期可以是任意時長,任期用連續的整數進行標號,每個節點都維護著當前的任期值,每個節點在檢測到自己的任期值低於其他節點會更新自己的任期值,設定為檢測到的較高值。當leader和candidate發現自己的任期低於別的節點,則會立即把自己轉換為follower
下面是幾種角色的流轉圖:
二:raft的選主
2.1:leader負責處理客戶端的請求
所有對日誌的新增或者狀態變化的操作都是通過leader來完成,當leader接收請求之後會將日誌分發到叢集的所有follower節點,日誌的資料流是從leader到其他的節點,而不會產生follower流向日誌的情況,raft會保證流向所有follower節點的日誌副本都是一致的:
選舉leader發生在以下兩種情況:
①當一個raft叢集初始化的時候②當選舉出來的主節點宕機、崩潰的時候
2.2:接下來談一談raft是如何選主的:
一個Raft
叢集開始,叢集中的節點所有的初始狀態都是 Follower,
然後設定任期(term為0),並啟動計時器發起選舉(Election),開始選舉之後每個參與方都會有一個隨機的超時時間(Election Timeout
),這裡的關鍵就是隨機 Timeout(
150ms 到 300ms之間)
,最先走完timeout時間的一個節點開始
發起投票,向還在 timeout
中的另外節點請求投票(Reuest Vote)並等待回覆,此時它就只能投給自己,然後raft會統計得票數,在計數器時間內,得票最多的會成為leader.這樣的結果就是最先發起投票的節點會有大概率成為主節點,選出 Leader
後,term值會+1,並且Leader
通過定期向所有 Follower
傳送心跳資訊(官方稱之為:Append Entries,Append Entries是一種RPC協議)保持連線。
兩個節點同時發起選舉
因為follwer節點的超時時間是隨機的,所以可能會存在兩個節點正好隨機到相同的random time,並且擁有相同的term,此時raft會如何處理呢?raft會在相同的random time out時間同時發起leader選舉,因為兩個Candidate存在相同的term和timeout,並且同時發起投票,最終他們得到的votes是相同的。這個時候raft會等待下一輪的重試,下一輪兩個節點的time out可能會不同,重試直到選舉出leader
2.3:當選舉完成之後考慮以下幾個情形:
情形一:leader宕機
每次當leader對所有的followe發出Append Entries的時候,follower會有一個隨機的超時時間,如果再超時時間內收到了leader的請求就會重置超時時間,如果沒有收到超過超時時間,follower沒有收到Leader
的心跳,follower會認為Leader
可能已經掛了,此時第一個超時的follower會發起投票,注意這個時候它依然會向宕機的原leader發出Reuest Vote,但原leader不會回覆。raft設計的
請求投票都是冪等的,會檢測狀態。當收到叢集超過一半的節點的RequestVote reply後,此時的follower會成為leader
ps:後期leader恢復正常之後,加入到raft叢集,初始化的角色是follower,而並非leader。因為任何時刻leader只有一個,如果是兩個,就會發生"腦裂"問題
情形二:follower宕機
follower宕機對整個叢集影響不大,最多的影響是leader發出的Append Entries無法被收到,但是leader還會繼續一直髮送,直到follower恢復正常。raft會保證傳送AppendEntries request的rpc訊息是冪等的,如果follower已經接受到了訊息,但是leader又讓它再次接受,follower會直接忽略
三:raft如何保證叢集的一致性
3.1:Raft
協議由leader節點負責接收客戶端的請求,leader會將請求包裝成log entry分發到從節點,所以叢集強依賴 Leader
節點的可用性,以確保叢集 資料的一致性。資料的流向只能從 Leader
節點向 Follower
節點轉移,這個過程叫做日誌複製(Log Replication):
① 當 Client 向叢集 Leader 節點 提交資料 後,Leader 節點 接收到的資料 處於 未提交狀態(Uncommitted)。
②接著 Leader 節點會併發地向所有Follower節點複製資料並等待接收響應ACK
③ leader會等待叢集中至少超過一半的節點已接收到資料後, Leader 再向 Client 確認資料 已接收。
④ 一旦向 Client 發出資料接收 Ack 響應後,表明此時 資料狀態 進入 已提交(Committed),Leader 節點再向 Follower 節點發通知告知該資料狀態已提交
⑤ follower開始commit自己的資料,此時raft叢集達到主節點和從節點的一致
3.2:在進行一致性複製的過程中,假如出現了異常情況,raft都是如何處理的呢?
1.資料到達 Leader 節點前,這個階段 Leader 掛掉不影響一致性
2.資料到達 Leader 節點,但未複製到 Follower 節點。這個階段Leader
掛掉,資料屬於未提交狀態,Client
不會收到Ack
會認為超時失敗可安全發起重試。
3.資料到達 Leader 節點,成功複製到 Follower 所有節點,但 Follower 還未向 Leader 響應接收。這個階段Leader
掛掉,雖然資料在Follower
節點處於未提交狀態(Uncommitted
),但是保持一致的。重新選出Leader
後可完成資料提交。
4.資料到達 Leader 節點,成功複製到 Follower 的部分節點,但這部分 Follower 節點還未向 Leader 響應接收。這個階段Leader
掛掉,資料在Follower
節點處於未提交狀態(Uncommitted
)且不一致。
Raft
協議要求投票只能投給擁有最新資料的節點。所以擁有最新資料的節點會被選為Leader
,然後再強制同步資料到其他Follower
,保證資料不會丟失並最終一致。
5.資料到達 Leader 節點,成功複製到 Follower 所有或多數節點,資料在 Leader 處於已提交狀態,但在 Follower 處於未提交狀態。
這個階段Leader
掛掉,重新選出新的Leader
後的處理流程和階段3
一樣。
6.資料到達 Leader 節點,成功複製到 Follower 所有或多數節點,資料在所有節點都處於已提交狀態,但還未響應 Client。這個階段Leader
掛掉,叢集內部資料其實已經是一致的,Client
重複重試基於冪等策略對一致性無影響。
四:如何解決腦裂問題
當raft在叢集中遇見網路分割槽的時候,叢集就會因此而相隔開,在不同的網路分割槽裡會因為無法接收到原來的leader發出的心跳而超時選主,這樣就會造成多leader現象,見下圖:在網路分割槽1和網路分割槽2中,出現了兩個leaderA和D,假設此時要更新分割槽2的值,因為分割槽2無法得到叢集中的大多數節點的ACK,會複製失敗。而網路分割槽1會成功,因為分割槽1中的節點更多,leaderA能得到大多數迴應
當網路恢復的時候,叢集不再是雙分割槽,raft會有如下操作:
①: leaderD發現自己的Term小於LeaderA,會自動下臺(step down)成為follower,leaderA保持不變依舊是叢集中的主leader角色
②: 分割槽中的所有節點會回滾roll back自己的資料日誌,並匹配新leader的log日誌,然後實現同步提交更新自身的值。通知舊leaderA也會主動匹配主leader節點的最新值,並加入到follower中
③: 最終叢集達到整體一致,叢集存在唯一leader(節點A)
五:總結
本篇部落格從整體上講了下raft的狀態角色、如何選舉出leader、如何保證一致性、以及如何處理網路分割槽時的腦裂問題,整理較為粗略,raft實現起來更為複雜和細緻,所以這裡只是淺談一下。理解raft的主要目的在於分散式環境中,對於叢集之間的節點互動、宕機後如何處理如何保證高可用、高一致性有一定的理解。