raft算法總結
raft算法概述
簡介
分布式系統除了提升整個體統的性能外還有一個重要特征就是提高系統的可靠性。提供可靠性可以理解為系統中一台或多台的機器故障不會使系統不可用(或者丟失數據)。保證系統可靠性的關鍵就是多副本(即數據需要有備份),一旦有多副本,那么久面臨多副本之間的一致性問題。一致性算法正是用於解決分布式環境下多副本之間數據一致性的問題的。業界最著名的一致性算法就是大名鼎鼎的Paxos(Chubby的作者曾說過:世上只有一種一致性算法,就是Paxos)。但Paxos是出了名的難懂,而Raft正是為了探索一種更易於理解的一致性算法而產生的。
raft協議的工作原理
raft會先選舉出leader,leader完全負責replicated log的管理。leader負責接受所有客戶端更新請求,然后復制到follower節點,並在“安全”的時候執行這些請求。如果leader故障,followes會重新選舉出新的leader。
Raft將一致性拆分為幾個關鍵元素:
- Leader選舉
- 日志復制
- 安全性
raft中的三種角色
Raft將系統中的角色分為領導者(Leader)、跟從者(Follower)和候選者(Candidate)。
- Leader:接受客戶端請求,並向Follower同步請求日志,當日志同步到大多數節點上后告訴Follower提交日志。
- Follower:接受並持久化Leader同步的日志,在Leader告知日志可以提交之后,提交日志。
- Candidate:Leader選舉過程中的臨時角色。
Raft要求系統在任意時刻最多只有一個Leader,正常工作期間只有Leader和Followers。Raft算法將時間分為一個個的任期(term),每一個term的開始都是Leader選舉。在成功選舉Leader之后,Leader會在整個term內管理整個集群。如果Leader選舉失敗,該term就會因為沒有Leader而結束。
term
Raft 算法將時間划分成為任意不同長度的任期(term)。任期用連續的數字進行表示。每一個任期的開始都是一次選舉(election),一個或多個候選人會試圖成為領導人。如果一個候選人贏得了選舉,它就會在該任期的剩余時間擔任領導人。在某些情況下,選票會被瓜分,有可能沒有選出領導人,那么,將會開始另一個任期,並且立刻開始下一次選舉。Raft 算法保證在給定的一個任期最多只有一個領導人。
RPC
Raft 算法中服務器節點之間通信使用遠程過程調用(RPC),並且基本的一致性算法只需要兩種類型的 RPC,為了在服務器之間傳輸快照增加了第三種 RPC。
【RPC有三種】:
- RequestVote RPC:候選人在選舉期間發起。
- AppendEntries RPC:領導人發起的一種心跳機制,復制日志也在該命令中完成。
- InstallSnapshot RPC: 領導者使用該RPC來發送快照給落后的追隨者。
Leader選舉
選舉過程
Raft 使用心跳(heartbeat)觸發Leader選舉。當服務器啟動時,初始化為Follower。Leader向所有Followers周期性發送heartbeat。如果Follower在選舉超時時間內沒有收到Leader的heartbeat,就會等待一段隨機的時間后發起一次Leader選舉。
每一個follower都有一個時鍾,是一個隨機的值,表示的是follower等待成為leader的時間,誰的時鍾先跑完,則發起leader選舉。(眾生平等,每個人都有被選舉的權利)
具體過程:
- 增加節點本地的 current term ,切換到candidate狀態
- 投自己一票
- 並行給其他節點發送 RequestVote RPCs
- 等待其他節點的回復
在這個過程中,根據來自其他節點的消息,可能出現三種結果
- 收到majority的投票(含自己的一票),則贏得選舉,成為leader
- 被告知別人已當選,那么自行切換到follower
- 一段時間內沒有收到majority投票,則保持candidate狀態,重新發出選舉
Leader選舉的限制
- 在任一任期內,單個節點最多只能投一票
- 候選人知道的信息不能比自己的少,即能被選舉成為Leader的節點,一定包含了所有已經提交的日志條目。
- first-come-first-served 先來先得
日志復制(保證數據一致性)
大概工作流程
當有了leader,系統應該進入對外工作期了。客戶端的一切請求來發送到leader,leader來調度這些並發請求的順序,並且保證leader與followers狀態的一致性。raft中的做法是,將這些請求以及執行順序告知followers。leader和followers以相同的順序來執行這些請求,保證狀態一致。leader把請求作為日志條目(Log entries)加入到它的日志中,然后並行的向其他服務器發起 AppendEntries RPC復制日志條目。當這條日志被復制到大多數服務器上,Leader將這條日志應用到它的狀態機並向客戶端返回執行結果。
- 客戶端的每一個請求都包含被復制狀態機執行的指令。
- leader把這個指令作為一條新的日志條目添加到日志中,然后並行發起 RPC 給其他的服務器,讓他們復制這條信息。
- 假如這條日志被安全的復制(大多數的flower響應即可),領導人就應用這條日志到自己的狀態機中,並返回給客戶端。
- 如果 follower 宕機或者運行緩慢或者丟包,leader會不斷的重試,直到所有的 follower 最終都復制了所有的日志條目。
狀態機說明
共識算法的實現一般是基於復制狀態機(Replicated state machines) 。
簡單來說:相同的初識狀態 + 相同的輸入 = 相同的結束狀態。引文中有一個很重要的詞deterministic
,就是說不同節點要以相同且確定性的函數來處理輸入,而不要引入一下不確定的值,比如本地時間等。如何保證所有節點 get the same inputs in the same order
,使用replicated log是一個很不錯的注意,log具有持久化、保序的特點,是大多數分布式系統的基石。
因此,可以這么說,在raft中,leader將客戶端請求(command)封裝到一個個log entry,將這些log entries復制(replicate)到所有follower節點,然后大家按相同順序應用(apply)log entry中的command,則狀態肯定是一致的。
日志
日志在每個節點上是什么樣子的呢 :
上圖顯示,共有 8 條日志,提交了 7 條。提交的日志都將通過狀態機持久化到磁盤中,防止宕機。
日志復制的兩條保證
- 如果不同日志中的兩個條目有着相同的索引和任期號,則它們所存儲的命令是相同的(原因:leader 最多在一個任期里的一個日志索引位置創建一條日志條目,日志條目在日志的位置從來不會改變)。
- 如果不同日志中的兩個條目有着相同的索引和任期號,則它們之前的所有條目都是完全一樣的(原因:每次 RPC 發送附加日志時,leader 會把這條日志條目的前面的日志的下標和任期號一起發送給 follower,如果 follower 發現和自己的日志不匹配,那么就拒絕接受這條日志,這個稱之為一致性檢查)。
日志的不正常情況
一般情況下,Leader和Followers的日志保持一致,因此 AppendEntries 一致性檢查通常不會失敗。然而,Leader崩潰可能會導致日志不一致:舊的Leader可能沒有完全復制完日志中的所有條目。
下圖闡述了一些Followers可能和新的Leader日志不同的情況。一個Follower可能會丟失掉Leader上的一些條目,也有可能包含一些Leader沒有的條目,也有可能兩者都會發生。丟失的或者多出來的條目可能會持續多個任期。
如何保證日志的正常復制
Leader通過強制Followers復制它的日志來處理日志的不一致,Followers上的不一致的日志會被Leader的日志覆蓋。Leader為了使Followers的日志同自己的一致,Leader需要找到Followers同它的日志一致的地方,然后覆蓋Followers在該位置之后的條目。
具體的操作是:Leader會從后往前試,每次AppendEntries失敗后嘗試前一個日志條目,直到成功找到每個Follower的日志一致位置點(基於上述的兩條保證),然后向后逐條覆蓋Followers在該位置之后的條目。
詳細過程如下:
- Leader維護了每個Follower節點下一次要接收的日志的索引,即nextIndex
- Leader選舉成功后將所有Follower的nextIndex設置為自己的最后一個日志條目+1
- Leader將數據推送給Follower,如果Follower驗證失敗(nextIndex不匹配),則在下一次推送日志時縮小nextIndex,直到nextIndex驗證通過
總結一下就是:當 leader 和 follower 日志沖突的時候,leader 將校驗 follower 最后一條日志是否和 leader 匹配,如果不匹配,將遞減查詢,直到匹配,匹配后,刪除沖突的日志。這樣就實現了主從日志的一致性。
安全性
上圖按時間序列展示了Leader在提交日志時可能會遇到的問題。
- 在 (a) 中,S1 是領導者,部分的復制了索引位置 2 的日志條目。
- 在 (b) 中,S1 崩潰了,然后 S5 在任期 3 里通過 S3、S4 和自己的選票贏得選舉,然后從客戶端接收了一條不一樣的日志條目放在了索引 2 處。
- 然后到 (c),S5 又崩潰了;S1 重新啟動,選舉成功,開始復制日志。在這時,來自任期 2 的那條日志已經被復制到了集群中的大多數機器上,但是還沒有被提交。
- 如果 S1 在 (d) 中又崩潰了,S5 可以重新被選舉成功(通過來自 S2,S3 和 S4 的選票),然后覆蓋了他們在索引 2 處的日志。反之,如果在崩潰之前,S1 把自己主導的新任期里產生的日志條目復制到了大多數機器上,就如 (e) 中那樣,那么在后面任期里面這些新的日志條目就會被提交(因為S5 就不可能選舉成功)。 這樣在同一時刻就同時保證了,之前的所有老的日志條目就會被提交。
究其根本,是因為term4時的leader s1在(C)時刻提交了之前term2任期的日志。為了杜絕這種情況的發生:
某個leader選舉成功之后,不會直接提交前任leader時期的日志,而是通過提交當前任期的日志的時候“順手”把之前的日志也提交了,具體怎么實現了,在log matching部分有詳細介紹。那么問題來了,如果leader被選舉后沒有收到客戶端的請求呢,論文中有提到,在任期開始的時候發立即嘗試復制、提交一條空的log .