版權聲明:本文爲博主原創文章,未經博主容許不得轉載。算法
手動碼字不易,請你們尊重勞動成果,謝謝服務器
Raft在Leader選舉階段使用term編號做爲提案編號來執行paxos算法進行leader選舉,爲了防止活鎖出現,Raft算法使用了隨機定時器的策略避開了同時競爭Leader的可能。在日至提交階段,算法保證了Leader要擁有所有日誌,而且Client只能與Leader交流,提交日誌。Leader利用兩階段提交和大多數集合確認的方式來確保日誌被成功保存。其算法和Zookeeper的ZAB算法有必定類似性。學習
不一樣於Paxos算法,Raft算法中只有一種角色。這個角色能夠有三種狀態:
一、Follower
二、Candidate
三、Leader.net
在每臺機器上都會存儲:
一、currentTerm:當前節點所能看到的最大的term值,該值單調增長
二、votedFor:當前term裏將票投給的對象,若是還沒有投票則爲空
三、log[]:日誌條目,會按順序做用於狀態機
四、commitIndex:當前節點最後一個被提交的日誌序號
五、lastApplied:當前節點最後一條被應用於狀態機的日誌序號,若是發現當前機器commitIndex > lastApplied則應該將本機log[]中序號爲(lastApplied, commitIndex]的部分應用到狀態機
六、snapshot:若是作了日誌快照則會存儲快照鏡像日誌
在狀態爲Leader機器上會額外存儲:
一、nextIndex[]:針對每一個其餘節點,下一個須要發送的日誌的序號
二、matchIndex[]:針對每一個其餘節點,當前所知的和Leader匹配的最大日誌編號code
一、Leader:當前集羣中的領導者(幹活最多的),一個Raft集羣只能有一個Leader持久存在
二、Candidate:Leader候選人,當接收到多數投票後會成爲Leader
三、Follower:跟隨者,接受Leader的日誌存儲、應用請求,擁護Leader的地位
四、term:Leader的選舉週期(假設全部機器都是從0開始),在一個Raft集羣內單調遞增。在一個term週期內,只能有一個Leader當選。這個概念和paxos算法中的提案號一致,在選舉過程當中使用paxos算法,將term做爲提案號申請本身做爲Leader。
五、State machine replication:Raft使用狀態機複製來實現日誌的一致和容錯,對多個相同的狀態機施以相同的事件序列,所獲得的最終狀態也是一致的
。
六、Client:客戶端,Raft集羣的服務對象,客戶端會請求Raft集羣去存儲本身的信息來保證分佈式一致性。對象
一、RequestVote:請求其餘節點投票給本身
請求參數:
term:當前節點選舉週期term值
candidateId:當前節點編號
lastLogIndex:當前節點最後一個日誌的索引
lastLogTerm:當前節點最後一個日誌的term週期blog
返回值:
term:接收投票節點的term值
voteGranted:是否投票給該申請節點索引
二、AppendEntries:Leader節點使用該消息向其餘節點同步日誌,而且發送空消息做爲心跳包以維持Leader的統治地位。
請求參數:
term:此Leader所在的選舉週期term值
leaderId:此Leader的節點編號
prevLogIndex:當前發送的日誌的前面一個日誌的索引
prevLogTerm:當前發送的日誌的前面一個日誌的term值
entries[]:須要個節點存儲的日誌序列
leaderCommit:此Leader已經提交給狀態機的最大日誌索引號
返回值:
term:接收日誌節點的term值
success:若是接收日誌節點的log[]結構中prevLogIndex索引處含有日誌而且該日誌的term等於prevLogTerm則返回true,不然false
三、InstallSnapshot:用來向節點安裝快照的消息。快照可表明狀態機依次應用了從0到第n個日誌後的狀態,發送快照可能會減小新節點的同步週期。
參數等請參考論文。
四、以上爲論文中提到的RPC消息,除了這些,一個實際應用的集羣還會有很對其餘的消息,如:添加、刪除節點,獲取狀態機狀態等。大多都是Client與Leader的交互請求,能夠根據須要在實現算法時添加。
在Raft集羣啓動時各個節點:
一、能夠獲取整個Raft集羣的全部節點鏈接信息
二、currentTerm初始爲0、votedFor初始爲空
三、初始狀態爲Follower
四、若是是從新啓動則有快照和日誌序列,若是爲新集羣則所有爲空
五、啓動隨機定時器,定時器超時時間在[m, n]範圍內,保證請求傳輸時間 << [m, n] << 平均一個服務器兩次出現宕機的時間間隔
一旦一個定時器超時,其會轉換爲Candidate狀態。轉換時執行:
一、重置隨機定時器
二、currentTerm自增1
三、給本身投一票(votedFor設置爲當前節點)
四、向全部其餘節點發送RequestVote消息。
在Candidate狀態下接收到RequestVote的返回結果:
一、獲得了超過半數節點的贊成(voteGranted爲true)則該節點當選Leader,轉換本身的狀態到Leader,並關閉隨機定時器,週期發送AppendEntries給其餘機器以保持領導地位。
二、不管是否已經成爲Leader,接收到的RequestVote返回消息中的term > currentTerm則設置currentTerm = term,並將本身轉換爲Follower狀態,並重置隨機定時器。
三、定時器超時前沒收到大多數投票則返回上述定時器超時邏輯開始下一輪投票。
在切換到Leader狀態時,執行:
一、關閉隨機定時器
二、將nextIndex[]中的值所有設置爲本身最後一條日誌的Index + 1
三、按期向其餘機器發送AppendEntries(發送週期小於隨機定時器最小超時時間,最好超時週期爲發送週期的三倍左右):
(1)term等於此Leader的currentTerm
(2)leaderId等於此Leader的節點編號
(3)prevLogIndex等於nextIndex[]中該節點對應的值-1
(4)prevLogTerm等於此Leader的log[]中prevLogIndex對應日誌的term值
(5)若是prevLogIndex+1不是此Leader的log[]中最後一條日誌,則 entries[]取log[]中prevLogIndex以後緊接着的部分日誌。
(6)leaderCommit等於此Leader的commitIndex
在Leader狀態下接收到AppendEntries的返回結果:
一、返回消息中的term > currentTerm則設置currentTerm = term,並將本身轉換爲Follower狀態,並重置隨機定時器。
二、返回消息中success爲false則將該節點在nextIndex[]對應的值減1
三、若是接收到大多數Follower的成功反饋,則能夠提交該條AppendEntries所同步的全部日誌,和此以前的全部日誌。Leader只容許提交當前term的日誌,不容許提交以前term的日誌,可是能夠經過提交當前term的日誌達到間接提交以前term的日誌的目的。
由於Leader不容許提交以前term的日誌,所以在Leader被選舉成功時能夠發送一條無心義日誌給其餘機器,以更新日誌列表中的最大term編號,當接收到大多數返回時提交該日誌,以達到提交以前已被大多數節點接受的日誌的目的
一個節點(不管當前是什麼狀態)接收到RequestVote(term, candidateId, lastLogIndex, lastLogTerm)消息,其會作以下判斷(條件依次判斷,不知足上一條纔會進入下一條):
一、若是這條消息攜帶的term < currentTerm
則返回當前的term週期並拒絕投票請求:(currentTerm, false),並保持當前節點狀態不變。
二、若是term == currentTerm
而且該節點的votedFor不爲空而且不等於candidateId則返回當前的term週期並拒絕投票請求:(currentTerm, false),並保持當前節點狀態不變,若是voteFor等於candidateId則給該節點返回投票:(currentTerm, true)並轉換當前節點爲Follower狀態,並重置隨機定時器。
三、若是term > currentTerm
則設置currentTerm = term, voteFor置爲空並進入下一步。
四、若是該節點最後一條日誌的term
> lastLogTerm
則返回當前的term週期並拒絕投票請求:(currentTerm, false),並保持當前節點狀態不變。
五、若是該節點最後一條日誌的term
== lastLogTerm
,則比較該節點最後一條日誌的Index
和lastLogIndex
的大小。若是該節點最後一條日誌的Index
> lastLogIndex
則返回當前的term週期並拒絕投票請求:(currentTerm, false),並保持當前節點狀態不變。不然令votedFor = candidateId
給該節點返回投票:(currentTerm, true),並轉換當前節點爲Follower狀態,並重置隨機定時器。
六、若是該節點最後一條日誌的term
< lastLogTerm
,令votedFor = candidateId
給該節點返回投票:(currentTerm, true)並轉換當前節點爲Follower狀態,並重置隨機定時器。
一個節點(不管當前是什麼狀態)接收到AppendEntries(term, leaderId, prevLogIndex, prevLogTerm, entries[], leaderCommit)消息,其會作以下判斷(條件依次判斷,不知足上一條纔會進入下一條):
一、若是這條消息攜帶的term < currentTerm
則返回當前的term週期並返回:(currentTerm, false),並保持當前節點狀態不變。
二、若是term == currentTerm
,設置voteFor = leaderId。若是term > currentTerm
則設置currentTerm = term, voteFor = leaderId**。轉換當前節點爲Follower狀態,重置隨機定時器**並進入下一步。
三、若是當前節點log[]結構中prevLogIndex索引處含有日誌而且該日誌的term等於prevLogTerm則先執行如下日誌存儲而後返回(currentTerm, true),不然返回(currentTerm, false)
日誌存儲:
一、將prevLogIndex以後的日誌所有刪除,並將entries[]中的日誌依次放入log[]中prevLogIndex以後的位置裏。
二、若是leaderCommit(參數裏)
> commitIndex(每一個節點存儲裏)
則設置commitIndex = leaderCommit並將(commitIndex, leaderCommit]區間的日誌應用到狀態機上(更新commitIndex後機器會自動應用該操做)。
公共要求:
一、全部節點在全部狀態下,只要經過消息看到了消息中的term > currentTerm則設置currentTerm = term,並將本身轉換爲Follower狀態,並重置隨機定時器。
二、若是發現當前機器commitIndex > lastApplied則應該將本機log[]中序號爲(lastApplied, commitIndex]的部分應用到狀態機
參考資料: In Search of an Understandable Consensus Algorithm