Raft一致性協議


分布式存儲系統通常通過維護多個副本來進行fault-tolerance,提高系統的availability,帶來的代價就是分布式存儲系統的核心問題之一:維護多個副本的一致性。一致性協議就是用來干這事的,即使在部分副本宕機的情況下。Raft是一種較容易理解的一致性協議。一致性協議通常基於replicated state machines,即所有結點都從同一個state出發,都經過同樣的一些操作序列,最后到達同樣的state。

為了便於理解,Raft大概將整個過程分為三個階段,leader election,log replication和commit(safety)。

每個server處於三個狀態:leader,follower,candidate。正常情況下,所有server中只有一個是leader,其它的都是follower。server之間通過RPC消息通信。follower不會主動發起RPC消息。leader和candidate(選主的時候)會主動發起RPC消息。

Leader election

時間被分為很多連續的隨機長度的term(一段時間),一個term由一個唯一的id標識。每個term一開始就進行leader election:

    1. followers將自己維護的current_term_id加1。

    2. 然后將自己的狀態轉成candidate。

    3. 發送RequestVoteRPC消息(帶上current_term_id) 給 其它所有server

這個過程會有三種結果:

    1. 自己被選成了主。當收到了majority的投票后,狀態切成leader,並且定期給其它的所有server發心跳消息(其實是不帶log的AppendEntriesRPC)以告訴對方自己是current_term_id所標識的term的leader。每個term最多只有一個leader,term id作為logical clock,在每個RPC消息中都會帶上,用於檢測過期的消息,比如自己是一個過期的leader(term id更小的leader)。當一個server收到的RPC消息中的rpc_term_id比本地的current_term_id更大時,就更新current_term_id為rpc_term_id,並且如果當前state為leader或者candidate時,將自己的狀態切成follower。如果rpc_term_id比本地的current_term_id更小,則拒絕這個RPC消息。

    2. 別人成為了主。如1所述,當candidate在等待投票的過程中,收到了大於或者等於本地的current_term_id的聲明對方是leader的AppendEntriesRPC時,則將自己的state切成follower,並且更新本地的current_term_id。

    3. 沒有選出主。當投票被瓜分,沒有任何一個candidate收到了majority的vote時,沒有leader被選出。這種情況下,每個candidate等待的投票的過程就超時了,接着candidates都會將本地的current_term_id再加1,發起RequestVoteRPC進行新一輪的leader election。

投票策略:

每個server只會給每個term投一票,具體的是否同意和后續的Safety有關。

 

當投票被瓜分后,所有的candidate同時超時,然后有可能進入新一輪的票數被瓜分,為了避免這個問題,Raft采用一種很簡單的方法:每個candidate的election timeout從150ms-300ms之間隨機取,那么第一個超時的candidate就可以發起新一輪的leader election,帶着最大的term_id給其它所有server發送RequestVoteRPC消息,從而自己成為leader,然后給他們發送心跳消息以告訴他們自己是主。

Log Replication

當leader被選出來后,leader就可以接受客戶端發來的請求了,每個請求包含一條需要被replicated state machines執行的命令。leader會把它作為一個log entry,append到它的日志中,然后給其它的server發AppendEntriesRPC。當leader確定一個log entry被safely replicated了,就apply這條log entry到狀態機中然后返回結果給客戶端。如果某個follower宕機了或者運行的很慢,或者網絡丟包了,則會一直給這個follower發AppendEntriesRPC直到日志一致。

當一條日志是commited時,leader才能決定將它apply到狀態機中。Raft保證一條commited的log entry已經持久化了並且會被所有的server執行。

當一個新的leader選出來的時候,它的日志和其它的follower的日志可能不一樣,這個時候,就需要一個機制來保證日志是一致的。如下圖所示,一個新leader產生時,集群狀態可能如下:

image

最上面這個是新leader,a~f是follower,每個格子代表一條log entry,格子內的數字代表這個log entry是在哪個term上產生的。

新leader產生后,log就以leader上的log為准。其它的follower要么少了數據比如b,要么多了數據,比如d,要么既少了又多了數據,比如f。

需要有一種機制來讓leader和follower對log達成一致,leader會為每個follower維護一個nextIndex,表示leader給各個follower發送的下一條log entry在log中的index,初始化為leader

的最后一條log entry的下一個位置。leader給follower發送AppendEntriesRPC消息,帶着(term_id, (nextIndex-1)), term_id即(nextIndex-1)這個槽位的log entry的term_id,follower接收到AppendEntriesRPC后,會從自己的log中找是不是存在這樣的log entry,如果不存在,就給leader回復拒絕消息,然后leader則將nextIndex減1,再重復,知道AppendEntriesRPC消息被接收。

以leader和b為例:

初始化,nextIndex為11,leader給b發送AppendEntriesRPC(6,10),b在自己log的10號槽位中沒有找到term_id為6的log entry。則給leader回應一個拒絕消息。接着,leader將nextIndex減一,變成10,然后給b發送AppendEntriesRPC(6, 9),b在自己log的9號槽位中同樣沒有找到term_id為6的log entry。循環下去,直到leader發送了AppendEntriesRPC(4,4),b在自己log的槽位4中找到了term_id為4的log entry。接收了消息。隨后,leader就可以從槽位5開始給b推送日志了。

Safety

1.哪些follower有資格成為leader?

Raft保證被選為新leader的server擁有所有的已經committed的log entry,這與ViewStamped Replication不同,后者不需要這個保證,而是通過其他機制從follower拉取自己沒有的commited的log entry。

這個保證是在RequestVoteRPC階段做的,candidate在發送RequestVoteRPC時,會帶上自己的最后一條log entry的term_id和index,server在接收到RequestVoteRPC消息時,如果發現自己的日志比RPC中的更新,就拒絕投票。日志比較的原則是,如果本地的最后一條log entry的term id更大,則更新,如果term id一樣大,則日志更多的更大(index更大)。

2. 哪些log entry被認為是commited?

 

Image[6]

 

兩種情況:

1. leader正在replicate當前term即term2的log entry給其它follower,一旦leader確認了這條log entry被majority寫盤了,這條log entry就被認為是committed。如圖a,S1作為當前term即term2的leader,log index為2的日志被majority寫盤了,這條log entry被認為是commited

2. leader正在replicate更早的term的log entry給其它follower。圖b的狀態是這么出來的:

S1作為term2的leader,給S1和S2 replicate完log index=2的日志后crash,當前狀態為:

S1 1 2 宕機

S2 1 2

S3 1

S4 1

S5 1

S5被選為term3的leader(由於S5的最后一條log entry比S3,S4的最后一條log entry更新或一樣新,接收到S3,S4,S5的投票),自己產生了一條term3的日志,沒有給任何人復制,就crash了,當前狀態如下:

S1 1 2

S2 1 2

S3 1

S4 1

S5 1 3 宕機

接着S1重啟后,又被選為term4的leader(接收到S1,S2,S3的投票,文中沒有指出S4?),然后S1給S3復制了log index為2的log entry,當前狀態如下:

S1 1 2

S2 1 2

S3 1 2

S4 1

S5 1 3 宕機

這個時候S5重啟,被選為了term5的主(接收了S2,S3,S4,S5的投票),那么S5會把log index為2的日志3復制給其它server,那么日志2就被overwrite了。

所以雖然這里日志2被majority的server寫盤了,但是並不代表它是commited的。

 

對commit加一個限制:主的當前term的至少一條log entry被majority寫盤

如:c圖中,就是主的當前term 4的一條log entry被majority寫盤了,假設這個時候S1宕機了,S5是不可能變成主的。因為S2和S3的log entry的term為4,比S5的3大。

 

關於算法的正確性證明見:Raft implementations

Log Compaction

在實際的系統中,不能讓日志無限增長,否則系統重啟時需要花很長的時間進行回放,從而影響availability。Raft采用對整個系統進行snapshot來處理,snapshot之前的日志都可以丟棄。

snapshot技術在Chubby和ZooKeeper系統中都有采用。

3TN$U25}D`IMYP9NGXXIO~Q

每個server獨立的對自己的系統狀態進行snapshot,並且只能對已經committed log entry(已經apply到了狀態機)進行snapshot,snapshot有一些元數據,包括last_included_index,即snapshot覆蓋的最后一條commited log entry的 log index,和last_included_term,即這條日志的termid。這兩個值在snapshot之后的第一條log entry的AppendEntriesRPC的consistency check的時候會被用上,之前講過。一旦這個server做完了snapshot,就可以把這條記錄的最后一條log index及其之前的所有的log entry都刪掉。

snapshot的缺點就是不是增量的,即使內存中某個值沒有變,下次做snapshot的時候同樣會被dump到磁盤。

當leader需要發給某個follower的log entry被丟棄了(因為leader做了snapshot),leader會將snapshot發給落后太多的follower。或者當新加進一台機器時,也會發送snapshot給它。

發送snapshot使用新的RPC,InstalledSnapshot。

做snapshot有一些需要注意的性能點,1. 不要做太頻繁,否則消耗磁盤帶寬。 2. 不要做的太不頻繁,否則一旦server重啟需要回放大量日志,影響availability。系統推薦當日志達到某個固定的大小做一次snapshot。3. 做一次snapshot可能耗時過長,會影響正常log entry的replicate。這個可以通過使用copy-on-write的技術來避免snapshot過程影響正常log entry的replicate。

 

Cluster membership changes

Raft將有server加入集群或者從集群中刪除也納入一致性協議中考慮,避免由於下線老集群上線新集群而引起的不可用。集群的成員列表重配置也是一條log entry,log內容包含了集群成員列表。

老集群配置用Cold表示,新集群配置用Cnew表示。

當集群成員配置改變時,leader收到人工發出的重配置命令從Cold切成Cnew,leader 給其它server復制一條特殊的log entry給其它的server,內容包括Cold∪Cnew,一旦server收到了這條特殊的配置log entry,其后的log entry會被replicate到Cold∪Cnew中,一條log entry被認為是committed的需要滿足這條日志既被Cold的majority寫盤,也被Cnew的majority寫盤。一旦Cold∪Cnew這條log entry被確認為committed,leader就會產生一條只包含了Cnew的log entry,同樣復制給所有server,server收到log后,老集群的server就可以自動下線了。

Performance

image

 

橫坐標代表沒有leader的ms數,每條線代表election timeout的隨機取值區間。

上圖說明只要給個5ms的區間,就能避免反復的投票被瓜分。超過10s沒有leader的情況都是因為投票被瓜分的情況。

150-150ms的election timeout區間,沒有主的時間平均287ms。

系統推薦使用150ms~300ms

 

參考資料:

In Search of an Understandable Consensus Algorithm


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM