分佈式一致性算法-Raft學習筆記

分佈式一致性算法-Raft學習筆記

版權聲明:本文爲博主原創文章,未經博主允許不得轉載。

手動碼字不易,請大家尊重勞動成果,謝謝

作者:http://blog.csdn.net/wang_wbq

Raft在Leader選舉階段使用term編號作爲提案編號來執行paxos算法進行leader選舉,爲了防止活鎖出現,Raft算法使用了隨機定時器的策略避開了同時競爭Leader的可能。在日至提交階段,算法保證了Leader要擁有全部日誌,並且Client只能與Leader交流,提交日誌。Leader利用兩階段提交和大多數集合確認的方式來確保日誌被成功保存。其算法和Zookeeper的ZAB算法有一定相似性。

Raft算法中角色和存儲

不同於Paxos算法,Raft算法中只有一種角色。這個角色可以有三種狀態:
1、Follower
2、Candidate
3、Leader

在每臺機器上都會存儲:
1、currentTerm:當前節點所能看到的最大的term值,該值單調增加
2、votedFor:當前term裏將票投給的對象,如果尚未投票則爲空
3、log[]:日誌條目,會按順序作用於狀態機
4、commitIndex:當前節點最後一個被提交的日誌序號
5、lastApplied:當前節點最後一條被應用於狀態機的日誌序號,如果發現當前機器commitIndex > lastApplied則應該將本機log[]中序號爲(lastApplied, commitIndex]的部分應用到狀態機
6、snapshot:如果做了日誌快照則會存儲快照鏡像

在狀態爲Leader機器上會額外存儲:
1、nextIndex[]:針對每個其他節點,下一個需要發送的日誌的序號
2、matchIndex[]:針對每個其他節點,當前所知的和Leader匹配的最大日誌編號

Raft算法中的相關概念

1、Leader:當前集羣中的領導者(幹活最多的),一個Raft集羣只能有一個Leader持久存在
2、Candidate:Leader候選人,當接收到多數投票後會成爲Leader
3、Follower:跟隨者,接受Leader的日誌存儲、應用請求,擁護Leader的地位
4、term:Leader的選舉週期(假設所有機器都是從0開始),在一個Raft集羣內單調遞增。在一個term週期內,只能有一個Leader當選。這個概念和paxos算法中的提案號一致,在選舉過程中使用paxos算法,將term作爲提案號申請自己作爲Leader。
5、State machine replication:Raft使用狀態機複製來實現日誌的一致和容錯,對多個相同的狀態機施以相同的事件序列,所得到的最終狀態也是一致的
6、Client:客戶端,Raft集羣的服務對象,客戶端會請求Raft集羣去存儲自己的信息來保證分佈式一致性。

Raft算法中的消息種類

1、RequestVote:請求其他節點投票給自己
請求參數:
term:當前節點選舉週期term值
candidateId:當前節點編號
lastLogIndex:當前節點最後一個日誌的索引
lastLogTerm:當前節點最後一個日誌的term週期

返回值:
term:接收投票節點的term值
voteGranted:是否投票給該申請節點

2、AppendEntries:Leader節點使用該消息向其他節點同步日誌,並且發送空消息作爲心跳包以維持Leader的統治地位。
請求參數:
term:此Leader所在的選舉週期term值
leaderId:此Leader的節點編號
prevLogIndex:當前發送的日誌的前面一個日誌的索引
prevLogTerm:當前發送的日誌的前面一個日誌的term值
entries[]:需要個節點存儲的日誌序列
leaderCommit:此Leader已經提交給狀態機的最大日誌索引號

返回值:
term:接收日誌節點的term值
success:如果接收日誌節點的log[]結構中prevLogIndex索引處含有日誌並且該日誌的term等於prevLogTerm則返回true,否則false

3、InstallSnapshot:用來向節點安裝快照的消息。快照可代表狀態機依次應用了從0到第n個日誌後的狀態,發送快照可能會減少新節點的同步週期。
參數等請參考論文。

4、以上爲論文中提到的RPC消息,除了這些,一個實際應用的集羣還會有很對其他的消息,如:添加、刪除節點,獲取狀態機狀態等。大多都是Client與Leader的交互請求,可以根據需要在實現算法時添加。

Raft算法的事件和狀態轉換

在Raft集羣啓動時各個節點:
1、可以獲取整個Raft集羣的所有節點連接信息
2、currentTerm初始爲0、votedFor初始爲空
3、初始狀態爲Follower
4、如果是重新啓動則有快照和日誌序列,如果爲新集羣則全部爲空
5、啓動隨機定時器,定時器超時時間在[m, n]範圍內,保證請求傳輸時間 << [m, n] << 平均一個服務器兩次出現宕機的時間間隔


一旦一個定時器超時,其會轉換爲Candidate狀態。轉換時執行:
1、重置隨機定時器
2、currentTerm自增1
3、給自己投一票(votedFor設置爲當前節點)
4、向所有其他節點發送RequestVote消息。


在Candidate狀態下接收到RequestVote的返回結果:
1、得到了超過半數節點的同意(voteGranted爲true)則該節點當選Leader,轉換自己的狀態到Leader,並關閉隨機定時器,週期發送AppendEntries給其他機器以保持領導地位。

2、無論是否已經成爲Leader,接收到的RequestVote返回消息中的term > currentTerm則設置currentTerm = term,並將自己轉換爲Follower狀態,並重置隨機定時器

3、定時器超時前沒收到大多數投票則返回上述定時器超時邏輯開始下一輪投票。


在切換到Leader狀態時,執行:
1、關閉隨機定時器
2、將nextIndex[]中的值全部設置爲自己最後一條日誌的Index + 1
3、定期向其他機器發送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的返回結果:
1、返回消息中的term > currentTerm則設置currentTerm = term,並將自己轉換爲Follower狀態,並重置隨機定時器

2、返回消息中success爲false則將該節點在nextIndex[]對應的值減1

3、如果接收到大多數Follower成功反饋,則可以提交該條AppendEntries所同步的所有日誌,和此之前的所有日誌。Leader只允許提交當前term的日誌,不允許提交之前term的日誌,但是可以通過提交當前term的日誌達到間接提交之前term的日誌的目的。

因爲Leader不允許提交之前term的日誌,因此在Leader被選舉成功時可以發送一條無意義日誌給其他機器,以更新日誌列表中的最大term編號,當接收到大多數返回時提交該日誌,以達到提交之前已被大多數節點接受的日誌的目的


一個節點(無論當前是什麼狀態)接收到RequestVote(term, candidateId, lastLogIndex, lastLogTerm)消息,其會做如下判斷(條件依次判斷,不滿足上一條纔會進入下一條):
1、如果這條消息攜帶的term < currentTerm則返回當前的term週期並拒絕投票請求:(currentTerm, false),並保持當前節點狀態不變

2、如果term == currentTerm並且該節點的votedFor不爲空並且不等於candidateId則返回當前的term週期並拒絕投票請求:(currentTerm, false),並保持當前節點狀態不變,如果voteFor等於candidateId則給該節點返回投票:(currentTerm, true)並轉換當前節點爲Follower狀態,並重置隨機定時器

3、如果term > currentTerm則設置currentTerm = term, voteFor置爲空並轉換當前節點爲Follower狀態,並重置隨機定時器,進入下一步。

4、如果該節點最後一條日誌的term > lastLogTerm則返回當前的term週期並拒絕投票請求:(currentTerm, false)。

5、如果該節點最後一條日誌的term == lastLogTerm,則比較該節點最後一條日誌的IndexlastLogIndex的大小。如果該節點最後一條日誌的Index > lastLogIndex則返回當前的term週期並拒絕投票請求:(currentTerm, false)。否則令votedFor = candidateId給該節點返回投票:(currentTerm, true)。

6、如果該節點最後一條日誌的term < lastLogTerm,令votedFor = candidateId給該節點返回投票:(currentTerm, true)。


一個節點(無論當前是什麼狀態)接收到AppendEntries(term, leaderId, prevLogIndex, prevLogTerm, entries[], leaderCommit)消息,其會做如下判斷(條件依次判斷,不滿足上一條纔會進入下一條):
1、如果這條消息攜帶的term < currentTerm則返回當前的term週期並返回:(currentTerm, false),並保持當前節點狀態不變

2、如果term == currentTerm,設置voteFor = leaderId。如果term > currentTerm則設置currentTerm = term, voteFor = leaderId。轉換當前節點爲Follower狀態,重置隨機定時器並進入下一步。

3、如果當前節點log[]結構中prevLogIndex索引處含有日誌並且該日誌的term等於prevLogTerm則先執行以下日誌存儲然後返回(currentTerm, true),否則返回(currentTerm, false)

日誌存儲:
1、將prevLogIndex之後的日誌全部刪除,並將entries[]中的日誌依次放入log[]中prevLogIndex之後的位置裏。
2、如果leaderCommit(參數裏) > commitIndex(每個節點存儲裏)則設置commitIndex = leaderCommit並將(commitIndex, leaderCommit]區間的日誌應用到狀態機上(更新commitIndex後機器會自動應用該操作)。


公共要求:
1、所有節點在所有狀態下,只要通過消息看到了消息中的term > currentTerm則設置currentTerm = term,並將自己轉換爲Follower狀態,並重置隨機定時器。

2、如果發現當前機器commitIndex > lastApplied則應該將本機log[]中序號爲(lastApplied, commitIndex]的部分應用到狀態機


參考資料:
In Search of an Understandable Consensus Algorithm

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章