分佈式系統一致性問題與Raft算法(下)

上一篇講述了什麼是分佈式一致性問題,以及它難在哪裏,liveness和satefy問題,和FLP impossibility定理。有興趣的童鞋能夠看看分佈式系統一致性問題與Raft算法(上)html

這一節主要介紹raft算法是如何解決分佈式系統中一致性問題的。提及raft你們可能比較陌生,但zookeeper應該都比較熟悉了,zookeeper的ZAB協議能夠說和raft算法是很是類似的。git

再PS:本篇的重點是介紹raft算法的邏輯,因此有些細節會選擇性得忽略,否則就太長了。對具體實現細節有興趣的童鞋更應該去看看具體的論文。文末附上MIT6.824的地址以及raft中英文論文,有需求能夠自取。github

1.raft算法的起源與paxos算法

要說raft算法,那就不得不先說paxos算法。在raft算法問世以前,paxos算法能夠說一直就是分佈式一致性的代名詞。但paxos有兩個主要問題:算法

  1. paxos算法複雜且難以理解
  2. paxos算法難以直接應用工程化

針對第一點,儘管有不少人試圖下降它的複雜程度,但依舊改變不了它難以理解的事實。而第二點就致命了,首先paxos算法能解決分佈式系統的共識問題,這個是已經被證實了的。但要將paxos算法實際應用起來,每每須要修改其算法結構,但極可能修改後算法就變了模樣,也就是說修改後的paxos算法與原先的paxos算法相比有存在缺陷的可能,而且難以證實。服務器

raft算法就是爲了解決paxos的缺陷而產生的。它更加易於理解,而且可以達到paxos算法的相同功能,也就是可以解決分佈式系統的一致性問題。網絡

那麼下面介紹下raft的具體實現吧。架構

2.raft算法

raft的總體架構先從這張圖看起,這是raft論文裏面的圖。 併發

這張圖揭示了客戶端向服務器發送請求的流程,咱們先簡單介紹下圖的內容,再分析下整個流程。左邊的是客戶端,右邊的是服務端(分佈式系統,即有N個節點)。客戶端負責發送請求更改分佈式系統中的狀態(變量),服務端負責接收信息,並讓系統中全部節點就某個狀態達成共識異步

服務端也就是分佈式系統環境,由一個leader組成,多個follower構成。leader負責接收消息並同步給其餘節點,以及負責解決節點信息不一致的問題,具體怎麼解決後面會說。而follwer負責接收leader的消息以及當接收不到消息的時候,試圖讓本身成爲leader分佈式

那麼整個流程大概是這樣:

  1. 客戶端發送請求給到服務端的leader(若是是追隨者,重定向到leader)
  2. leader接收到信息,改變自身的狀態,並將這一改變信息發送給所有的follower
  3. 當大多數follower節點將成功這個改變應用到自身的時候,leader確認了此次改變
  4. 將改變成功的消息返回給客戶端

這就是大概的流程,固然其中省略了不少細節。那麼如今,咱們來看看具體的內部流程。

簡單得說,raft經過一個選定一個系統惟一的領導者(其餘節點爲追隨者),由它來負責管理各個節點(包括本身)的日誌信息,來實現一致性。那麼這樣就有了兩個問題,第一個是惟一的領導者選舉,第二個是保證每一個節點日誌的一致性,咱們逐個來講。

注意這裏只會講解大概的思路,儘可能不去太深刻到細節中去,仍是那句話,對具體實現細節感興趣的童鞋推薦看原論文(文末附)。

領導者選舉

先大概介紹下領導者選舉的流程。首先,leader會週期性得發送心跳(非固定週期,就是說可能間隔10ms發心跳給a,15毫秒發心跳給b,再間隔8ms發給a),維繫本身的地位。當某個追隨者在時間內沒接收到leader的心跳的時候,它就會知道leader掛了,這個時候追隨者就會嘗試推舉本身成爲領導者(成爲候選人),併發送請求讓其餘追隨者投票給本身。其餘追隨者經過某種規則判斷該候選人有沒有「資質」,有則投票給他,不然不投票。最終,當超過半數的追隨者認同該候選人,那麼它就成爲了新的leader。

在這個過程當中,主要面臨的也是兩個問題,

  • 如何讓系統中只有惟一一個領導者,也就是如何處理殭屍節點(Zombie),或者說腦裂的問題
  • 當集羣中沒有領導者的時候,若是選出最合適的領導者,也就是追隨者投票的時候如何進行資質認定

處理殭屍節點問題(Zombie)

處理這個問題,在raft算法中,引入了一個任期的概念。任期從0開始,隨着leader的更迭逐漸遞增,經過這個任期的概念,解決了多數分佈式系統內部不一致的問題。

好比說一個leader掛掉或不可達的時候,會有一個追隨者試圖成爲新leader,這時候它的任期會自動加1。還記得嗎,競選leader的節點須要發送請求給每個追隨者投票,這個請求信息中就包含新的任期,追隨者接收信息對比發現請求裏面的任期比本身的大,便會更新本身的任期

這樣一來,上面說到的殭屍節點問題就解決了,當舊的leader(殭屍leader)從新返回後,發送心跳給追隨者,追隨者發現心跳請求裏面的任期比本身還低,便再也不鳥它。舊的leader發現沒人鳥本身,也就明白了本身已經再也不是leader,因此就變成了追隨者

若是你足夠細心,你會發現這個邏輯裏面還隱藏着一個問題。要是某個任期在競選leader的時候,沒有獲取到足夠的選票,也就是系統內大多數節點不承認它該怎麼辦呀?很簡單,這個任期就會變成一個空任期,會直接開始下一個任期的領導者選舉。至於投票不投票的邏輯,這是咱們接下來要說的。

選出最合適的領導者

咱們在上面說了,每一個節點會維持一個存放日誌的list。其實這個list不止存放日誌,它還存放了每條日誌對應的任期。相似

[('狀態5','任期0'),('狀態10','任期0'),('狀態14','任期1')]

這樣的列表。

咱們知道當追隨者試圖成爲leader(也就是候選人)的時候,會廣播投票請求,請求裏面就包含了競選者日誌list中最後一條日誌的索引,以及對應的任期。每一個進行投票的追隨者會與自身的日誌list比較,若是索引比自身list的最後索引小,那麼說明候選人的日誌沒本身的新,這時候追隨者會拒絕投票。

這樣的特性可以保證系統儘量得選出日誌最新的節點,爲何說盡量?由於依舊存在可能丟失狀態。好比leader接收到請求,還沒發給其餘節點,或只發給少數節點。那麼沒來得及完成處理的這些請求就可能丟失,但即使這樣,系統在最終依舊會能維持一致,只是並不是最新更改的狀態值,或者說有部分數據任然會丟失。

維持日誌一致性

首先,全部的狀態改變(能夠理解爲變量值改變)都是由leader執行,而後將這一改變輻射到全部的追隨者。每一個節點都維持一個存日誌的list,用以存儲每一條改變狀態的信息。

leader接收客戶端的操做指令後,先追加這一指令到本身的日誌list,而後會將指令發送給追隨者,追隨者主要目的就是接收這些指令,並追加到本身日誌list中。leader負責管理和檢測追隨者的日誌list是否和本身的一致,由此實現整個分佈式系統的一致性。

在這個過程,主要也是有幾個問題須要解決:

  • 如何在無序的網絡中控制追隨者日誌的一致
  • 當追隨者出現殭屍節點,在殭屍節點恢復正常的時候,如何確保日誌和leader變得一致

日誌一致的保證

在介紹日誌一致性前,須要明白這樣兩個事實:

若是在不一樣的節點的日誌list中,兩個條目擁有相同的索引和任期號,那麼他們存儲了相同的指令

若是在不一樣的節點的日誌list中,兩個條目擁有相同的索引和任期號,那麼他們以前的全部日誌條目也所有相同

也就是說,兩個節點各自有一個list保存日誌,若是兩個節點各自的list中的某個索引相同點,任期也相同,那麼說明它前面的內容都是一致的。

解決日誌一致性這個問題其實不難,就是當leader將客戶端的指令輻射出去的時候,要等到全部追隨者狀態確認後再執行下一條客戶端指令,典型的用效率換取準確性。這裏的狀態確認只有三種,成功,失敗和鏈接不上客戶端。注意失敗和鏈接不上是兩種狀況,失敗有多是由於某些日誌數據不一致,這時就須要找到追隨者日誌list中與一致的那個點,而後覆蓋掉後面不一致的數據。最極端的狀況就是發現追隨者所有都和leader的日誌list不一致,那麼就會將leader的list所有覆蓋追隨者的日誌list,這種作法也叫作強制複製

而鏈接不上則多是由於追隨者正忙,或者它真的掛掉了。上一篇講過,要在異步的網絡通訊中真正識別這兩種狀況幾乎是不可能的。這時候,咱們能夠直接認爲它掛掉了,若是發現其實沒掛,那麼直接按照殭屍節點的邏輯來處理。

處理殭屍節點

當發現追隨者節點不可達的時候,會將它標記爲殭屍節點(論文裏是說無限重試,但實際操做發現無限重試存在一些問題),等待該追隨者重啓。這樣一來,當殭屍節點從新恢復時最大的問題其實就是它的任期日誌list遠落後於leader。

解決這些問題的方法其實上面都講到了,每次leader請求,追隨者會比較任期,發現本身任期低會自動更新。若是是日誌list,那麼會找到與leader日誌list一致的那個索引,而後覆蓋後面的所有list。

最後再來結合具體的例子看看吧。

灰色方框之上的是一個剛擔任leader的節點。a,b,c,到f是不一樣的幾個follower節點,每一個節點後面的那行表明日誌list,裏面的數字表示任期。

當一個leader當選的時候,a,b,...到f基本涵蓋了可能的狀況。a和b表示追隨者缺乏部分日誌,那麼這時候追加就行。c和d表示r日誌過多,那麼要刪除索引11以及以後的日誌,讓日誌和leader保持一致。e和f代表r日誌list有不一致的內容,即某些地方任期不一致,這時候e會找到索引相同的最後一個節點(這裏是5),覆蓋後面的內容,f也是一樣的道理。

OK,以上就是raft算法維持分佈式系統一致性的基本思路,固然還有一些額外的內容,好比防止日誌list過長而能夠採用的checkpoint技術,以及客戶端實現exctly once語義的方法,這些比較細節的內容就很少說了。這篇文章的目的仍是但願起到拋磚引玉的做用而已。

以上~

raft算法原版

raft算法中文

mit6.824地址

原文出處:https://www.cnblogs.com/listenfwind/p/12390489.html

相關文章
相關標籤/搜索