raft協議


一、Raft一致性算法

  Eureka:Peer To Peer,每個節點的地位都是均等的,每個節點都可以接收寫入請求,每個節點接收請求之后,進行請求打包處理,異步化延遲一點時間,將數據同步給 Eureka 集群當中的其他節點。任何一台節點宕機之后,理論上應該是不影響集群運行的,都可以從其他節點獲取注冊表信息。
  Etcd、Consul,Zookeeper, Nacos,其中的 CP 模式也是基於 Raft 協議 來實現分布式一致性算法的。當然 Zookeeper 在 Raft 協議基礎上做了一些改良,使用的 ZAB 分布式一致性協議來實現的。
  1. 基本概念
1. leader選舉:當已有的leader故障時必須選出一個新的leader。
2. 日志復制:leader接受來自客戶端的命令,記錄為日志,並 復制給集群中的其他服務器,並強制其他節點的日志與leader保持一致。
3. 安全safety措施:通過一些措施確保系統的安全性,如 確保所有狀態機按照相同順序執行相同命令的措施。
服務器三種角色:leader、candidate、follower
1. follower只會響應leader和candidate的請求,
2. 客戶端的請求則全部由leader處理,
3. 有客戶端請求了一個follower也會將請求重定向到leader。
 
    集群剛啟動時,所有節點都是follower,之后在time out信號的驅使下,follower會轉變成candidate去拉取選票,獲得大多數選票后就會成為leader,這時候如果其他候選人發現了新的leader已經誕生,就會自動轉變為follower;而如果另一個time out信號發出時,還沒有選舉出leader,將會重新開始一次新的選舉。
  Raft協議中,將時間分成了一些任意長度的時間片,稱為term,term使用連續遞增的編號的進行識別。
每一個term都從新的選舉開始,candidate們會努力爭取稱為leader。一旦獲勝,它就會在剩余的term時間內保持leader狀態,在某些情況下(如term3)選票可能被多個candidate瓜分,形不成多數派,因此term可能直至結束都沒有leader,下一個term很快就會到來重新發起選舉。
 
  term也起到了系統中邏輯時鍾的作用,每一個server都存儲了當前term編號,在server之間進行交流的時候就會帶有該編號,如果一個server的編號小於另一個的,那么它會將自己的編號更新為較大的那一個;如果leader或者candidate發現自己的編號不是最新的了,就會自動轉變為follower;如果接收到的請求的term編號小於自己的當前term將會拒絕執行。
 
server之間的交流是通過RPC進行的。只需要實現兩種RPC就能構建一個基本的Raft集群:
* RequestVote RPC:它由選舉過程中的candidate發起,用於拉取選票
* AppendEntries RPC:它由leader發起,用於復制日志或者發送心跳信號。

2. leader選舉

  Raft通過 心跳機制發起leader選舉。節點都是從follower狀態開始的, 如果收到了來自leader或candidate的RPC,那它就 保持follower狀態,避免爭搶成為candidate。Leader會發送空的AppendEntries RPC作為心跳信號來確立自己的地位,如果 follower一段時間(election timeout)沒有收到心跳,它就會認為leader已經掛了, 發起新的一輪選舉。選舉發起后,一個follower會增加自己的當前term編號並轉變為candidate。它會首先投自己一票,然后向其他所有節點並行發起RequestVote RPC。
candidate狀態將可能發生如下三種變化:
  1. 贏得選舉,成為leader(如果它在一個term內收到了大多數的選票,將會在接下的剩余term時間內稱為leader,然后就可以通過發送心跳確立自己的地位。每一個server在一個term內只能投一張選票,並且按照先到先得的原則投出)
  2.  其他server成為leader(在等待投票時,可能會收到其他server發出AppendEntries RPC心跳信號,說明其他leader已經產生了。這時通過比較自己的term編號和RPC過來的term編號,如果比對方大,說明leader的term過期了,就會拒絕該RPC,並繼續保持候選人身份; 如果對方編號不比自己小,則承認對方的地位,轉為follower.)
  3.  選票被瓜分,選舉失敗(如果沒有candidate獲取大多數選票, 則沒有leader產生, candidate們等待超時后發起另一輪選舉. 為了防止下一次選票還被瓜分,必須采取一些額外的措施, raft采用隨機election timeout的機制防止選票被持續瓜分。通過將timeout隨機設為一段區間上的某個值, 因此很大概率會有某個candidate率先超時然后贏得大部分選票.)
  1. 日志復制過程
  客戶端提交每一條命令都會被按順序記錄到leader的日志中,每一條命令都包含term編號和順序索引,然后向其他節點並行發送AppendEntries RPC用以復制命令(如果命令丟失會不斷重發), 當復制成功也就是大多數節點成功復制后,leader就會提交命令,即執行該命令並且將執行結果返回客戶端,raft保證已經提交的命令最終也會被其他節點成功執行。leader會保存有當前已經提交的最高日志編號。順序性確保了相同日志索引處的命令是相同的,而且之前的命令也是相同的。當發送AppendEntries RPC時,會包含leader上一條剛處理過的命令,接收節點如果發現上一條命令不匹配,就會拒絕執行。
  特殊故障:如果leader崩潰了,它所記錄的日志沒有完全被復制,會造成日志不一致的情況,follower相比於當前的leader可能會丟失幾條日志,也可能會額外多出幾條日志,這種情況可能會持續幾個term。
    在上圖中,框內的數字是term編號,a、b丟失了一些命令,c、d多出來了一些命令,e、f既有丟失也有增多,這些情況都有可能發生。比如f可能發生在這樣的情況下:f節點在term2時是leader,在此期間寫入了幾條命令,然后在提交之前崩潰了,在之后的term3中它很快重啟並再次成為leader,又寫入了幾條日志,在提交之前又崩潰了,等他蘇醒過來時新的leader來了,就形成了上圖情形。在Raft中,leader通過強制follower復制自己的日志來解決上述日志不一致的情形,那么沖突的日志將會被重寫。為了讓日志一致,先找到最新的一致的那條日志(如f中索引為3的日志條目),然后把follower之后的日志全部刪除,leader再把自己在那之后的日志一股腦推送給follower,這樣就實現了一致。而尋找該條日志,可以通過AppendEntries RPC,該RPC中包含着下一次要執行的命令索引,如果能和follower的當前索引對上,那就執行,否則拒絕,然后leader將會逐次遞減索引,直到找到相同的那條日志。
    然而這樣也還是會有問題,比如某個follower在leader提交時宕機了,也就是少了幾條命令,然后它又經過選舉成了新的leader,這樣它就會強制其他follower跟自己一樣,使得其他節點上剛剛提交的命令被刪除,導致客戶端提交的一些命令被丟失了
Raft通過為選舉過程添加一個限制條件,解決了上面提出的問題,該限制確保leader包含之前term已經提交過的所有命令。Raft通過投票過程確保 只有擁有全部已提交日志的candidate能成為leader。由於candidate為了拉選票需要通過RequestVote RPC聯系其他節點,而之前提交的命令至少會存在於其中某一個節點上,因此只要candidate的日志至少和其他大部分節點的一樣新就可以了, follower如果收到了不如自己新的candidate的RPC,就會將其丟棄.
    還可能會出現另外一個問題, 如果命令已經被復制到了大部分節點上,但是還沒來的及提交就崩潰了,這樣后來的leader應該完成之前term未完成的提交. Raft通過讓leader統計當前term內還未提交的命令已經被復制的數量是否半數以上, 然后進行提交.
  1. 日志壓縮
  隨着日志大小的增長,會占用更多的內存空間,處理起來也會耗費更多的時間,對系統的可用性造成影響,因此必須想辦法壓縮日志大小。Snapshotting是最簡單的壓縮方法,系統的全部狀態會寫入一個snapshot保存起來,然后丟棄截止到snapshot時間點之前的所有日志。
  每一個server都有自己的snapshot,它只保存當前狀態,如上圖中的當前狀態為x=0,y=9,而last included index和last included term代表snapshot之前最新的命令,用於AppendEntries的狀態檢查。
  雖然每一個server都保存有自己的snapshot,但是當follower嚴重落后於leader時,leader需要把自己的snapshot發送給follower加快同步,此時用到了一個新的RPC:InstallSnapshot RPC。follower收到snapshot時,需要決定如何處理自己的日志,如果收到的snapshot包含有更新的信息,它將丟棄自己已有的日志,按snapshot更新自己的狀態,如果snapshot包含的信息更少,那么它會丟棄snapshot中的內容,但是自己之后的內容會保存下來。

二、zab對比raft

1. 上一輪次的leader的殘留的數據:
  Raft:對於之前term的過半或未過半復制的日志采取的是保守的策略,全部判定為未提交,只有當當前term的日志過半了,才會順便將之前term的日志進行提交
  ZooKeeper:采取激進的策略,對於所有過半還是未過半的日志都判定為提交,都將其應用到狀態機中
2. 怎么阻止上一輪次的leader假死的問題
  Raft的copycat實現為:每個follower開通一個復制數據的RPC接口,誰都可以連接並調用該接口,所以Raft需要來阻止上一輪次的leader的調用。每一輪次都會有對應的輪次號,用來進行區分,Raft的輪次號就是term,一旦舊leader對follower發送請求,follower會發現當前請求term小於自己的term,則直接忽略掉該請求,自然就解決了舊leader的干擾問題
  ZooKeeper:一旦server進入leader選舉狀態則該follower會關閉與leader之間的連接,所以舊leader就無法發送復制數據的請求到新的follower了,也就無法造成干擾了
3. raft流程
1. client連接follower或者leader,如果連接的是follower則,follower會把client的請求(寫請求,讀請求則自身就可以直接處理)轉發到leader
2. leader接收到client的請求,將該請求轉換成entry,寫入到自己的日志中,得到在日志中的index,會將該entry發送給所有的follower(實際上是批量的entries)
3. follower接收到leader的AppendEntries RPC請求之后,會將leader傳過來的批量entries寫入到文件中(通常並沒有立即刷新到磁盤),然后向leader回復OK
4. leader收到過半的OK回復之后,就認為可以提交了,然后應用到leader自己的狀態機中,leader更新commitIndex,應用完畢后回復客戶端
5. 在下一次leader發給follower的心跳中,會將leader的commitIndex傳遞給follower,follower發現commitIndex更新了則也將commitIndex之前的日志都進行提交和應用到狀態機中
4. zab流程
1. client連接follower或者leader,如果連接的是follower則,follower會把client的請求(寫請求,讀請求則自身就可以直接處理)轉發到leader
2. leader接收到client的請求,將該請求轉換成一個議案,寫入到自己的日志中,會將該議案發送給所有的follower(這里只是單個發送)
3. follower接收到leader的議案請求之后,會將該議案寫入到文件中(通常並沒有立即刷新到磁盤),然后向leader回復OK
4. leader收到過半的OK回復之后,就認為可以提交了,leader會向所有的follower發送一個提交上述議案的請求,同時leader自己也會提交該議案,應用到自己的狀態機中,完畢后回復客戶端
5. follower在接收到leader傳過來的提交議案請求之后,對該議案進行提交,應用到狀態機中
5. 連續性日志:
  如果是連續性日志,則leader在分發給各個follower的時候,只需要記錄每個follower目前已經同步的index即可,如Raft
  如果是非連續性日志,如ZooKeeper,則leader需要為每個follower單獨保存一個隊列,用於存放所有的改動,如ZooKeeper,一旦是隊列就引入了一個問題即順序性問題,即follower在和leader進行同步的時候,需要阻塞leader處理寫請求,先將follower和leader之間的差異數據先放入隊列,完成之后,解除阻塞,允許leader處理寫請求,即允許往該隊列中放入新的寫請求,從而來保證順序性
  • 正常情況下:
  Raft對請求先轉換成entry,復制時,也是按照leader中log的順序復制給follower的,對entry的提交是按index進行順序提交的,是可以保證順序的。
  ZooKeeper在提交議案的時候也是按順序寫入各個follower對應在leader中的隊列,然后follower必然是按照順序來接收到議案的,對於議案的過半提交也都是一個個來進行的。
  • 異常情況:follower掛掉又重啟的過程:
  Raft:重啟之后,由於leader的AppendEntries RPC調用,識別到leader,leader仍然會按照leader的log進行順序復制,也不用關心在復制期間新的添加的日志,在下一次同步中自動會同步。
  ZooKeeper:重啟之后,需要和當前leader數據之間進行差異的確定,同時期間又有新的請求到來,所以需要暫時獲取leader數據的讀鎖,禁止此期間的數據更改,先將差異的數據先放入隊列,差異確定完畢之后,還需要將leader中已提交的議案和未提交的議案也全部放入隊列,即ZooKeeper的2個集合數據,讀寫鎖。
 
  • 會不會有亂序的問題?
  Raft:Raft對於之前term的entry被過半復制暫不提交,只有當本term的數據提交了才能將之前term的數據一起提交,也是能保證順序的
  ZooKeeper:ZooKeeper每次leader選舉之后都會進行數據同步,不會有亂序問題
總結:2PC (兩階段提交) + 集群過半節點寫機制

三、分區

  目前ZooKeeper和Raft都是過半即可,所以對於分區是容忍的。如5台機器,分區發生后分成2部分,一部分3台,另一部分2台,這2部分之間無法相互通信
  其中,含有3台的那部分,仍然可以湊成一個過半,仍然可以對外提供服務,但是它不允許有server再掛了,一旦再掛一台則就全部不可用了。
含有2台的那部分,則無法提供服務,即只要連接的是這2台機器,都無法執行相關請求。
 
參考:
 

 


免責聲明!

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



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