上一篇講述了什麼是分佈式一致性問題,以及它難在哪裏,liveness和satefy問題,和FLP impossibility定理。有興趣的童鞋能夠看看分佈式系統一致性問題與Raft算法(上)。html
這一節主要介紹raft算法是如何解決分佈式系統中一致性問題的。提及raft你們可能比較陌生,但zookeeper應該都比較熟悉了,zookeeper的ZAB協議能夠說和raft算法是很是類似的。git
再PS:本篇的重點是介紹raft算法的邏輯,因此有些細節會選擇性得忽略,否則就太長了。對具體實現細節有興趣的童鞋更應該去看看具體的論文。文末附上MIT6.824的地址以及raft中英文論文,有需求能夠自取。github
要說raft算法,那就不得不先說paxos算法。在raft算法問世以前,paxos算法能夠說一直就是分佈式一致性的代名詞。但paxos有兩個主要問題:算法
針對第一點,儘管有不少人試圖下降它的複雜程度,但依舊改變不了它難以理解的事實。而第二點就致命了,首先paxos算法能解決分佈式系統的共識問題,這個是已經被證實了的。但要將paxos算法實際應用起來,每每須要修改其算法結構,但極可能修改後算法就變了模樣,也就是說修改後的paxos算法與原先的paxos算法相比有存在缺陷的可能,而且難以證實。服務器
raft算法就是爲了解決paxos的缺陷而產生的。它更加易於理解,而且可以達到paxos算法的相同功能,也就是可以解決分佈式系統的一致性問題。網絡
那麼下面介紹下raft的具體實現吧。架構
raft的總體架構先從這張圖看起,這是raft論文裏面的圖。 併發
這張圖揭示了客戶端向服務器發送請求的流程,咱們先簡單介紹下圖的內容,再分析下整個流程。左邊的是客戶端,右邊的是服務端(分佈式系統,即有N個節點)。客戶端負責發送請求更改分佈式系統中的狀態(變量),服務端負責接收信息,並讓系統中全部節點就某個狀態達成共識。異步
服務端也就是分佈式系統環境,由一個leader組成,多個follower構成。leader負責接收消息並同步給其餘節點,以及負責解決節點信息不一致的問題,具體怎麼解決後面會說。而follwer負責接收leader的消息以及當接收不到消息的時候,試圖讓本身成爲leader。分佈式
那麼整個流程大概是這樣:
這就是大概的流程,固然其中省略了不少細節。那麼如今,咱們來看看具體的內部流程。
簡單得說,raft經過一個選定一個系統惟一的領導者(其餘節點爲追隨者),由它來負責管理各個節點(包括本身)的日誌信息,來實現一致性。那麼這樣就有了兩個問題,第一個是惟一的領導者選舉,第二個是保證每一個節點日誌的一致性,咱們逐個來講。
注意這裏只會講解大概的思路,儘可能不去太深刻到細節中去,仍是那句話,對具體實現細節感興趣的童鞋推薦看原論文(文末附)。
先大概介紹下領導者選舉的流程。首先,leader會週期性得發送心跳(非固定週期,就是說可能間隔10ms發心跳給a,15毫秒發心跳給b,再間隔8ms發給a),維繫本身的地位。當某個追隨者在時間內沒接收到leader的心跳的時候,它就會知道leader掛了,這個時候追隨者就會嘗試推舉本身成爲領導者(成爲候選人),併發送請求讓其餘追隨者投票給本身。其餘追隨者經過某種規則判斷該候選人有沒有「資質」,有則投票給他,不然不投票。最終,當超過半數的追隨者認同該候選人,那麼它就成爲了新的leader。
在這個過程當中,主要面臨的也是兩個問題,
處理這個問題,在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是否和本身的一致,由此實現整個分佈式系統的一致性。
在這個過程,主要也是有幾個問題須要解決:
在介紹日誌一致性前,須要明白這樣兩個事實:
若是在不一樣的節點的日誌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語義的方法,這些比較細節的內容就很少說了。這篇文章的目的仍是但願起到拋磚引玉的做用而已。
以上~
原文出處:https://www.cnblogs.com/listenfwind/p/12390489.html