Raft一致性協議

Raft 一致性協議

概念入門:http://www.jdon.com/artichect/raft.htmlhtml

原文來自:https://www.cnblogs.com/foxmailed/p/3418143.html算法

分佈式存儲系統一般經過維護多個副原本進行fault-tolerance,提升系統的availability,帶來的代價就是分佈式存儲系統的核心問題之一:維護多個副本的一致性。一致性協議就是用來幹這事的,即便在部分副本宕機的狀況下。Raft是一種較容易理解的一致性協議。一致性協議一般基於replicated state machines,即全部結點都從同一個state出發,都通過一樣的一些操做序列,最後到達一樣的state。網絡

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

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

Leader election性能

時間被分爲不少連續的隨機長度的term(一段時間),一個term由一個惟一的id標識。每一個term一開始就進行leader election:spa

    1. followers將本身維護的current_term_id加1。3d

    2. 而後將本身的狀態轉成candidate。日誌

    3. 發送RequestVoteRPC消息(帶上current_term_id) 給 其它全部serverorm

這個過程會有三種結果:

    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

相關文章
相關標籤/搜索