一、Raft算法概述
1、三種角色
Raft是一個用於管理日志一致性的協議。它將分布式一致性分解為多個子問題:Leader選舉(Leader election)、日志復制(Log replication)、安全性(Safety)、日志壓縮(Log compaction)等。同時,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而結束。
2、Term
Raft 算法將時間划分成為任意不同長度的任期(term)。任期用連續的數字進行表示。每一個任期的開始都是一次選舉(election),一個或多個候選人會試圖成為領導人。如果一個候選人贏得了選舉,它就會在該任期的剩余時間擔任領導人。在某些情況下,選票會被瓜分,有可能沒有選出領導人,那么,將會開始另一個任期,並且立刻開始下一次選舉。Raft 算法保證在給定的一個任期最多只有一個領導人。
3、RPC
Raft 算法中服務器節點之間通信使用遠程過程調用(RPC),並且基本的一致性算法只需要兩種類型的 RPC,為了在服務器之間傳輸快照增加了第三種 RPC。
【RPC有三種】:
-
RequestVote RPC:候選人在選舉期間發起。
-
AppendEntries RPC:領導人發起的一種心跳機制,復制日志也在該命令中完成。
-
InstallSnapshot RPC: 領導者使用該RPC來發送快照給太落后的追隨者。
二、Leader選舉
1、Leader選舉的過程
Raft 使用心跳(heartbeat)觸發Leader選舉。當服務器啟動時,初始化為Follower。Leader向所有Followers周期性發送heartbeat。如果Follower在選舉超時時間內沒有收到Leader的heartbeat,就會等待一段隨機的時間后發起一次Leader選舉。
每一個follower都有一個時鍾,是一個隨機的值,表示的是follower等待成為leader的時間,誰的時鍾先跑完,則發起leader選舉。
Follower將其當前term加一然后轉換為Candidate。它首先給自己投票並且給集群中的其他服務器發送 RequestVote RPC。結果有以下三種情況:
-
贏得了多數的選票,成功選舉為Leader;
-
收到了Leader的消息,表示有其它服務器已經搶先當選了Leader;
-
沒有服務器贏得多數的選票,Leader選舉失敗,等待選舉時間超時后發起下一次選舉。
2、Leader選舉的限制
在Raft協議中,所有的日志條目都只會從Leader節點往Follower節點寫入,且Leader節點上的日志只會增加,絕對不會刪除或者覆蓋。
這意味着Leader節點必須包含所有已經提交的日志,即能被選舉為Leader的節點一定需要包含所有的已經提交的日志。因為日志只會從Leader向Follower傳輸,所以如果被選舉出的Leader缺少已經Commit的日志,那么這些已經提交的日志就會丟失,顯然這是不符合要求的。
這就是Leader選舉的限制:能被選舉成為Leader的節點,一定包含了所有已經提交的日志條目。
三、日志復制(保證數據一致性)
1、日志復制的過程
Leader選出后,就開始接收客戶端的請求。Leader把請求作為日志條目(Log entries)加入到它的日志中,然后並行的向其他服務器發起 AppendEntries RPC復制日志條目。當這條日志被復制到大多數服務器上,Leader將這條日志應用到它的狀態機並向客戶端返回執行結果。
-
客戶端的每一個請求都包含被復制狀態機執行的指令。
-
leader把這個指令作為一條新的日志條目添加到日志中,然后並行發起 RPC 給其他的服務器,讓他們復制這條信息。
-
假如這條日志被安全的復制,領導人就應用這條日志到自己的狀態機中,並返回給客戶端。
-
如果 follower 宕機或者運行緩慢或者丟包,leader會不斷的重試,直到所有的 follower 最終都復制了所有的日志條目。
簡而言之,leader選舉的過程是:1、增加term號;2、給自己投票;3、重置選舉超時計時器;4、發送請求投票的RPC給其它節點。
2、日志的組成
日志由有序編號(log index)的日志條目組成。每個日志條目包含它被創建時的任期號(term)和用於狀態機執行的命令。如果一個日志條目被復制到大多數服務器上,就被認為可以提交(commit)了。
上圖顯示,共有 8 條日志,提交了 7 條。提交的日志都將通過狀態機持久化到磁盤中,防止宕機。
3、日志的一致性
(1)日志復制的兩條保證
-
如果不同日志中的兩個條目有着相同的索引和任期號,則它們所存儲的命令是相同的(原因:leader 最多在一個任期里的一個日志索引位置創建一條日志條目,日志條目在日志的位置從來不會改變)。
-
如果不同日志中的兩個條目有着相同的索引和任期號,則它們之前的所有條目都是完全一樣的(原因:每次 RPC 發送附加日志時,leader 會把這條日志條目的前面的日志的下標和任期號一起發送給 follower,如果 follower 發現和自己的日志不匹配,那么就拒絕接受這條日志,這個稱之為一致性檢查)。
(2)日志的不正常情況
一般情況下,Leader和Followers的日志保持一致,因此 AppendEntries 一致性檢查通常不會失敗。然而,Leader崩潰可能會導致日志不一致:舊的Leader可能沒有完全復制完日志中的所有條目。
下圖闡述了一些Followers可能和新的Leader日志不同的情況。一個Follower可能會丟失掉Leader上的一些條目,也有可能包含一些Leader沒有的條目,也有可能兩者都會發生。丟失的或者多出來的條目可能會持續多個任期。
(3)如何保證日志的正常復制
Leader通過強制Followers復制它的日志來處理日志的不一致,Followers上的不一致的日志會被Leader的日志覆蓋。Leader為了使Followers的日志同自己的一致,Leader需要找到Followers同它的日志一致的地方,然后覆蓋Followers在該位置之后的條目。
具體的操作是:Leader會從后往前試,每次AppendEntries失敗后嘗試前一個日志條目,直到成功找到每個Follower的日志一致位置點(基於上述的兩條保證),然后向后逐條覆蓋Followers在該位置之后的條目。
總結一下就是:當 leader 和 follower 日志沖突的時候,leader 將校驗 follower 最后一條日志是否和 leader 匹配,如果不匹配,將遞減查詢,直到匹配,匹配后,刪除沖突的日志。這樣就實現了主從日志的一致性。
四、安全性
Raft增加了如下兩條限制以保證安全性:
-
擁有最新的已提交的log entry的Follower才有資格成為leader。
-
Leader只能推進commit index來提交當前term的已經復制到大多數服務器上的日志,舊term日志的提交要等到提交當前term的日志來間接提交(log index 小於 commit index的日志被間接提交)。
五、日志壓縮
在實際的系統中,不能讓日志無限增長,否則系統重啟時需要花很長的時間進行回放,從而影響可用性。Raft采用對整個系統進行snapshot來解決,snapshot之前的日志都可以丟棄(以前的數據已經落盤了)。
每個副本獨立的對自己的系統狀態進行snapshot,並且只能對已經提交的日志記錄進行snapshot。
【Snapshot中包含以下內容】:
-
日志元數據,最后一條已提交的 log entry的 log index和term。這兩個值在snapshot之后的第一條log entry的AppendEntries RPC的完整性檢查的時候會被用上。
-
系統當前狀態。
當Leader要發給某個日志落后太多的Follower的log entry被丟棄,Leader會將snapshot發給Follower。或者當新加進一台機器時,也會發送snapshot給它。發送snapshot使用InstalledSnapshot RPC。
做snapshot既不要做的太頻繁,否則消耗磁盤帶寬, 也不要做的太不頻繁,否則一旦節點重啟需要回放大量日志,影響可用性。推薦當日志達到某個固定的大小做一次snapshot。
做一次snapshot可能耗時過長,會影響正常日志同步。可以通過使用copy-on-write技術避免snapshot過程影響正常日志同步。
六、成員變更
1、常規處理成員變更存在的問題
我們先將成員變更請求當成普通的寫請求,由領導者得到多數節點響應后,每個節點提交成員變更日志,將從舊成員配置(Cold)切換到新成員配置(Cnew)。但每個節點提交成員變更日志的時刻可能不同,這將造成各個服務器切換配置的時刻也不同,這就有可能選出兩個領導者,破壞安全性。
考慮以下這種情況:集群配額從 3 台機器變成了 5 台,可能存在這樣的一個時間點,兩個不同的領導者在同一個任期里都可以被選舉成功(雙主問題),一個是通過舊的配置,一個通過新的配置。
簡而言之,成員變更存在的問題是增加或者減少的成員太多了,導致舊成員組和新成員組沒有交集,因此出現了雙主。
2、解決方案之一階段成員變更
Raft解決方法是每次成員變更只允許增加或刪除一個成員(如果要變更多個成員,連續變更多次)。
七、關於Raft的一些面試題
1、Raft分為哪幾個部分?
主要是分為leader選舉、日志復制、日志壓縮、成員變更等。
2、Raft中任何節點都可以發起選舉嗎?
Raft發起選舉的情況有如下幾種:
-
剛啟動時,所有節點都是follower,這個時候發起選舉,選出一個leader;
-
當leader掛掉后,時鍾最先跑完的follower發起重新選舉操作,選出一個新的leader。
-
成員變更的時候會發起選舉操作。
3、Raft中選舉中給候選人投票的前提?
Raft確保新當選的Leader包含所有已提交(集群中大多數成員中已提交)的日志條目。這個保證是在RequestVoteRPC階段做的,candidate在發送RequestVoteRPC時,會帶上自己的last log entry的term_id和index,follower在接收到RequestVoteRPC消息時,如果發現自己的日志比RPC中的更新,就拒絕投票。日志比較的原則是,如果本地的最后一條log entry的term id更大,則更新,如果term id一樣大,則日志更多的更大(index更大)。
4、Raft網絡分區下的數據一致性怎么解決?
發生了網絡分區或者網絡通信故障,使得Leader不能訪問大多數Follwer了,那么Leader只能正常更新它能訪問的那些Follower,而大多數的Follower因為沒有了Leader,他們重新選出一個Leader,然后這個 Leader來接受客戶端的請求,如果客戶端要求其添加新的日志,這個新的Leader會通知大多數Follower。如果這時網絡故障修復 了,那么原先的Leader就變成Follower,在失聯階段這個老Leader的任何更新都不能算commit,都回滾,接受新的Leader的新的更新(遞減查詢匹配日志)。
5、Raft數據一致性如何實現?
主要是通過日志復制實現數據一致性,leader將請求指令作為一條新的日志條目添加到日志中,然后發起RPC 給所有的follower,進行日志復制,進而同步數據。
6、Raft的日志有什么特點?
日志由有序編號(log index)的日志條目組成,每個日志條目包含它被創建時的任期號(term)和用於狀態機執行的命令。
7、Raft和Paxos的區別和優缺點?
-
Raft的leader有限制,擁有最新日志的節點才能成為leader,multi-paxos中對成為Leader的限制比較低,任何節點都可以成為leader。
-
Raft中Leader在每一個任期都有Term號。
8、Raft prevote機制?
Prevote(預投票)是一個類似於兩階段提交的協議,第一階段先征求其他節點是否同意選舉,如果同意選舉則發起真正的選舉操作,否則降為Follower角色。這樣就避免了網絡分區節點重新加入集群,觸發不必要的選舉操作。
9、Raft里面怎么保證數據被commit,leader宕機了會怎樣,之前的沒提交的數據會怎樣?
leader會通過RPC向follower發出日志復制,等待所有的follower復制完成,這個過程是阻塞的。
老的leader里面沒提交的數據會回滾,然后同步新leader的數據。
10、Raft日志壓縮是怎么實現的?增加或刪除節點呢??
在實際的系統中,不能讓日志無限增長,否則系統重啟時需要花很長的時間進行回放,從而影響可用性。Raft采用對整個系統進行snapshot來解決,snapshot之前的日志都可以丟棄(以前的數據已經落盤了)。
snapshot里面主要記錄的是日志元數據,即最后一條已提交的 log entry的 log index和term。
11、Raft里面的lease機制是什么,有什么作用?
租約機制確保了一個時刻最多只有一個leader,避免只使用心跳機制產生雙主的問題。中心思想是每次租約時長內只有一個節點獲得租約、到期后必須重新頒發租約。
12、Raft協議的leader選舉,正常情況下,網絡抖動造成follower發起leader選舉,且該follower的Term比現有leader高,集群中所有結點的日志信息當前一致,這種情況下會選舉成功嗎?
參考網絡分區的情況。