![](http://static.javashuo.com/static/loading.gif)
Raft算法簡述java
Raft概要
Raft算法是一種用於管理Replicated Log的共識算法,其算法結果與效率與Multi-Paxos一致,可是在算法的設計結構上與Paxos算法是不一樣的,Raft算法更加便於理解和實現,主要有如下兩點:node
Raft算法將leader選舉,日誌複製以及安全共識要素分離出來,並強化一致性,即強leader模型,集羣服務節點以leader節點爲主來實現實現一系列值的共識和各節點日誌的一致,強leader這樣作的目的是爲了減小集羣其餘服務節點必須考慮的狀態,換言之,就是若是集羣服務存在leader節點,那麼對非leader節點服務而言,我只考慮leader節點是存活的狀況下,保證我本身當前服務節點狀態不會發生變動.python
其次,Raft算法提供成員變動機制,嚴格來講是引入單節點變動機制來解決集羣中存在「腦裂」狀況(集羣出現多個leader節點)git
Raft集羣節點狀態
Follower狀態: 集羣服務中普通的節點,主要職責有如下方面: 一是負責接收和處理leader節點的消息;二是負責維持與leader節點之間的心跳檢測,以感知leader節點是處於可用狀態;三是經過心跳檢測獲取leader節點不可用狀態時,將會推薦本身做爲候選節點而發起投票選舉操做.github
Candidate狀態: 當前集羣節點爲候選leader服務節點,將會向集羣其餘服務節點發起RPC消息的投票請求,通知其餘集羣服務節點來進行投票,若是超過半數投票那麼當前服務節點將晉升成爲leader節點.redis
Leader狀態: 經過選舉投票成爲leader節點,此時主要的工做職責有三:一是負責接收客戶端的全部寫入請求,包括集羣服務節點的寫入請求也將會轉發到leader節點來處理;二是同步client的數據操做日誌(binlog/oplog)到各個follower服務節點,以保證數據的一致性;三是發送心跳檢測以便於各個follower節點可以感知到leader節點是處於可用狀態而不會發起投票選舉操做.算法
Raft算法參考學習
分佈式鍵值對存儲系統Etcd: https://etcd.io/docs/v3.4.0/數據庫
基於Go語言實現的分佈式註冊與配置中心Consul: https://www.consul.io/docs安全
Raft開源產品總覽: https://raft.github.io/微信
Raft學習指南: http://thesecretlivesofdata.com/raft/
Raft算法核心原理
集羣強Leader選舉
leader選舉要素
任期Term: leader選舉存在任期Term,每次完成leader選舉會更新一次任期Term.
超時Timeout: 具有兩種含義,即包含leader心跳檢測超時以及候選節點等待選舉結果超時.
leader選舉過程
-
初始化狀態
![](http://static.javashuo.com/static/loading.gif)
-
投票請求
![](http://static.javashuo.com/static/loading.gif)
投票請求響應
當候選服務節點A發起RPC投票請求的時候,會在當前的服務節點設置一個等待選舉結果的隨機超時時間timeout,那麼存在兩種狀況:
若是是在timeout時間內,集羣的服務節點B以及C接收到候選節點A的RPC投票請求而且此時尚未接收到其餘服務節點的投票請求,那麼就會更新當前的任期編號爲1,同時將投票給A服務節點並給予響應,即:
![](http://static.javashuo.com/static/loading.gif)
-
若是在超時時間內沒有得到半數投票,那麼原先的選舉會失效並將會從新發起投票選舉,若是未超時,A服務節點接將收到其餘服務節點投票響應併爲本身的選票進行相應的計算增長,即:
![](http://static.javashuo.com/static/loading.gif)
-
A節點得到半數投票成爲leader節點
![](http://static.javashuo.com/static/loading.gif)
日誌複製
日誌項Log Entry
日誌項屬性
![](http://static.javashuo.com/static/loading.gif)
由上圖可知,日誌實體(log Entry)主要包含如下幾個屬性:
日誌執行指令(cmd):客戶端服務發起事務請求操做並經過leader服務節點的共識模塊輸出的一條持久化到leader服務節點所在的狀態機上的指令.
日誌索引值(logIndex): 日誌項對應的整數索引值,用來標識日誌項的,是一個連續的、單調遞增的整數號碼,而且當前的日誌項(logEntry)不會改變其在日誌(log)下的位置
日誌任期編號(term): 建立當前日誌項(logEntry)的leader節點當前所處的任期編號term,主要來保證appendEnrty到日誌log的一致性檢查.
日誌項複製原理
-
客戶端服務client service向Raft集羣服務發起事務請求操做,將轉發由Raft集羣的leader節點進行事務請求的寫入以此來保證Raft集羣服務的共識問題,假設客戶端服務發起寫請求操做 set x=10
,即:
![](http://static.javashuo.com/static/loading.gif)
-
leader節點接收到事務請求操做,將請求提交給leader節點服務下的共識模塊進行事務操做並輸出cmd指令以RPC的方式進行AppendEntries(複製日誌項)到其餘服務節點中,即:
![](http://static.javashuo.com/static/loading.gif)
-
當follower節點將接收到leader節點RPC的appendEntries(日誌複製)進行持久化後,將會返回給leader節點,而leader節點若是接收到大多數follower節點的RPC日誌複製成功的響應,那麼就會將當前的log應用到leader節點的狀態機並給予客戶端的響應.即:
![](http://static.javashuo.com/static/loading.gif)
這個時候leader節點若是有新的RPC日誌項複製抑或是發起heartbeat心跳檢測,
RPC-AppendEntries&Heartbeat
會攜帶當前最大的且即將提交的index
到follower節點,follower節點會進行一致性檢查流程並將日誌項提交到本地狀態機以保證與leader節點的日誌數據一致.即:
![](http://static.javashuo.com/static/loading.gif)
-
整個RPC日誌複製流程以下:
![](http://static.javashuo.com/static/loading.gif)
最後,關於Raft算法的日誌複製,能夠類比數據庫的主從複製架構來思考,上述的提交日誌能夠理解爲binlog或者是oplog,那麼每次發起的RPC日誌複製抑或是心跳檢測都會攜帶日誌最大且即將提交的索引值index,經過binlog或者是oplog將數據更新到節點的狀態機上.
日誌的一致性檢測
一致性檢測原理
![](http://static.javashuo.com/static/loading.gif)
在上述圖中,咱們看到leader節點與follower節點的日誌數據存在不一致,咱們知道Raft算法是屬於強leader模型,一切以「leader」爲主,所以日誌複製也不例外,一旦出現不一致,那麼follower節點會進行一致性檢查並以leader發送的RPC日誌爲主將不一致性的數據強制更新爲與leader節點日誌一致,而對於leader節點的日誌是不會覆蓋和刪除本身的日誌記錄.也就是說對於raft算法的一致性檢查原理主要包含如下步驟:
leader節點經過RPC日誌複製的一致性檢查,查找與follower節點上與本身相同的日誌項最大的索引值,這個時候follower節點的索引值以前的日誌記錄與leader是保持一致的,而以後的日誌將產生不一致.
leader節點找到與follower節點相同的索引值並將索引值以後的日誌記錄複製並經過RPC發送到follower節點,強制follower節點更新日誌數據不一致的記錄.
一致性檢查流程
以上面的日誌記錄爲例,leader節點當前的索引值爲8,而follower節點A與follower節點B索引值分別爲5和8,這個時候假設leader節點向集羣follower節點經過RPC發送新的日誌記錄抑或是心跳消息.此時follower節點A與B將會進行一致性檢查.
-
若爲RPC複製新的日誌項,其檢查流程以下:
![](http://static.javashuo.com/static/loading.gif)
若爲心跳檢測,其檢查流程以下:
![](http://static.javashuo.com/static/loading.gif)
index=9
或者
index=8
的日誌項更新,因而對於leader節點將向後遞減從新發送新的RPC複製日誌到follower節點,即:
![](http://static.javashuo.com/static/loading.gif)
小結
-
leader節點經過日誌複製RPC消息,將當前最新且uncommited的日誌索引值發送到Raft集羣的follower節點上,假設此時的消息索引值 index=rpcIndex
-
Raft集羣的follower節點接收到日誌複製的RPC消息,會檢查上一個日誌索引值preIndex(index=rpcIndex-1)對應的日誌項entry中的index,term以及cmd的屬性是否匹配,若是不匹配則會拒絕當前index(rpcIndex)的entry更新並返回失敗消息給leader節點,若是成功則持久化當前日誌項並返回成功消息給leader節點(一致性檢查). -
leader節點接收到follower節點的失敗消息,因而遞減當前的index`(rpcIndex-1),再次發起新的日誌RPC複製消息到follower節點上 -
這個時候follower再次進行一致性檢查,若是匹配那麼返回成功給leader節點,此時leader節點就會知道當前follower節點的數據索引值(rpcIndex-2)與leader節點的日誌是一致的. -
最後leader節點再次發起日誌複製的RPC請求,複製並更新該索引值(rpcIndex-2)以後的日誌項,最終實現leader節點與follower節點的日誌一致性.
成員變動
集羣成員變動問題
集羣中leader節點崩潰與恢復
![](http://static.javashuo.com/static/loading.gif)
從上面咱們能夠看到在一個Raft集羣服務中,若是leader節點發生不可用,那麼剩下的follower節點將會從新進行選舉,假設此時選舉B做爲leader節點,那麼當原有的leader節點恢復正常的時候,此時集羣存在兩個「leader」節點,如何解決?
-
集羣擴容
![](http://static.javashuo.com/static/loading.gif)
從上述能夠看到,在實現集羣服務節點的擴容時,若是新加入的服務節點恰好碰上原有的集羣服務發生網絡分區,致使C與B,A節點失去聯繫,而在C節點恢復的時候與剛加入的服務節點D&E組成一個新的Raft集羣(D&E與原有的服務節點屬於同一個區域內);其次若是是新加入的兩個節點與原有的服務節點不屬在同一個區域,那麼當前raft集羣擴容就存在跨區域的集羣,跨區域必然會存在網絡不可靠的因素,所以一旦兩個區域發生網絡分區錯誤,那此時新區域下的D&E以及原有的區域集羣節點分別組成了一個Raft集羣,此時就會面臨雙leader節點問題.
從上述的問題分析中,咱們都看到集羣服務可能出現多個leader節點,這就違背了Raft算法的強leader且惟一性的特徵,而對於Raft算法解決這類集羣成員節點變動則是經過單節點變動來解決集羣的「腦裂」問題.
單節點解決成員變動
在Raft算法的論文中關於集羣成員節點的變動存在着一個集羣的配置屬性,即在瞭解單節點變動方式以前,咱們須要對集羣cluster的配置有一個基本的認知(與ES集羣類似)
集羣配置
在一個穩定的Raft集羣服務中,存在着如下的一個leader以及兩個follower節點,follower節點的配置須要從leader節點同步進行更新,因而在這裏咱們關注leader節點的配置便可,也就是整個集羣的一份配置.對於ES集羣而言,其配置信息以下:
![](http://static.javashuo.com/static/loading.gif)
引入集羣配置的目的
-
其一,咱們能夠想到的方案是停更集羣服務的執行,更改集羣的配置再重啓生效,可是顯然在當前互聯網應用中是不容許也是不被採納的,極大影響用戶體驗,容易形成用戶流失; -
其二是重啓集羣服務的步驟存在着誤操做步驟,容易致使上線服務時出現不可預知的錯誤.
單節點變動原理
-
假設現有的Raft集羣服務配置爲C[n1,n2,...],此時服務集羣加入一個節點Xm,同時對應的配置nm,這個時候leader節點監聽到有新的節點加入讀取節點配置nm並更新到當前的leader配置C中,設新配置爲C1,即[n1,n2,...nm],並向新節點同步數據log. -
其次leader節點更新配置以後C1併發起日誌複製的RPC請求消息到集羣服務的各個節點,集羣服務節點接收到RPC消息請求並將新的配置應用到本地狀態機中,至此完成了單節點變動.
解決成員變動分析
-
崩潰恢復過程
-
Raft集羣的leader節點發生不可用,原先具有leader選舉權的follower節點進行leader選舉,最終節點B成爲新一輪的leader,即以下:
![](http://static.javashuo.com/static/loading.gif)
-
這個時候若是A節點恢復並從新加入到現有的Raft集羣中,那麼利用單節點變動原理,leaderB更新配置並將log同步覆蓋更新節點A,此時A節點天然做爲follower節點,最後leader節點B經過日誌複製的RPC向集羣服務更新配置最終保證集羣的強leader模型,即:
-
擴容解決「腦裂」問題
![](http://static.javashuo.com/static/loading.gif)
在現有的Raft集羣中加入節點D,此時集羣raft_cluster監聽到節點D加入集羣,此時leader節點更新集羣的配置並向節點D同步日誌數據log,最後更新集羣配置以後再向Raft集羣服務節點發起日誌複製的RPC請求消息同步最新的集羣配置信息從而保證Raft集羣的強一致性.
集羣跨區域「腦裂」問題(擴展)
集羣腦裂
初始化狀態,只有華南區域部署Raft集羣服務,但因業務需求緣由,須要在華北地區增長機器節點,並加入到現有的Raft集羣服務,即:
-
若是兩區域網絡沒有發生分區錯誤,按照上述單節點變動原則,最終Raft集羣配置以及節點狀態以下:
![](http://static.javashuo.com/static/loading.gif)
若是兩區域的網絡發生分區,那麼就會致使Raft集羣服務在不一樣的區域中產生兩個leader節點,即:
-
華南區域leader節點發生不可用,而在從新選舉的過程當中,區域產生分區錯誤,致使Raft集羣服務出現兩個leader節點,即:
![](http://static.javashuo.com/static/loading.gif)
對於3&4問題,屬於跨區域的集羣的「腦裂」問題(ES集羣也存在一樣的問題)
如何解決集羣腦裂問題
腦裂產生的緣由
網絡發生分區錯誤: 機器宕機抑或是網絡擁堵超時
每一個節點具有成爲leader節點資格.
腦裂解決
基於上述的現象,發生網絡分區錯誤最有可能的緣由就是網絡超時,因而能夠針對分區域的不一樣節點設定對應的超時時間,好比能夠將華北區域的節點超時時間變長(等待心跳檢查以及選舉leader的超時時間)
其次能夠將node節點下降權限,僅做爲提供數據服務,不具有競選leader資格(即沒有投票資格),假設當前集羣節點個數爲n,根據反證法以及投票知足大多數原則(理想狀態不考慮隨機超時時間)可知最小能夠配置具有成爲leader節點個數爲
(n/2)+1
而且是在處於同一個區域下,即:
![](http://static.javashuo.com/static/loading.gif)
Raft算法分析小結
節點狀態變化
Raft算法集羣節點服務的初始化狀態均爲Follower節點,而且每個Follower節點具有成爲Leader節點的資格,也就是說能夠同時具有存儲數據以及集羣Leader的特徵,其次Follower節點都擁有一個隨機等待leader節點發起ping心跳檢測的超時時間timeout
成爲候選節點以後就會更新任期Term並隨機設置對應的投票請求超時時間,若是在指定的超時時間內超過半數投票,那麼就會晉升成爲leader節點,並週期性地向集羣從服務節點發起心跳檢測以免Follower節點成爲候選節點發起leader投票選擇.
主觀下線,即Raft算法集羣服務節點中有一個Follower節點超時沒有獲得leader節點的心跳檢測請求,那麼就會爲本身進行投票,此時會晉升成爲候選節點,又稱爲單節點的主觀下線,即憑藉單節點服務設置的超時時間timeout來判斷leader服務是否可用.(redis的sentinel集羣服務的sdown)
客觀下線(不屬於Raft算法),即集羣半數以上的Follower節點沒有在超時的時間內獲得leader節點的心跳檢測請求(或者說集羣大多數節點認爲leader節點不可用)而從新發起投票選舉請求.(redis的sentinel集羣服務的odown)
節點之間的通信
-
leader投票選舉請求: 經過候選節點發起RPC的投票請求,集羣其餘節點在指定的timeout內接收到投票請求並按照先來先得的順序進行投票. -
日誌複製請求: 集羣服務的leader節點接收到事務操做執行事務指令併發起RPC請求對事務指令複製到事務操做日誌log中並同步到集羣其餘服務節點 -
leader與follower節點心跳維持: leader節點發起RPC並攜帶任期Term信息的心跳檢測,以此來維持集羣服務leader節點與follower節點之間的健康檢測狀態,保證集羣服務的可用性.
共識與數據一致性
Raft算法架構
![](http://static.javashuo.com/static/loading.gif)
共識問題
什麼是共識
簡而言之,就是不一樣進程p對分別輸入一組u的數據,經過相同的程序處理邏輯來保證其輸出值最終都是v.即:
![](http://static.javashuo.com/static/loading.gif)
-
對於Raft集羣存在哪些共識問題
-
leader的選舉
-
事務操做
數據一致性
什麼是數據一致性
Raft集羣服務對外提供數據的讀取始終保證一致性,即對Raft集羣以外的服務,簡稱爲客戶端服務client service,client service多個節點node向Raft集羣服務發起讀取請求,那麼要保證client service的每一個節點node讀取到請求數據都是一致的.
如何保證一致性
當Raft算法已經選舉Leader節點以後,爲了保證Raft集羣中的數據一致性,Raft算法採起強制的Leader策略,將客戶端的寫入操做更新到leader節點的日誌文件中,並以RPC通信的方式複製到Raft集羣的從服務節點中,從服務節點從操做日誌來增量更新數據從而保證leader與follower節點數據的一致性.
日誌複製異常
當leader節點發起復制日誌的RPC請求消息到Raft集羣中各個follower節點,若是此時leader節點發生不可用,那麼此時會有如下幾種狀況(前提是leader節點的日誌已經寫入成功,leader節點恢復以前):
其一follower節點大多數接收到複製日誌的請求並執行成功;
其二follower節點只有少數接收到複製日誌的請求並執行成功;
其三follower節點沒有接收到複製日誌的請求;
leader任期Term
任期特徵
Raft算法中任期Term包含時間段以及編號,而且會影響leader選舉和請求的處理
Raft算法中leader節點的任期編號具有單調性,且爲正整數遞增
任期的更新
當Raft集羣服務中存在follower節點發現leader節點不可用的時候,就會成爲candidate節點,併爲當前節點的任期自增1,而後將本身的任期編號Term以參數的形式攜帶在投票請求的RPC中向其餘服務節點發起新一輪任期leader節點的投票選舉.
任期與投票選舉
Follower節點接收到Candidate節點的任期投票,若當前的follower節點任期比投票選舉的任期小且是在超時的時間範圍內,那麼當前Follower節點就會爲發起投票的請求節點進行選舉並給予響應
Follower節點接收到Candidate節點的任期投票,若當前的follower節點任期比投票選舉的任期大,那麼當前的Follower節點將會拒絕投票請求
若是Raft服務的leader節點因爲發生不可用,而同時在不可用期間Raft集羣已經經過選舉產生新的leader節點服務,此時當原先的leader服務節點恢復健康狀態時,因爲會接收到leader節點的心跳檢測以及任期編號等信息,發現當前的任期編號比接收到任期編號小,那麼這時候原先的leader節點就會更新本身的任期併成爲集羣服務的follower節點(單節點變動).
選舉規則
週期檢測
leader節點會週期性向follower節點發起心跳檢測(攜帶投票選舉的Term),以免follower節點成爲候選節點進行投票選舉.
節點具有成爲leader權利
Raft集羣服務的follower節點在指定的時間內沒有接收到leader節點的心跳檢測消息,那麼此時會認爲leader節點不可用,會推薦本身成爲候選節點並自增任期編號,同時發起leader選舉.
大多數原則
follower節點成爲candidate節點發起的leader選舉,若是得到集羣服務大多數服務節點的投票響應,那麼當前follower節點就會成爲leader節點
具有持續性
Raft集羣經過每次選舉產生新的leader,同時會對應新的Term,每一輪新Term都具有持續性,也就是說在一個任期Term內,只要是leader節點是可用的,那麼就不會發生新的一輪選舉,可用性是經過週期性檢測來保證.
一次投票&先來服務
![](http://static.javashuo.com/static/loading.gif)
知足完整性日誌
日誌完整性高的follower節點拒絕投票給日誌完整性低的候選節點.假設如今Raft集羣服務提交的日誌log以下(其中包含日誌索引logIndex,實體log包含任期編號term以及變動的指令):
![](http://static.javashuo.com/static/loading.gif)
logIndex=5
的候選節點向一個日誌索引下標爲
logIndex=8
的follower節點發起投票請求,這個時候follower節點將會被拒絕,主要緣由是後者的
logIndex
更大,相對地,其日誌完整性更高(可類比於kafka的ISR副本機制)
Raft算法中的timeout含義
-
follower節點等待leader節點發起的心跳檢測的隨機超時時間 -
每次發起新的一輪選舉時,候選節點發起投票請求並等待投票請求響應的隨機超時時間.
關於raft算法的論文,感興趣的同窗能夠關注下面公衆號並回復「raft」獲取,最後感謝花時間閱讀,如對你有用,歡迎轉發或好看,謝謝!
![](http://static.javashuo.com/static/loading.gif)
老鐵們關注走一走,不迷路
![](http://static.javashuo.com/static/loading.gif)
本文分享自微信公衆號 - 疾風先生(Gale2Writing)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。