前言算法
我計劃寫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
entry in log
如圖所示,咱們能夠把log 理解爲entry的集合。在entry中包含了common命令、entry所在的term 以及每個entry的順序編號index。
raft的一致性保證了下面的屬性:
一、若是在不一樣節點中log中的entry有相同的index 和term。 那麼必定存儲的是相同的command。
二、若是在不一樣節點中log中的entry有相同的index 和term。 那麼此entry以前的全部entry都是相同的。
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是一致的。
安全性
上面的屬性還不可以充分的保證系統的安全性。 考慮下面的例子:
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可視化
知乎,寫得通常可是有借鑑地方