raft協議詳解

hiekay發表於2018-12-17

1、raft協議是什麼?

 分散式系統之於單機系統,優勢之一就是有更好的容錯性

  • 比如,一臺機器上的磁碟損壞,資料丟失,可以從另一臺機器上的磁碟恢復(分散式系統會對資料做備份)
  • 比如,叢集中某些機器當機,整個叢集還可以對外提供服務

  這是如何做到的?比較容易的一個想法就是備份(backup)。一個系統的工作模是:接受客戶端的command,系統進行處理,將處理的結果返回給客戶端。由此可見,系統裡的資料可能會因為command而變化。

  實現備份的做法之一就是複製狀態機(Repilcated State Machine,RSM),它有一個很重要的性質——確定性(deterministic)

  • 如果兩個相同的、確定性的狀態從同一狀態開始,並且以相同的順序獲得相同的輸入,那麼這兩個狀態機將會生成相同的輸出,並且結束在相同的狀態

也就是說,如果我們能按順序將command作用於狀態機,它就可以產生相同的狀態和相同的輸出

  那麼一個狀態機如何實現呢?如下圖所示(來自raft協議):

image

  上圖中,每個RSM都有一個replicated log,儲存的是來自客戶端的commands。每個RSM中replicate log中commads的順序都是相同的,狀態機按順序處理replicate log中的command,並將處理的結果返回給客戶端。由於狀態機具有確定性,因此每個狀態機的輸出和狀態都是相同的。

  上圖中有一個模組——Consensus Module剛剛沒有提及。這個模組用於保證每個server上Log的一致性

  • 如果不做任何保障,直接將commad暴力寫入,一旦伺服器當機或者出現什麼其他故障,就會導致這個Log丟失,並且無法恢復。而出現故障的可能性是很高的,這就導致系統不可用
  • raft就是Consensus Module的一個實現

因此,raft是一致性協議,是用來保障servers上副本一致性的一種演算法。

2、raft協議原理

  下面將看論文時我認為的重要點進行記錄。 

   raft協議遵循的性質

  • Election Safty 

    • 每一個任期內只能有一個領導人
  • Leader Append-Only

    • leader只能追加日誌條目,不能重寫或者刪除日誌條目
  • Log Maching

    • 如果兩個日誌條目的index和term都相同,則兩個如果日誌中,兩個條目及它們之前的日誌條目也完全相同
  • Leader Completeness

    • 如果一條日誌被commited過,那麼大於該日誌條目任期的日誌都應該包含這個點
  • State Machine Safety 

    • 如果一個server將某個特定index的日誌條目交由狀態機處理了,那麼對於其他server,交由狀態及處理的log中相同index的日誌條目應該相同

2.1 如何保證Election Safty

  raft中,只要candidate獲得多數投票,就可以成為領導人。follower在投票的時候遵循兩個條件:

  • 先到先得
  • cadidate的term大於follower的term,或者兩個term相等,但cadidate的index大於follower的index

  對於選舉結果:

  • 如果票被瓜分,產生兩個leader,這次選舉失效,進行下一輪的選舉
  • 只有一個leader的情況是被允許的

  這裡重要的一點是:如何保證在有限的時間內確定出一個候選人,而不會總是出現票被瓜分的情況?raft使用了一個比較優雅的實現方式,隨機選舉超時(randomize election timeouts)。這就使得每個server的timeout不一樣,發起新一輪選舉的時候,有些server還不是voter;或者一些符合條件的candidate還沒有參加下一輪。這種做法使得單個leader會很快被選舉出來。

2.2 如何保證Log Matching

  Leader在進行AppendEntry RPCs的時候,這個訊息中會攜帶preLogIndex和preLogTerm這兩個資訊,follower收到訊息的時候,首先判斷它最新日誌條目的index和term是否和rpc中的一樣,如果一樣,才會append.

  這就保證了新加日誌和其前一條日誌一定是一樣的。從第一條日誌起就開始遵循這個原理,很自然地可以作出這樣的推斷。

2.3 如何保證Leader Completeness

這個在raft協議中是有完整證明的,這個證明比較簡短,用反正法,我在看的時候加了一些標註。

  假設leaderU是第一個沒有包含leaderT中commitT點(T

基於這個假設,一個事實是,開始選舉的時候,U中就不包含T中的commit點由於leaderT有commitT點,說明在任期T內,有大部分的follower都有commitT的點。這就說明,一定存在一個voter,它包含了commitT點,並且它投票給了leaderU如果leaderU和這個voter有相同的term,那麼leaderU的日誌長度一定大於等於這個voter(否則會因為index小而被拒絕投票),那麼leaderU肯定包含了voter的所有資訊(這個是由Log Matching的屬性決定的,它們包含有相同的term,因此相同index的日誌條目肯定相同),leaderU中肯定包含commit點,這與假設矛盾如果leaderU和這個voter的term不同,那麼leaderU的日誌index一定大於等於voter的index。也就是說,為leaderU新增最後一條entry的那個leader因該已經包含提交的日誌(這是因為leaderU的leader的term>leaderU的term>voter的term,而leaderU是的一個不符合條件的任期,所以leaderU的leader應該是符合條件的,肯定就包含了voter的commit點),即包含commit點,根據Log Maching的原則,leaderU裡面一定包含了這一點,這與假設矛盾因此,leader completeness是可以保證的

2.4 raft協議中有一個約定,不能提交之前任期內log entry作為commit點。這是為什麼?

  這個問題主要是raft協議中commiting entries from previous term部分看的時候有點困惑,開始誤解成了這個約定是用來保證之前任期內已經被複制到大多數server卻沒有被提交的日誌在新的仍期內不會被覆蓋。 

  實際上,論文中的figrure8的過程是一個正確的過程。

image

  在(c)中,index=2並沒有被提交,在(d)中被複制了是一個正確的做法。論文想闡述的是:如果在(c)中,leader提交了這個之前任期內的entry,在(d)中依然會被覆蓋,也就是說被commit的entry覆蓋了,這是一個錯誤!因此約定“can not commit entries from previous term”

2.5 cluster membership changes

如果叢集的配置發生了變化,例如,新加入幾臺server,掛掉幾臺server。這是會影響選舉的。

例如,如果新增了伺服器,卻沒有更新原來server的配置,會導致leader election只有老機器在參與又比如,如果直接將新的配置更新到leader這個方法是有問題的。如果leader沒有及時通知到所有的伺服器,那麼存在部分server是老配置,部分server是新配置,從而可能會產生兩個leader,如下圖的情況:

image

raft的解決方法就是two phase approch,引入一個過度配置,稱為共同一致狀態。具體的做法和圖示:

leader收到更新配置請求的時候,產生一個(old,new)entry,並append進日誌通過rpc讓follower追加這條日誌如果順利,將這條日誌commit產生new entry, append到日誌通過rpc讓follower追加如果順利commit,從而完成新配置的生成

image

 考慮上述過程:

1,2兩個階段,如果過程中出現問題,大多數情況old成為新的leader

因為此時,擁有(old,new)entry的server並不是大多數如果說,已經複製給大多數server,只是未提交,那麼(old,new)是有可能被選為leader,不過這沒有什麼太大的影響,因為新的leader在被選之後,會傳送一條no-op的rpc,這個時候(old,new)就會被提交。重要的是,此時也僅有可能一個leader被選出,old不肯那個被選舉為leader.3,4,5階段,大多數情況(old,new)成為leader,例外與上條類似5階段就是new成為leader

2.6 log過長或日誌回放時間過長怎麼辦?

此時就需要做log compaction

  raft採用的方法是寫時複製的snapshot(寫是複製在linux中可以通過fork來完成)

寫時複製主要是處於效能考慮的,如果state machine資料太多,snapshot將會耗費大量的時間,也許會導致系統可用性大大降低



相關文章