从paxos到raft zab,为何raft能够“独领风骚”
文章目錄
- RAFT出現的緣由
- RAFT 的實現
- STATE MACHINE
- Log Replicated State Machine
- Leader Election
- 基本角色
- 關鍵變量
- 基本選舉過程
- Log Replicated
- 基本概念
- 基本操作
- Safety
- Log Replication: Consistency check
- Leader Election: Leader Completeness
- 總結
- RAFT 和 ZAB 的對比
- 參考文獻:
閱讀本篇之后,能夠回答如下幾個問題:
- paxos和raft的區別,為什么要在paxos基礎上推出raft
- raft 如何保證分布式consensus 特性?
- Replicated Log State Machine 在raft中的作用。
- raft 相比于ZAB的區別。
本篇主要是幾篇共識算法相關論文的匯總,綜合做一個描述,希望能夠讓大家能夠對共識算法:pxos, raft,zab 能夠有一個全面的了解,方便在今后的使用中選擇適合自己業務的共識算法。
這一些問題涉及的分布式共識算法細節會比較多,難免會有理解上的紕漏。如相關機制的描述有理解上的差異,歡迎留言一起討論。
RAFT出現的緣由
raft論文【3】是在2016年發出的,在此之前到1998年之間 大家備受paxos的“折磨”【4】。主要原因還是說分布式發展太快,大數據的計算存儲對分布式的能力需要越來越緊迫,而一個易理解,易實現的分布式共識算法是分布式系統的基礎,能夠極快得解決自己業務問題并領先同行(當然,核心還是能夠為公司創造更多的價值,節約資源成本)。
而PAXOS 則成為各大公司的阻礙,復雜的證明細節,簡略的實現細節,讓研究者和開發者望而卻步。
PAXOS原始論文【4】大家有興趣可以簡單看看,從問題描述到協議設計及相關一致性/正確性的完整證明,確實是嚴謹的學術論文,但并不適用于教育(國外大學會有分布式課程MIT6.824等)和工業化。即使后續推出了一個原理性的簡化版論文【5】,但又因為細節過少,沒有辦法快速實現。只能作為分布式共識算法的鼻祖,供有興趣的人取之。
到此大家也都比較清楚了,PAXOS 難以理解 也 難以實現,實屬業界公有的分布式實現的痛點。
這里也基于一些大佬的總結,將PAXOS的簡易原理描述做了一個總結,供大家參考,節約學習入門成本。
分布式共識算法:從PAXOS 到 Multi PAXOS
所以RAFT的出現就是為了解決PAXOS的兩個問題,當然最終的結果是解決了,才有現在的etcd,braft,tikv等
接下來深入了解一下RAFT如何將復雜問題簡單化。(當然一些數據證明肯定是站在PAXOS的基礎上來描述的,才能夠在raft中簡單帶過。)
RAFT 的實現
RAFT 認為自己相比于PAXOS 易于理解的原因除了簡化了共識過程中的角色,更主要的是將復雜問題進行分治。
即 RAFT共識算法的實現總結為如下幾個子問題:
- Leader Election
- Log Replicated
- Safety
- Membership Management
- Log Compaction
其中主要的過程是前面三個階段,完成了leader的選舉和日志的同步,并且這兩個過程通過safety保證了前面兩步的一致性和可靠性。
在詳細描述選舉過程之前需要對狀態機(state machine) 以及 日志復制狀態機(log replicated state machine)的原理做一個講解,方便后續對RAFT的Log Replicated過程的理解。
STATE MACHINE
如上圖,狀態機 是一些內部數據/指令執行的狀態遷移過程,通過狀態機能夠和外部客戶端進行通信。
狀態機的主要作用是:
- 管理集群內部狀態
- 和客戶端進行交互
使用狀態機的分布式系統,分布式存儲,分布式服務 很多,包括HDFS的name node, ceph的blustore, MemCached等
基于狀態機實現的日志復制狀態機(Log Replicated State Machine)則是分布式共識算法 一致性實現的基礎。
Log Replicated State Machine
日志復制狀態機的基本構造如下:
每一個日志復制狀態機 存在于一個節點或者一個集群的角色服務上(follower/leader),內部主要有三個模塊:
- Consensus Module 共識模塊。主要作用有兩個:完成來自客戶端請求的解析,將客戶端請求解析為能夠被狀態機識別的 log entry;和其他server的共識模塊進行rpc通信,轉發客戶端的請求。
- log 日志列表。用來存放共識模塊解析出來的log entry,追加寫。
- state machine 狀態機。用來執行日志列表中的日志條目,一般是command的形態。
一個共識集群中每一個參與集群選舉、一致性保障的角色服務進程中都會維護一個這樣的日志復制狀態機。
如上示意圖代表整個狀態機 集群 從接受到客戶端請求到完成日志復制最終回復客戶端的 過程。
模擬這個復制狀態機集群 處理客戶端請求的過程 可以分為五步:
- 客戶端將請求 x<–z 發送個leader上的共識模塊
- leader的共識模塊將客戶端請求轉發給follower上的共識模塊,并保證收到大多數(quroum,大于等于n/2 + 1,n表示集群節點個數)的回應
- 每個server的復制狀態機將各自共識模塊介些的log entry追加到自己本地的log列表中
- 每個server的復制狀態機并行提交log entry中的日志條目到狀態機中 執行日志中的指令,比如將x設置為z。這一步可以看作是commit階段,需要持久化。
- 完成了commit 之后,leader向客戶大返回成功。
當然這個復制狀態機并不能代表完整的raft的日志復制的過程,一些日志復制的細節上會有差異,但是整個日志復制到完成提交的過程是 raft Log Replicated的基礎。
接下來我們詳細探討一下整個raft各個階段的基本實現過程,揣摩將共識問題分解為一個個子問題一一實現的好處。
PS: raft論文中會完整得描述從選舉到日志合并和成員變更的過程,但日志合并和成員變更本身會有非常多的細節,全部放在本篇則會過于冗余。本篇的定位是raft的基本原理,也就是會描述Leader Election , Log Replicated 以及 以上兩個階段的穩定性Safety保證。
Leader Election
介紹日志選舉前需要對整個raft中的角色以及其作用做一個統一的介紹。
基本角色
這里的角色在實際raft相關的應用中是以服務進程的形式存在的。
follower,所有角色開始時的狀態,等待接受leader心跳RPCs,如果收不到則會變成CandidateCandidate,候選人。是變成Leader的上一個角色,候選人會向其他所有節點發送RequestVote RPCs,如果收到集群大多數的回復,則會將自己角色變更為Leader,并發送AppendEntries RPCs。Leader,集群的皇帝/主人…,raft能夠保證每一個集群僅有一個leader。負責和客戶端進行通信,并將客戶端請求轉發給集群其他成員。
關鍵變量
-
Election Timeout選舉超時時間。即Cadidate 向集群其他節點發送vote請求時,如果在Election Timeout時間內沒有收到大多數的回復,則會重新發送vote rpc。以上將實際
RequestVote簡寫為vote ,就是請求投票的rpc一般這個超時時間是在
150-300ms的隨機時間,為了防止集群出現頻繁的 split vote 影響leader選舉效率的情況,將這個超時時間取在155-300ms范圍內的隨機時間。當然,這個數值也是經過測試的,超時時間設置在150-300ms 之間能夠保證raft集群 leader的穩定性,也可以將超時時間設置的比較低(12-24ms),但是存在的網絡延遲則會導致一些不必要的leader選舉。關于splite vote的情況可以看如下圖,圖片來自raft可視化官網【6】:
兩個節點收到對方的vote請求之前變成了candidate,發送了各自的request vote。 -
Heartbeats Timeout心跳超時時間。follower接受來自leader的心跳,如果在heartbeats timeout這個時間段內follower沒有收到來自leader的AppendEntries RPCs,則follower會重新觸發選舉。收到了,則重置follower 本地的 heartbeats timeout。 -
RequestVote RPCs以上兩個超時過程也說了,投票是的rpc請求 -
AppendEntries RPCsleader 同步數據時的rpc請求。
基本選舉過程
在之前的描述過程中基本選舉已有有一些描述了,這里通過角色變化圖展示一下。
主要是三個角色的變化完成leader選舉。
- follower 沒有收到heartbead 變成Candidate
- Candiate 投票給自己,并發送投票請求給其他server ,收到大多數的相應則變成leader,否則等待超時再次變成Candidate 發送投票 或 接受其他的投票。
- Leader 發送AppendEntries 復制日志到其他follower;維護心跳來保證自己被其他follower可見。心跳超時或發現更高的Term 就變成follower。
Leader選舉過程中除了之前提到的基本變量,還會有一個Term 的概念。
這是raft論文中的一幅Term概念描述的圖。簡單來說Term 可以用一個數字來表示,主要提現的是raft集群Leader的存活周期。即當前Term為1,維持了一段時間,這段時間集群的leader沒有發生變化。而當Term變成2,表示一定發生了Leader的變更(leader所在服務器跪了),但不一定表示Leader一定被選舉出來了。
上圖中的 term3 則完全沒有選出leader,這種情況的出現就是上文中描述的splite vote的情況,這個時候Term也會增加,當時并沒有leader 被選出來,在ceph/zookeeper中 其實就類比于Epoch。
關于Term在candidate 投票過程中發生的變化 如下圖。
到此整個raft 選主過程就描述完了,一些一致性保證的問題會在日志復制 講完之后進一步整體描述。
Log Replicated
日志復制的過程就會用到我們前面講到的Log Replicated State Machine,raft 是基于這個機制來實現日志復制的基本過程。
講述日志復制的過程之前 還對日志復制的基本概念做一個描述
基本概念
除了日志復制狀態機已經完整介紹過了,這里主要介紹一下raft中的log entry 列表。
如上圖,其中每一個角色都有自己的log entry列表,列表中的每一個方格代表一條entry。
方格中有一個數字1、2、3,一條entry x<–3、y<–1,數字代表的是Term,entry是能被狀態機執行的commnad。
最上面有一排數字:1、2、3… 表示entry索引,是entry的全局唯一標識。
從這個日志列表中,我們能夠看到通過index + term 能夠唯一標識一條entry,且整個集群正常工作的成員的entry和index需要保持一致,上圖中存在部分節點出現過異常導致現在的entry出現不一致的情況,這個時候leader會負責完成Log Replicated 來將follower的entry補全。
基本操作
leader 完成Log Replicated 的過程是通過如下幾步完成的:
- 客戶端發送請求到leader
- leader 將客戶端的請求轉化成entry 追加到log列表中
- leader 通過 AppendEntries RPCs 將log entry 發送到其他follower
- 新的log entry被committed 需要經過如下幾步:
- leader 首先在自己本地的狀態機執行log entry中的command(落盤持久化),并向客戶端返回成功
- leader 喚醒follower節點各自本地按順序提交log entry
- follower節點將log entry添加到狀態機中完成持久化操作
- 如果發生follower異常/宕機,會持續嘗試發送AppendEntries RPCs
從上面的基本操作我們能夠發現當前Term 下的 Leader 一定是擁有最全的且已經完成了持久化的log entry,整個過程是通過日志復制狀態機來完成的。
接下來會討論一些異常情況,即follower的日志相比于leader的日志存在缺失的情況,Leader該如何處理?還有如何保證選舉出來的leader擁有最全的日志信息?
Safety
先回答第一個問題,leader和follower 日志不一致的情況,leader會怎么處理。
Log Replication: Consistency check
進入正題之前 關注如下幾個需要注意的前提
- AppendEntries RPCs 內容 包括<index, Term>, 分別是log entry在日志列表中的位置index 和 它所處leader 的Term號。
- follower 收到leader發送的entry, 必須在<index,Term>和內容匹配,匹配則接受并應用在自己本地的復制狀態機中;如果不匹配,follower會拒絕接受當前的Log Entry,返回reject
此時Leader 會調整<index,Term>信息,即發送本地列表中已經提交的上一個log enry 給follower - 通過induction step 不斷的回退嘗試 方式來完成 Leader和follower 的日志匹配屬性。
Example1:
follower 日志落后于leader的日志,y<–1 這一條日志相比于Leader缺失。這種情況的出現是由于follower 在Term 3可能宕機,可能網絡延遲,總之沒有收到leader 的rpc,重新啟動之后需要和leader坐日志同步。
按照如上幾條前提:
- Leader 發送<4,3> 的log AppendEntries給follower,follower發現本地最新的是<3,2>,則拒絕leader 的log entry。
- Leader induction step,調整發送<3,2> 給follower, follower發現匹配,也就是<3,2>以前的log entry都完成了匹配,那follower就可以接受最新的leader的log entry了。
- 接下來的AppendEntries PRCs leader就可以攜帶最新的log entry 給follower。
最終完成集群log entry的完全匹配。
Example 2:
這個情況是follower的日志多余leader的日志,這種的情況是出現在網絡分區的情況。兩個分區分別發送自己的rpc,一個分區中存在舊的leader,并未發生Term的變更;而另一個分區會重新選舉leader ,維護更高版本的Term。到現在分區重新恢復,就出現了Leader和follower之間的數據 Term相差較多。
還是按照日志一致性前提,通過induction step不斷回退日志版本,leader發送的日志回退到<3,2>還是沒有發現和follower的日志達成匹配,所以會繼續回退,發送<2,1>的entry信息。
就是如上這種情況,最終發現<2,1>能夠匹配,則會將<2,1>之后的leader entry發送給follower,從而覆蓋掉follower的低term log。
這里會衍生出來的一些問題:
按照之前描述的leader election過程,可能會選出一個leader 的log entry比follower 最新的log entry版本低,那導致的情況就是這個新的leader 會把follower數據覆蓋掉,這種情況可能嗎?
接下來需要深入探討一下Leader Election的限制了。
Leader Election: Leader Completeness
細節描述:
- 一旦一個log entry 在當前集群完成提交,未來選舉的所有leader都需要包含這條entry
- 日志不全的server 無法被成功選舉
- 選舉過程中的 candidate 發送的 RequestVote RPCs 需要包含 本地最新的index和term
- 接受投票的server 發現收到的index和term 比自己本地還舊,會拒絕選舉
- log entry是按照<lastTerm, lastIndex> 進行比較的,先比較lastTerm的大小,如果相等則比較lastIndex
如上圖,幾個不同的server有自己的log entry列表。
- 如果S4 的RequestVote RPCs 先到達其他server ,按照<lastTerm, lastIndex>比較規則,它會收到S1,S2,S5的選票并當選為leader,同時后續也會將S3中的9,10 日志覆蓋掉。
- 如果S2的RequestVote RPCs 先到達其他server,顯然,它無法贏得選舉;沒有選票返回,直到超時,會進入下一輪重新選舉。
- 當然 S3現發起的 vote 肯定能夠當選。
這樣,選舉出來的Leader能夠擁有集群大多數的log entry,而對于S3 server這樣的情況顯然9和10 index log entry已經可以被丟棄,當時的leader也僅僅在自己本地提交,其他follower一個也沒有收到提交的entry(集群內部網絡嚴重問題)。
總結
到此整個raft的基本過程就講完了,除了后續的成員變更和日志合并之外。整個raft實現的共識算法 基本過程也就兩個階段:Leader Election, Log Replicated。
在這兩個階段基礎上通過兩種一致性 完成了異常情況下的集群數據一致性保障。
- Leader Election的一致性通過<lastTerm, lastIndex> 比較完成,最新的leader擁有大多數成員認可的最全的日志;
- Log Replicated 一致性通過 induction step, 還是比較日志的term和index,舊了leader就發送更舊的,直到完全和follower匹配,再補充新的日志。
raft的易理解特性 從論文給的數據能夠很明顯得看出來,讓一部分學生分別學習raft和paxos 并完成相關的實驗。
可以發現學生的反饋中 raft更易理解,更易實現,對應的測試分數也比paxos更高。
RAFT 和 ZAB 的對比
關于ZAB協議的基本過程可以參考:
zookeeper ZAB協議原理淺析
相比于RAFT 從內部描述上的不同主要如下:
- ZAB 的 entry(history entry) 的數據同步方向是從follower向leader發送;raft則是leader負責向follower同步log entry
- ZAB 中一個leader周期是epoch; raft則是 term
從基本選舉和日志同步的流程上來看:
ZAB 需要經過的rpc更多,整個leader election + discovery + synchonization 的RPC次數超過RAFT 幾倍;伴隨著復雜度也是幾倍。
從提供的服務來看:
zookeeper能夠保證強一致性,選舉出來的leader一定擁有最全最新的日志,不是raft 中leader 比大多數新就可以。
zookeeper 能夠提供分布式鎖,分布式隊列,分布式配置管理 這樣的協調服務,處于應用之外。
raft能夠集成到應用內部,完成一個分布式存儲(tikv),分布式kv(etcd), 分布式數據庫(各個大廠自研)等更加完備的分布式系統,已有的業界raft實現都能夠在raft論文基礎上快速完成自己想要的分布式系統的開發。
完整的raft,paxos,zab對比如下:
參考文獻:
1. Zookeeper: https://pdos.csail.mit.edu/6.824/papers/zookeeper.pdf
2. ZAB: http://www.tcs.hut.fi/Studies/T-79.5001/reports/2012-deSouzaMedeiros.pdf
3. RAFT: https://raft.github.io/raft.pdf
4.PAXOS: https://lamport.azurewebsites.net/pubs/lamport-paxos.pdf
5.PAXOS SIMPLE https://lamport.azurewebsites.net/pubs/paxos-simple.pdf
6.raft website
總結
以上是生活随笔為你收集整理的从paxos到raft zab,为何raft能够“独领风骚”的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 是你吗歌词是哪首歌啊?
- 下一篇: LSM 优化系列(六)-- 【ATC‘2