分布式共識(shí)及Raft簡(jiǎn)介
所謂分布式共識(shí)(consensus)则吟,與CAP理論中的一致性(consistency)其實(shí)是異曲同工序无,就是在分布式系統(tǒng)中先誉,所有節(jié)點(diǎn)對(duì)同一份數(shù)據(jù)的認(rèn)知能夠達(dá)成一致望迎。保證集群共識(shí)的算法就叫共識(shí)算法搜骡,它與一致性協(xié)議這個(gè)詞也經(jīng)嘲雎保互相通用稿壁。
當(dāng)今最著名的共識(shí)算法就是Paxos算法幽钢。它由Leslie Lamport在1990年提出,很長(zhǎng)時(shí)間以來(lái)都是一致性的事實(shí)標(biāo)準(zhǔn)傅是。但是它有兩個(gè)不小的缺點(diǎn):難以理解和證明匪燕,難以在實(shí)際工程中實(shí)現(xiàn)绍绘。Google Chubby的工程師就曾有以下的評(píng)論:
There are significant gaps between the description of the Paxos algorithm and the needs of a real-world system... the final system will be based on an unproven protocol.
于是2014年滤否,來(lái)自斯坦福的兩位大佬Diego Ongaro與John Ousterhout通過(guò)論文《In Search of an Understandable Consensus Algorithm》提出了一個(gè)新的共識(shí)算法Raft贬墩。從題目就可以看出肠牲,Raft的特點(diǎn)就是容易理解,在此基礎(chǔ)上也容易實(shí)現(xiàn)迟隅,因此在real world中啼止,它的應(yīng)用也比Paxos要廣泛锦聊,比較有名的如etcd梗劫、Kudu等享甸。
Raft為了達(dá)到易懂易用的目標(biāo)截碴,主要做了兩件事:一是分解問(wèn)題(decomposition)梳侨,即將復(fù)雜的分布式共識(shí)問(wèn)題拆分為領(lǐng)導(dǎo)選舉(leader election)、日志復(fù)制(log replication)和安全性(safety)三個(gè)子問(wèn)題日丹,并分別解決走哺;二是壓縮狀態(tài)空間(state space reduction),相對(duì)于Paxos算法而言施加了更合理的限制哲虾,減少因?yàn)橄到y(tǒng)狀態(tài)過(guò)多而產(chǎn)生的不確定性丙躏。
下面先簡(jiǎn)要介紹共識(shí)算法的基礎(chǔ)——復(fù)制狀態(tài)機(jī),然后就來(lái)按順序研究Raft是如何解決三個(gè)子問(wèn)題的束凑。
復(fù)制狀態(tài)機(jī)
在共識(shí)算法中晒旅,所有服務(wù)器節(jié)點(diǎn)都會(huì)包含一個(gè)有限狀態(tài)自動(dòng)機(jī),名為復(fù)制狀態(tài)機(jī)(replicated state machine)汪诉。每個(gè)節(jié)點(diǎn)都維護(hù)著一個(gè)復(fù)制日志(replicated logs)的隊(duì)列废恋,復(fù)制狀態(tài)機(jī)會(huì)按序輸入并執(zhí)行該隊(duì)列中的請(qǐng)求,執(zhí)行狀態(tài)轉(zhuǎn)換并輸出結(jié)果扒寄∮愎模可見(jiàn),如果能保證各個(gè)節(jié)點(diǎn)中日志的一致性该编,那么所有節(jié)點(diǎn)狀態(tài)機(jī)的狀態(tài)轉(zhuǎn)換和輸出也就都一致迄本。共識(shí)算法就是為了保障這種一致性的,下圖示出簡(jiǎn)單的復(fù)制狀態(tài)機(jī)及其相關(guān)架構(gòu)课竣。
- 某個(gè)節(jié)點(diǎn)的共識(shí)模塊(包含共識(shí)算法的實(shí)現(xiàn))從客戶端接收請(qǐng)求嘉赎。
- 該共識(shí)模塊將請(qǐng)求寫(xiě)入自身的日志隊(duì)列置媳,并與其他節(jié)點(diǎn)的共識(shí)模塊交流,保證每個(gè)節(jié)點(diǎn)日志都相同公条。
- 復(fù)制狀態(tài)機(jī)處理日志中的請(qǐng)求半开。
- 將輸出結(jié)果返回給客戶端。
領(lǐng)導(dǎo)選舉
節(jié)點(diǎn)狀態(tài)與轉(zhuǎn)移規(guī)則
根據(jù)分布式系統(tǒng)的Quorum機(jī)制與NRW算法赃份,集群中半數(shù)以上節(jié)點(diǎn)可用時(shí)寂拆,就能正確處理分布式事務(wù),因此Raft集群幾乎都使用奇數(shù)節(jié)點(diǎn)抓韩,可以防止腦裂并避免浪費(fèi)資源纠永。采用ZAB協(xié)議的ZooKeeper集群也是如此。
在Raft集群中谒拴,任意節(jié)點(diǎn)同一時(shí)刻只能處于領(lǐng)導(dǎo)者(leader)尝江、跟隨者(follower)、候選者(candidate)三種狀態(tài)之一英上。下圖示出節(jié)點(diǎn)狀態(tài)的轉(zhuǎn)移規(guī)則炭序。
可見(jiàn),集群建立時(shí)所有節(jié)點(diǎn)都是跟隨節(jié)點(diǎn)苍日。如果在一定時(shí)間過(guò)后發(fā)現(xiàn)沒(méi)有領(lǐng)導(dǎo)節(jié)點(diǎn)惭聂,就會(huì)切換到候選狀態(tài),發(fā)起選舉相恃。得到多數(shù)票的候選者就會(huì)成為領(lǐng)導(dǎo)節(jié)點(diǎn)辜纲。如果候選節(jié)點(diǎn)或當(dāng)前領(lǐng)導(dǎo)節(jié)點(diǎn)發(fā)現(xiàn)了更新的領(lǐng)導(dǎo)者,就會(huì)主動(dòng)退回跟隨狀態(tài)拦耐。
領(lǐng)導(dǎo)節(jié)點(diǎn)全權(quán)負(fù)責(zé)管理復(fù)制日志耕腾,也就是從客戶端接收請(qǐng)求,復(fù)制到跟隨節(jié)點(diǎn)杀糯,并告訴跟隨節(jié)點(diǎn)何時(shí)可以處理這些請(qǐng)求扫俺。如果領(lǐng)導(dǎo)節(jié)點(diǎn)故障或斷開(kāi)連接,就會(huì)重新進(jìn)行選舉固翰±俏常可見(jiàn),領(lǐng)導(dǎo)節(jié)點(diǎn)的存在大大簡(jiǎn)化了共識(shí)算法的設(shè)計(jì)倦挂。
領(lǐng)導(dǎo)任期
在上面的圖中出現(xiàn)了任期(term)這個(gè)詞畸颅。領(lǐng)導(dǎo)者并不是一直“在位”的,工作一段時(shí)間之后方援,就會(huì)選舉出新的領(lǐng)導(dǎo)者來(lái)接替它没炒。
由上圖可見(jiàn),藍(lán)色表示選舉時(shí)間段,綠色表示選舉出的領(lǐng)導(dǎo)者在位的時(shí)間段送火,這兩者合起來(lái)即稱作一個(gè)任期拳话,其計(jì)數(shù)值是自增的。任期的值就可以在邏輯上充當(dāng)時(shí)間戳种吸,每個(gè)節(jié)點(diǎn)都會(huì)保存一份自己所見(jiàn)的最新任期值弃衍,稱為currentTerm。另外坚俗,如果因?yàn)槠睌?shù)相同镜盯,沒(méi)能選出領(lǐng)導(dǎo),就會(huì)立即再發(fā)起新的選舉猖败。
選舉流程
如果一個(gè)或多個(gè)跟隨節(jié)點(diǎn)在選舉超時(shí)(election timeout)內(nèi)沒(méi)有收到領(lǐng)導(dǎo)節(jié)點(diǎn)的心跳(一個(gè)名為AppendEntries的RPC消息速缆,本意是做日志復(fù)制用途,但此時(shí)不攜帶日志數(shù)據(jù))恩闻,就會(huì)發(fā)起選舉流程:
- 增加本地的currentTerm值艺糜;
- 將自己切換到候選狀態(tài);
- 給自己投一票幢尚;
- 給其他節(jié)點(diǎn)發(fā)送名為RequestVote的RPC消息破停,請(qǐng)求投票;
- 等待其他節(jié)點(diǎn)的消息尉剩。
根據(jù)其他節(jié)點(diǎn)回復(fù)的消息真慢,會(huì)出現(xiàn)如下三種結(jié)果:
- 收到多數(shù)節(jié)點(diǎn)的投票,贏得選舉边涕,成為領(lǐng)導(dǎo)節(jié)點(diǎn)晤碘;
- 收到其他當(dāng)選節(jié)點(diǎn)發(fā)來(lái)的AppendEntries消息,轉(zhuǎn)換回跟隨節(jié)點(diǎn)功蜓;
- 選舉超時(shí)后沒(méi)收到多數(shù)票,也沒(méi)有其他節(jié)點(diǎn)當(dāng)選宠蚂,就保持候選狀態(tài)重新選舉式撼。
獲得多數(shù)票的節(jié)點(diǎn)只要當(dāng)選,就會(huì)立即給其他所有節(jié)點(diǎn)發(fā)送AppendEntries求厕,避免再次選舉著隆。另外,在同一任期內(nèi)呀癣,每個(gè)節(jié)點(diǎn)只能投一票美浦,并且先到先得(first-come-first-served),也就是會(huì)把票投給RequestVote消息第一個(gè)到達(dá)的那個(gè)節(jié)點(diǎn)项栏。
至于上面的第三種情況浦辨,也就是所謂“split vote”現(xiàn)象,容易在很多跟隨者變成候選者時(shí)出現(xiàn)沼沈,因?yàn)闆](méi)有節(jié)點(diǎn)能得到多數(shù)票流酬,選舉有可能無(wú)限繼續(xù)下去币厕。所以,Raft設(shè)置的選舉超時(shí)并不是完全一樣的芽腾,而是有些許隨機(jī)性旦装,來(lái)盡量使得投票能夠集中到那些較“快”的節(jié)點(diǎn)上。
日志復(fù)制
日志格式
領(lǐng)導(dǎo)節(jié)點(diǎn)選舉出來(lái)后摊滔,集群就可以開(kāi)始處理客戶端請(qǐng)求了阴绢。前面已經(jīng)說(shuō)過(guò),每個(gè)節(jié)點(diǎn)都維護(hù)著一個(gè)復(fù)制日志的隊(duì)列艰躺,它們的格式如下圖所示旱函。
可見(jiàn),日志由一個(gè)個(gè)按序排列的entry組成描滔。每個(gè)entry內(nèi)包含有請(qǐng)求的數(shù)據(jù)棒妨,還有該entry產(chǎn)生時(shí)的領(lǐng)導(dǎo)任期值。在論文中含长,每個(gè)節(jié)點(diǎn)上的日志隊(duì)列用一個(gè)數(shù)組log[]表示券腔。
復(fù)制流程
當(dāng)客戶端發(fā)來(lái)請(qǐng)求時(shí),領(lǐng)導(dǎo)節(jié)點(diǎn)首先將其加入自己的日志隊(duì)列拘泞,再并行地發(fā)送AppendEntries RPC消息給所有跟隨節(jié)點(diǎn)纷纫。領(lǐng)導(dǎo)節(jié)點(diǎn)收到來(lái)自多數(shù)跟隨者的回復(fù)之后,就認(rèn)為該請(qǐng)求可以提交了(見(jiàn)圖中的commited entries)陪腌。然后辱魁,領(lǐng)導(dǎo)節(jié)點(diǎn)將請(qǐng)求應(yīng)用(apply)到復(fù)制狀態(tài)機(jī),并通知跟隨節(jié)點(diǎn)也這樣做诗鸭。這兩步做完后染簇,就不會(huì)再回滾。
這種從提交到應(yīng)用的方式與最基礎(chǔ)的一致性協(xié)議——兩階段提交(2PC)有些相似强岸,但Raft只需要多數(shù)節(jié)點(diǎn)的確認(rèn)锻弓,并不需要全部節(jié)點(diǎn)都可用。
注意在上圖中蝌箍,領(lǐng)導(dǎo)節(jié)點(diǎn)和4個(gè)跟隨節(jié)點(diǎn)的日志并不完全相同青灼,這可能是由于跟隨節(jié)點(diǎn)反應(yīng)慢、網(wǎng)絡(luò)狀況差等原因妓盲。領(lǐng)導(dǎo)節(jié)點(diǎn)會(huì)不斷地重試發(fā)送AppendEntries杂拨,直到所有節(jié)點(diǎn)上的日志達(dá)到最終一致,而不實(shí)現(xiàn)強(qiáng)一致性悯衬。這就是CAP理論中在保證P的情況下弹沽,C與A無(wú)法兼得的體現(xiàn)。
日志復(fù)制的過(guò)程仍然遺留了一個(gè)問(wèn)題:如果領(lǐng)導(dǎo)或者跟隨節(jié)點(diǎn)發(fā)生異常情況而崩潰,如何保證日志的最終一致性贷币?它屬于下面的安全性問(wèn)題中的一部分击胜,稍后會(huì)解答它。
安全性
安全性是施加在領(lǐng)導(dǎo)選舉役纹、日志復(fù)制兩個(gè)解決方案上的約束偶摔,用于保證在異常情況下Raft算法仍然有效,不能破壞一致性促脉,也不能返回錯(cuò)誤的結(jié)果辰斋。所有分布式算法都應(yīng)保障安全性,在其基礎(chǔ)上再保證活性(liveness)瘸味。
Raft協(xié)議的安全性保障有5種宫仗,分別是:選舉安全性(election safety)、領(lǐng)導(dǎo)者只追加(leader append-only)旁仿、日志匹配(log matching)藕夫、領(lǐng)導(dǎo)者完全性(leader completeness)、狀態(tài)機(jī)安全性(state machine safety) 枯冈。下面分別來(lái)看毅贮。
選舉安全性
選舉安全性是指每個(gè)任期內(nèi)只允許選出最多一個(gè)領(lǐng)導(dǎo)。如果集群中有多于一個(gè)領(lǐng)導(dǎo)尘奏,就發(fā)生了腦裂(split brain)滩褥。根據(jù)“領(lǐng)導(dǎo)選舉”一節(jié)中的描述,Raft能夠保證選舉安全炫加,因?yàn)椋?/p>
- 在同一任期內(nèi)瑰煎,每個(gè)節(jié)點(diǎn)只能投一票;
- 只有獲得多數(shù)票的節(jié)點(diǎn)才能成為領(lǐng)導(dǎo)俗孝。
領(lǐng)導(dǎo)者只追加
在講解日志復(fù)制時(shí)酒甸,我們可以明顯地看出,客戶端發(fā)出的請(qǐng)求都是插入領(lǐng)導(dǎo)者日志隊(duì)列的尾部驹针,沒(méi)有修改或刪除的操作烘挫。這樣可以使領(lǐng)導(dǎo)者的行為盡量簡(jiǎn)單化,使之沒(méi)有任何不確定的行為柬甥,同時(shí)也作為下一節(jié)要說(shuō)的日志匹配的基礎(chǔ)。
日志匹配
日志匹配的具體描述如下其垄。
如果兩個(gè)節(jié)點(diǎn)的日志隊(duì)列中苛蒲,兩個(gè)entry具有相同的下標(biāo)和任期值,那么:
- 它們攜帶的客戶端請(qǐng)求相同绿满;
- 它們之前的所有entry也都相同臂外。
第一點(diǎn)自然由上一節(jié)的“領(lǐng)導(dǎo)者只追加”特性來(lái)保證,而第二點(diǎn)則由AppendEntries RPC消息的一個(gè)簡(jiǎn)單機(jī)制來(lái)保證:每條AppendEntries都會(huì)包含最新entry之前那個(gè)entry的下標(biāo)與任期值,如果跟隨節(jié)點(diǎn)在對(duì)應(yīng)下標(biāo)找不到對(duì)應(yīng)任期的日志漏健,就會(huì)拒絕接受并告知領(lǐng)導(dǎo)節(jié)點(diǎn)嚎货。
有了日志匹配特性,就可以解決日志復(fù)制中那個(gè)遺留問(wèn)題了蔫浆。假設(shè)由于節(jié)點(diǎn)崩潰殖属,跟隨節(jié)點(diǎn)的日志出現(xiàn)了多種異常情況,如下圖瓦盛。
注意圖中不是6個(gè)跟隨節(jié)點(diǎn)洗显,而是6種可能的情況。比如a和b是丟失了entry原环,c和d是有多余的未提交entry挠唆,e和f則是既有丟失又有冗余。這時(shí)領(lǐng)導(dǎo)節(jié)點(diǎn)就會(huì)找到兩個(gè)日志隊(duì)列中最近一條匹配的日志點(diǎn)嘱吗,將該點(diǎn)之后跟隨節(jié)點(diǎn)的所有日志都刪除玄组,然后將自己的這部分日志復(fù)制給它。例如對(duì)于上圖中的情況e來(lái)說(shuō)谒麦,最近一條匹配的日志下標(biāo)為5俄讹,那么5之后的所有entry都會(huì)被刪除,被替換成領(lǐng)導(dǎo)者的日志弄匕。
領(lǐng)導(dǎo)者完全性
領(lǐng)導(dǎo)者完全性是指颅悉,如果有一條日志在某個(gè)任期被提交了,那么它一定會(huì)出現(xiàn)在所有任期更大的領(lǐng)導(dǎo)者日志里迁匠。這也是由兩點(diǎn)來(lái)決定的:
- 日志提交的定義:該條日志被成功復(fù)制到多數(shù)節(jié)點(diǎn)才算是提交剩瓶;
- 選舉投票的附加限制:只有當(dāng)節(jié)點(diǎn)A的日志比節(jié)點(diǎn)B的日志更新時(shí),B才可能會(huì)投票給A城丧。也就是說(shuō)延曙,如果一個(gè)候選節(jié)點(diǎn)沒(méi)有包含所有被提交的日志,那么它一定不會(huì)被選舉為領(lǐng)導(dǎo)亡哄。
根據(jù)這兩個(gè)描述枝缔,每次選舉出的領(lǐng)導(dǎo)節(jié)點(diǎn)一定包含有最新的日志,因此只存在跟隨節(jié)點(diǎn)從領(lǐng)導(dǎo)節(jié)點(diǎn)更新日志的情況蚊惯,而不會(huì)反過(guò)來(lái)愿卸,這也使得一致性邏輯更加簡(jiǎn)化,并且為下面的狀態(tài)機(jī)安全性提供保證截型。
狀態(tài)機(jī)安全性
狀態(tài)機(jī)安全性是說(shuō)趴荸,如果一個(gè)節(jié)點(diǎn)已經(jīng)向其復(fù)制狀態(tài)機(jī)應(yīng)用了一條日志中的請(qǐng)求,那么對(duì)于其他節(jié)點(diǎn)的同一下標(biāo)的日志宦焦,不能應(yīng)用不同的請(qǐng)求发钝。這句話就很拗口了顿涣,因此我們來(lái)看一種意外的情況。
- 在時(shí)刻a酝豪,節(jié)點(diǎn)S1是領(lǐng)導(dǎo)者涛碑,第2個(gè)任期的日志只復(fù)制給了S2就崩潰了。
- 在時(shí)刻b孵淘,S5被選舉為領(lǐng)導(dǎo)者蒲障,第3個(gè)任期的日志還沒(méi)來(lái)得及復(fù)制,也崩潰了夺英。
- 在時(shí)刻c晌涕,S1又被選舉為領(lǐng)導(dǎo)者,繼續(xù)復(fù)制日志痛悯,將任期2的日志給了S3余黎。此時(shí)該日志復(fù)制給了多數(shù)節(jié)點(diǎn),但還未提交载萌。
- 在時(shí)刻d惧财,S1又崩潰,并且S5重新被選舉為領(lǐng)導(dǎo)者扭仁,將任期3的日志復(fù)制給S0~S4垮衷。
這里就有問(wèn)題了,在時(shí)刻c的日志與新領(lǐng)導(dǎo)者的日志發(fā)生了沖突乖坠,此時(shí)狀態(tài)機(jī)是不安全的搀突。
為了解決該問(wèn)題,Raft不允許領(lǐng)導(dǎo)者在當(dāng)選后提交“前任”的日志熊泵,而是通過(guò)日志匹配原則仰迁,在處理“現(xiàn)任”日志時(shí)將之前的日志一同提交。具體方法是:在領(lǐng)導(dǎo)者任期開(kāi)始時(shí)顽分,立刻提交一條空的日志徐许,所以上圖中時(shí)刻c的情況不會(huì)發(fā)生,而是像時(shí)刻e一樣先提交任期4的日志卒蘸,連帶提交任期2的日志雌隅。就算此時(shí)S1再崩潰,S5也不會(huì)重新被選舉了缸沃。
The End
如果想要更直觀地理解Raft恰起,建議參考這里,是一個(gè)用動(dòng)畫(huà)來(lái)描述該算法的網(wǎng)頁(yè)趾牧,形象生動(dòng)村缸。