raft算法理論與實踐[1]

前言算法


我計劃寫raft的一系列文章,包含從理論到代碼實踐,此文章依託於MIT的研究生課程。數組


背景安全


raft 是一種分佈式的共識算法,其目的是要實現多個節點集羣的容錯性,一致性從而可以構建大規模的軟件系統。服務器

在raft以前,比較有名的是Paxos。可是paxos難於理解。併發

raft的誕生是爲了讓共識算法更容易理解,在工程上更容易實現。分佈式


和其餘的共識算法不一樣的是,raft具備下面的特色:ide

一、leader:raft中會有一個領導者具備超級權限,能夠把本身的log 複製到其餘節點中。區塊鏈

二、leader election: raft每隔一段隨機的時間就會進行leader的選舉3d

三、raft容許集羣配置變化時正常運行。日誌


Replicated state machine


狀態機是分佈式系統中的一個重要概念,任何一個系統的最終狀態均可以當作是每個操做的集合。所以,算法會維護一份replicated log,將每一份操做都存儲起來。

每個節點只要按順序執行log中的命令,就會到達相同的最終狀態。這樣,即使是系統奔潰也能夠快速的恢復。

共識算法須要保證relicated log的一致性,服務器收到客戶端發出來的執行命令Command後,會將其加入到log中。

服務器之間會相互的交流,保證最後的log的一致性(即使服務器奔潰),即Command 會複製到其餘服務器的log中,全部服務器的log是相同的,有序的。

其餘服務器會執行此log,即會執行此命令。最後,全部的服務器都會到達同一個狀態。


共識算法必須知足下面的屬性:

一、在極端狀況下(丟包、奔潰)任然可以保證安全性。

二、大多數節點正常的狀況下可以保證可用。

三、不能依靠時間戳去保證log的一致性。

四、當大部分的節點經過RPC遠程調用交流 達成共識後,command就能夠被確認和執行。小部分節點的不穩定不會影響整個系統。


raft basic


raft集羣通常保持奇數個數量(5個節點比較廣泛). 從而只要大部分節點存活,便可用。

raft中的節點有3種狀態。 leader, Candidates, follower。

一、通常的狀態只會存在一個leader,其他的節點都是follower。

二、leader會處理全部的客戶端請求, 若是是客戶端請求follower,也會被轉發到leader處理。

三、Candidates 是一種選舉時候的過渡狀態,用於自身拉票選舉leader。


在raft中會有一個叫作term的時間週期。term是以選舉leader開始的,若是Candidates選舉成爲了leader,那麼其會成爲這個term剩下時間的leader。

有時候,在整個term週期都沒有選舉出leader。這時一個新的選舉會在不久後開始。

Terms 在raft中相似於一種時間戳,後一個必定比前一個後發生,這一點和比特幣中的區塊鏈很相似。

每個服務器會存儲一個當前的term,其會隨着時間的增長而增加。若是某一個節點的當前term小於其餘節點,那麼節點會更新本身的term爲最大的term。

若是一個candidate 發現本身當前的term 過期了,那麼其會當即變爲follower。

raft節點之間經過RPC(遠程過程調用)來進行通訊。 RequestVote 方法用於candidate在選舉時候使用,AppendEntries用於leader在通知其餘節點複製log時使用。同時也用於心跳檢測。

RPC 是併發的,並支持失敗重試。


選舉


在raft中會有一套心跳檢測,只要follower收到來自leader或者Candidates的數據,那麼其會保持follower的狀態。

若是follower一段時間內沒有收到RPC請求,這意味着選舉超時( election timeout )。

這時follower會將current term 加1,過渡到Candidates狀態。

其會給本身投票,併發送RequestVote RPC請求給其餘的節點,拉票!


Candidates狀態會持續,直到下面的3種狀況發生:

一、當其得到了大部分節點的支持後,其贏得了選舉,變爲了leader。

一旦其變爲了leader,其會向其餘節點發送 AppendEntries RPC, 確認其leader的地位,阻止選舉。

二、其餘節點成爲了leader。

若是其收到了其餘節點的AppendEntries RPC. 並發現其餘節點的current term比本身的大,則其變爲follower狀態。

三、一段時間過去任然沒有參與者。

若是有許多的節點同時變爲了candidate,則可能會出現一段時間內都沒有節點可以選舉成功的狀況。

在raft中,爲了快速解決並修復這個問題,規定了每個candidate在選舉前會重置一個隨機的選舉超時( election timeout )時間,此隨機時間會在一個區間內(eg.150-300ms)

這樣保證了,在大部分的狀況下,有一個惟一的節點首先選舉超時,其在大部分節點選舉超時前發送心跳檢測,贏得了選舉。


當一個leader在心跳檢測中發現另外一個節點有更高的term時,會轉變爲follower。不然其將一直保持leader狀態。


日誌複製(Log replication)


當成爲leader後,其會接受來自客戶端的請求。每個客戶端請求都包含一個將要被節點的狀態機執行的command。

leader其會將這個command 包裝爲一個entry放入到log中,並經過AppendEntries RPC 發送給其餘節點,要求其添加此entry到log中。


當entry被 大部分的節點接受並複製後,這個entry的狀態變爲了committed. raft算法保證了commited entry到最後必定可以會被全部節點的狀態機執行。

一旦follower知道(AppendEntries RPC)某一個entry被commit以後,follower會按順序執行log中的entry


 watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXNoaXhpYW5nbGlhbg==,size_16,color_FFFFFF,t_70

                                                                                         entry in log


如圖所示,咱們能夠把log 理解爲entry的集合。在entry中包含了common命令、entry所在的term 以及每個entry的順序編號index。


raft的一致性保證了下面的屬性:

一、若是在不一樣節點中log中的entry有相同的index 和term。 那麼必定存儲的是相同的command。

二、若是在不一樣節點中log中的entry有相同的index 和term。 那麼此entry以前的全部entry都是相同的。


watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXNoaXhpYW5nbGlhbg==,size_16,color_FFFFFF,t_70

                                                                                      leader crashes


節點f可能會發生,若是其是term 2的leader, 添加entry到log中,可是沒有commit時就奔潰了,其快速恢復後又變爲了term 3 的leader, 添加entry到log中,沒有commit又繼續奔潰了。


在正常的狀況下,上面的兩個屬性都能知足,可是異常狀況下,這種狀況會被打破,可能會出現如上圖所示的情形,


在raft中,爲了處理這樣的不一致性,強制要求follower的log與leader的log要一致。


所以leader必需要發現一個entry,在這個entry以後的都是不相同的entry。在這個entry以前的都是一致的entry。在leader中會爲每個follower維護一份nextIndex 數組。標誌了將要發送給follower的下一個index。 最後,follower會刪除掉全部不一樣的entry,並用和leader一致的log。這一過程,都會經過AppendEntries RPC 執行完畢。當AppendEntries RPC返回success,就代表follower 與 leader的log是一致的。


安全性


上面的屬性還不可以充分的保證系統的安全性。 考慮下面的例子:


watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXNoaXhpYW5nbGlhbg==,size_16,color_FFFFFF,t_70

                                                                                       safe problem


上圖要說明的是,一個已經被commit的entry 在目前的狀況下是有可能被覆蓋掉的。例如在a 階段s1成爲了leader,其entry尚未commit。 在b階段,s1奔潰,s5成爲了leader ,添加log可是任然沒有commit。 在c階段,s5奔潰,s1成爲了leader。其entry成爲了commit。 在d階段s1奔潰,s5成爲了leader,其會將本已commit的entry給覆蓋掉。


raft使用一種更簡單的方式來解決這個難題,raft爲leader添加了限制:

要成爲leader 必需要包含過去全部的commit entry。

Candidates 要想成爲leader,必需要通過大部分follower節點的贊成。

而commit entry 也代表其已經存在於大部分的服務器中。 所以commit entry 至少會出如今這些follower節點中的至少有一個節點。所以咱們能夠證實,在大部分的follower中,至少有一個是包含了leader的全部commit entry的。

所以 若是一個candidate的log是最新的(即他與其餘的節點對比時,若是term更大的,最新。若是term相同的,那麼越長的那個log越新。)其才能夠成爲leader。


所以可知,一個leader必定包含了之前leader的commit entry。


todo


配置改變、 日誌壓縮快照(log compaction / snapshotting )


總結


上面對於raft的描述,保證了存在5點:

一、Election Safety:在一個term週期內只會存在一個leader。

二、Leader Append-Only: leader只會添加log,而不會刪除或者覆蓋log。

三、Log Matching:若是兩個log有一個相同index與term的entry,那麼他們以前的log都是相同的。

四、Leader Completeness:若是一個log entry在一個term週期成爲commit, 那麼其必定會存在於下一個leader的log中。

五、State Machine Safety:若是某節點已經將index A 應用於其狀態機。則之後其餘節點不可能在同一index A 卻具備不一樣的log entry。 由於應用到狀態機說明已經被commit,而藉助於第4點得證。


參考資料


raft論文

raft可視化

知乎,寫得通常可是有借鑑地方

相關文章
相關標籤/搜索