分布式基石|最難?paxos?和最易?raft??
什么是一致性協(xié)議?
注意,今天是大白話隨便聊聊,目的是直白的了解 raft 是什么,不用太摳理論定義。什么是一致性協(xié)議?字面理解就是讓某些東西保持一致的協(xié)議嘛。什么是一致?大白話就是內(nèi)容完全相同唄。以存儲(chǔ)場(chǎng)景舉例,假設(shè)有三個(gè)磁盤文件,大小為 1M ,如果三個(gè)文件 1M 的數(shù)據(jù)都完全相同,那么這可以說(shuō)這文件的數(shù)據(jù)是一致的。一致性還分了不同的等級(jí),如線性、因果、最終一致性等等,而且如果站在不同的系統(tǒng)層面來(lái)看,承諾的一致性也會(huì)有所不同。這些今天都不重要,重要的是我們知道了:一致性協(xié)議就是用來(lái)達(dá)到一致的協(xié)議唄。有兩個(gè)最出名的一致性協(xié)議:paxos 和 raft 。數(shù)學(xué)上已經(jīng)嚴(yán)格證明了 paxos 的正確性,只要嚴(yán)格遵守它協(xié)議的約束,就能保證在分布式的惡劣環(huán)境下多副本數(shù)據(jù)的一致。我們來(lái)看一下吧!
paxos 協(xié)議
paxos 是 Leslie Lamport 大神于 1990 年提出的一致性協(xié)議。它解決的問題是一個(gè)分布式系統(tǒng)如何就某個(gè)值(決議)達(dá)成一致。
劃重點(diǎn):paxos 協(xié)議本質(zhì)是確定一個(gè)值。論文《The part-time parliarment》提到的 paxos 里面有兩個(gè)重要角色:
- Proposer:提議發(fā)起者
- Acceptor:提議接受者
第三定律:如果一輪編號(hào)為 Bbal 的投票,多數(shù)派中任意一位成員曾投過(guò) Bbal 編號(hào)小的票(B'),那么 Bdec == B'dec;
上面就是 paxos 最核心的內(nèi)容,但是說(shuō)實(shí)話,每一個(gè)字都看得懂,但是連起來(lái)就不知道啥意思?
paxos 到底能做啥?這個(gè)我們存儲(chǔ)系統(tǒng)有啥關(guān)系?它為啥那么難懂?paxos 難就難在于它沒告訴大家,這個(gè)東西能用來(lái)做啥,映射不到現(xiàn)實(shí),就無(wú)法產(chǎn)生共鳴。我們先接受一個(gè)事實(shí):paxos 的本質(zhì)是確定一個(gè)值,且一旦這個(gè)值確定之后,后續(xù)無(wú)論怎么投票,無(wú)論發(fā)生什么,這個(gè)值保持不變。那我就比以前更懵逼了!怎么越說(shuō)越糊涂了了,說(shuō)好的做一個(gè)分布式存儲(chǔ)服務(wù)嗎?存儲(chǔ)服務(wù)應(yīng)該允許可以寫入任何數(shù)據(jù),且可以 Update 的嘛。確定一個(gè)不能變的值有啥用?
paxos 的工程化
我們下面嘗試將 paxos 工程化,將它具現(xiàn)化到現(xiàn)實(shí)的工程實(shí)現(xiàn)。
?1???確定一個(gè)值,有啥用?
回到最開始的問題,確定一個(gè)值對(duì)我們有啥用?我們來(lái)簡(jiǎn)要發(fā)散下 paxos 工程化的思路。paxos 本質(zhì):確定一個(gè)值,現(xiàn)在把這里面參與的角色打包起來(lái),Proposer,Acceptor,Proposal 等等組成的抽象的集合:paxos instance,稱為 paxos 實(shí)例:劃重點(diǎn):每個(gè)實(shí)例必須是完全獨(dú)立,投票互不干涉,即可。一個(gè) instance 確定一個(gè)值,多個(gè) instance 確定多個(gè)值。這些值不斷的被確定(永不更改),形成了一個(gè)值序列,這有啥用?
?2???確定多個(gè)值有啥用?
接著上面,我們現(xiàn)在有了一系列永遠(yuǎn)無(wú)法被修改了值序列,有啥用?存儲(chǔ)服務(wù)的基本特點(diǎn)是允許存儲(chǔ)任何數(shù)據(jù),并且能夠增刪改。哪還有啥用?這一個(gè)個(gè)值序列像不像一個(gè)東西:日志!這個(gè)跟 rocksdbdb,leveldb 的 wal 日志是不是差不多意思了?我們應(yīng)用這些日志就能得到一致性的輸出。所以我們還缺個(gè)啥?狀態(tài)機(jī)嘛。
?3???加個(gè)狀態(tài)機(jī)就起飛了
什么是狀態(tài)機(jī)?狀態(tài)機(jī)全稱為有限狀態(tài)機(jī)。它接收條件的觸發(fā),由一種狀態(tài)轉(zhuǎn)變?yōu)樾碌臓顟B(tài)。初始狀態(tài)相同,輸入的一系列事件相同,那么它最終的狀態(tài)一定相同。這可太常見了,比如 rocksdb,leveldb 等等 lsm 存儲(chǔ),它們數(shù)據(jù)先寫 append log ,通過(guò)重放日志到達(dá)的系統(tǒng)狀態(tài)一定是一致的。這種狀態(tài)機(jī)的應(yīng)用模式可不僅限于存儲(chǔ)服務(wù)。到這,我相信童鞋們已經(jīng)很豁然開朗了,只要我們通過(guò) paxos ?來(lái)產(chǎn)生分布式一致的有序的操作日志,加上狀態(tài)機(jī)的配合,實(shí)現(xiàn)一個(gè)分布式存儲(chǔ)服務(wù)必然不是問題。通過(guò)不停的確定一個(gè)個(gè)值,形成一個(gè)有序的操作系列,配合狀態(tài)機(jī)的應(yīng)用,這,就是 paxos 的工程化方向。
?4???活鎖的問題怎么解決?
對(duì)于 paxos 來(lái)說(shuō),Proposer 和 Acceptor 角色是可以重疊的,每個(gè)節(jié)點(diǎn)既可以是 Proposer,也可以是 Acceptor ,或者兩者都是。這帶來(lái)了非常大的靈活,每一個(gè) Proposer 都可以遞交協(xié)議(寫入數(shù)據(jù)),但由于最終只能確定一個(gè)值,那么這會(huì)導(dǎo)致非常多的無(wú)效功,這期間是使用類似樂觀鎖來(lái)解決那些沖突的提議。比如說(shuō),A 剛遞交一個(gè)提案,B 就遞交一個(gè)新提案導(dǎo)致 A 的提案被否定了,然后 A 又迅速遞交一個(gè)提案,形成了一種類似活鎖的狀態(tài),這時(shí)間就浪費(fèi)了呀。怎么解決?問題根因在于可以提案的點(diǎn)太多,大家都是平等的。那么統(tǒng)一聲音才能解決這個(gè)問題。于是Leader 就應(yīng)運(yùn)而生。通過(guò)某種方法指定一個(gè)節(jié)點(diǎn)為 Leader ,只有一個(gè)節(jié)點(diǎn)能遞交提案,這樣就解決了混亂問題,效率提隨之提升(這就是 Multi-Paxos )。
?5???paxos 工程化小結(jié)
小結(jié)一下,如果要將一個(gè) paxos 工程化落地,衍生了哪些東西:
- paxos 本質(zhì)是確定一個(gè)值,把參與確定這個(gè)值的角色打包稱為一組實(shí)例( paxos instance );2.不同實(shí)例之間決議互不干擾。多組 paxos 實(shí)例確定多個(gè)值,形成一組操作序列,也是就日志 ;
- 日志 狀態(tài)機(jī) 可以成為任何有意義的工程系統(tǒng);
- 為了解決遞交提案混亂可能引發(fā)的效率問題(比如活鎖),可以通過(guò)指定 Leader 角色來(lái)解決;
raft 協(xié)議
終于到了 raft 協(xié)議,raft 的論文開篇就是這么一段話:
Raft is a consensus algorithm for managing a replicated log. It produces a result equivalent to (multi-)Paxos, and it is as efficient as Paxos, but its structure is different from Paxos;raft 證明和 paxos 等價(jià),raft 是一種日志復(fù)制的一致性算法。看懂了嗎?raft 的著眼點(diǎn)就是 日志 狀態(tài)機(jī) 的方向。劃重點(diǎn):raft 天生就是 paxos 協(xié)議工程化的一種樣子。如下圖:圖里交代了關(guān)鍵模塊:
- 客戶端( Client ):就是用戶嘛,寫入數(shù)據(jù)的就是它嘍;
- 一致性模塊( Consensus Module ):負(fù)責(zé)寫入 log,并且把 log 復(fù)制到其他節(jié)點(diǎn);
- 狀態(tài)機(jī)( State Machine ):輸入 log ,推進(jìn)變更系統(tǒng)狀態(tài);
- raft 就是管理日志復(fù)制的算法;
- 日志 狀態(tài)機(jī) 就能落地一個(gè)一致性的系統(tǒng)應(yīng)用;
- 集群角色有分類,Leader 作為唯一的寫入點(diǎn),所有日志復(fù)制是 Leader 到 Follower 單項(xiàng)傳輸;
- Leader 的選舉;
- 日志的復(fù)制;
- 正確性的保證(約束條件);
- 選出一個(gè) Leader ;
- 把 Leader 的日志復(fù)制分發(fā)到 Follower 節(jié)點(diǎn);
- 數(shù)據(jù)怎么讀寫?
- 節(jié)點(diǎn)擴(kuò)縮容怎么搞?
?1???Leader 選舉
角色轉(zhuǎn)變:簡(jiǎn)單看下 raft 協(xié)議中關(guān)于 Leader 選舉的部分。下面是角色轉(zhuǎn)化圖,非常清晰:圖里至少能得到這么幾點(diǎn)知識(shí)點(diǎn):
- 系統(tǒng)開始每個(gè)節(jié)點(diǎn)都是從 Follower 角色開始;
- 定時(shí)器超時(shí)之后,角色轉(zhuǎn)變?yōu)?Candidate ,開始競(jìng)選 Leader;
- Candidate 如果獲得多數(shù)人的支持,那么選舉成功,角色轉(zhuǎn)變?yōu)?Leader 。如果選舉失敗,那么退為 Follower ;
- 無(wú) Leader 狀態(tài)(選舉中);
- 正常狀態(tài)( Leader );
- 任期編號(hào);
- 當(dāng)前日志的最新位置;
- 先比 term ,誰(shuí)更大誰(shuí)就新;
- 舉個(gè)例子,F(xiàn)ollower 節(jié)點(diǎn)保存的任期是 4,Candidate 發(fā)過(guò)來(lái)的是 3 ,這種就直接拒絕了;
- 如果任期相同,那么就比較 index ,index 誰(shuí)更大就新;
- 舉個(gè)例子,對(duì)面發(fā)過(guò)來(lái)的 index 是 7,我本地是的 8 ,那么就多說(shuō)了,拒絕;
?2???日志復(fù)制
日志復(fù)制有幾個(gè)特點(diǎn):
- 日志傳輸為單向傳輸,Leader 到 Follower ;
- Leader 永遠(yuǎn)不會(huì)改寫或者刪除自己的日志,永遠(yuǎn)只做 Append ;
- 日志內(nèi)容一切以 Leader 為主,哪怕是強(qiáng)制覆蓋 ;
- x = 4
- y = 7
- a 時(shí)刻:Leader 為 S1( 黑框的為 Leader ),它有著最新的日志 index:2 ,雖然最新的 index:2 并沒有 committed(復(fù)制到多數(shù)),只復(fù)制到了 S2 ;
- b 時(shí)刻:S1 掛了,S5 被選舉為 Leader ,任期為 3 ,并且 Client 還遞交了一個(gè)寫入;
- c 時(shí)刻:S5 掛了,S1 被重新選舉為 Leader ,任期為 4,這個(gè)時(shí)候它復(fù)制日志,把 index:2 的日志復(fù)制給了 S1,S2,S3 ,這是滿足了 quorum (但注意了,這個(gè)系統(tǒng)千萬(wàn)不能認(rèn)為 commit 了,且往后看)。并且 Client 還遞交了一個(gè)寫入在 index:3 的位置;
- d 時(shí)刻:S1 掛了,S5 被重新選舉為 Leader(S2,S3,S4 都會(huì)投票),于是把 index:2 的日志強(qiáng)制覆蓋到所有節(jié)點(diǎn);
- e 時(shí)刻:這個(gè)時(shí)刻是一種假設(shè),假設(shè)說(shuō),S1 在 c 時(shí)刻的時(shí)候在掛掉之前把任期 4,index:3 的日志復(fù)制到多數(shù)節(jié)點(diǎn),那結(jié)果又不一樣了。這種場(chǎng)景系統(tǒng)可以認(rèn)為 index:3 被 commit 了,index:2 則是被間接 commit 了;
?3???狀態(tài)機(jī)
這部分其實(shí)是最簡(jiǎn)單的,狀態(tài)機(jī)做的事情我們叫做 apply 。apply 的內(nèi)容則是各個(gè)業(yè)務(wù)自行解釋,舉個(gè)例子,如下的日志,這是一個(gè)典型的 kv 系統(tǒng)的樣子:日志 apply 完之后,系統(tǒng)狀態(tài)為:
x?=?4
y?=?7
劃重點(diǎn):日志里面的內(nèi)容由業(yè)務(wù)自行解釋,raft 只保證日志復(fù)制是完全一致的。?4???Propose 遞交
用戶的入口就是從遞交 Propose 開始,由 Leader 接收用戶請(qǐng)求,然后封裝成日志的樣子,經(jīng)過(guò)了 commit( 確定這個(gè)值 )之后就能對(duì)外承諾。思考一個(gè)小問題:集群只有一個(gè) Leader ,如果請(qǐng)求發(fā)給了 Follower 呢?難不成 Client 還要專門記錄誰(shuí)是 Leader ?也沒關(guān)系,F(xiàn)ollower 可以透明轉(zhuǎn)發(fā)給 Leader 。Leader 處理好之后,回應(yīng)即可。劃重點(diǎn):還是那句話,只由 Leader 來(lái)發(fā)起,就算發(fā)給了 Follower ,請(qǐng)求也會(huì)轉(zhuǎn)發(fā) Leader。
?5???成員變更
成員變更一般分為兩種場(chǎng)景:
- 單節(jié)點(diǎn)變更
- 多節(jié)點(diǎn)變更
- S1,S2 認(rèn)為 S1 是 Leader,在原有 3 節(jié)點(diǎn)的集群中,滿足多數(shù),合法 ;
- S3,S4,S5 認(rèn)為 S3 是 Leader ,在新的 5 節(jié)點(diǎn)集群中滿足多數(shù),合法;
- 最開始集群配置( S1,S2,S3 ),我們暫且叫做 C_old ;
- 遞交兩條集群變更的日志,Add S4,Add S5 ,Leader 向所有 S1,S2,S3 廣播日志;
- 所有節(jié)點(diǎn)( S1,S2,S3 )收到這兩條日志,則代表這兩條日志被 commit 了,于是 apply 這兩條日志,apply 的行為:集群配置變更為( S1,S2,S3,S4,S5 )