可進入我的博客查看原文。
Raft 算法是可以用來替代 Paxos 算法的分布式一致性算法,而且 raft 算法比 Paxos 算法更易懂且更容易實現(xiàn)撞反。本文對 raft 論文進行翻譯,希望能有助于讀者更方便地理解 raft 的思想。如果對 Paxos 算法感興趣茄螃,可以看我的另一篇文章:分布式系列文章——Paxos算法原理與推導(dǎo)
摘要
Raft 是用來管理復(fù)制日志(replicated log)的一致性協(xié)議。它跟 multi-Paxos 作用相同连锯,效率也相當归苍,但是它的組織結(jié)構(gòu)跟 Paxos 不同。這使得 Raft 比 Paxos 更容易理解并且更容易在工程實踐中實現(xiàn)萎庭。為了使 Raft 協(xié)議更易懂霜医,Raft將一致性的關(guān)鍵元素分開,如 leader 選舉驳规、日志復(fù)制和安全性肴敛,并且它實施更強的一致性以減少必須考慮的狀態(tài)的數(shù)量。用戶研究的結(jié)果表明吗购,Raft 比 Paxos 更容易學(xué)習(xí)医男。 Raft 還包括一個用于變更集群成員的新機制,它使用重疊的大多數(shù)(overlapping majorities)來保證安全性捻勉。
1 介紹
一致性算法允許多臺機器作為一個集群協(xié)同工作镀梭,并且在其中的某幾臺機器出故障時集群仍然能正常工作。 正因為如此踱启,一致性算法在建立可靠的大規(guī)模軟件系統(tǒng)方面發(fā)揮了關(guān)鍵作用报账。 在過去十年中,Paxos [15,16] 主導(dǎo)了關(guān)于一致性算法的討論:大多數(shù)一致性的實現(xiàn)都是基于 Paxos 或受其影響埠偿,Paxos 已成為用于教授學(xué)生一致性相關(guān)知識的主要工具透罢。
不幸的是,Paxos 實在是太難以理解冠蒋,盡管許多人一直在努力嘗試使其更易懂羽圃。 此外,其架構(gòu)需要復(fù)雜的改變來支持實際系統(tǒng)抖剿。 結(jié)果是朽寞,系統(tǒng)開發(fā)者和學(xué)生都在與 Paxos 斗爭识窿。
在我們自己與 Paxos 斗爭之后,我們開始著手尋找一個新的一致性算法脑融,可以為系統(tǒng)開發(fā)和教學(xué)提供更好的基礎(chǔ)喻频。 我們的方法是不尋常的,因為我們的主要目標是可理解性:我們可以為實際系統(tǒng)定義一個一致性算法吨掌,并以比 Paxos 更容易學(xué)習(xí)的方式描述它嗎半抱?在該算法的設(shè)計過程中,重要的不僅是如何讓該算法起作用膜宋,還有清晰地知道該算法為什么會起作用窿侈。
這項工作的結(jié)果是一個稱為 Raft 的一致性算法。 在設(shè)計 Raft 時秋茫,我們使用了特定的技術(shù)來提高可理解性史简,包括分解(Raft 分離 leader 選舉,日志復(fù)制和安全)和狀態(tài)空間減少(相對于 Paxos 肛著,Raft 減少了不確定性程度和服務(wù)器之間彼此不一致的方式 )圆兵。 一項針對兩個大學(xué)的 43 名學(xué)生的用戶研究表明,Raft 比 Paxos 更容易理解:在學(xué)習(xí)兩種算法后枢贿,其中 33 名學(xué)生能夠更好地回答關(guān)于 Raft 的問題殉农。
Raft 在許多方面類似于現(xiàn)有的一致性算法(尤其是 Oki 和 Liskov 的 Viewstamped Replication [29,22]),但它有幾個新特性:
- Strong leader:在 Raft 中局荚,日志條目(log entries)只從 leader 流向其他服務(wù)器超凳。 這簡化了復(fù)制日志的管理,使得 raft 更容易理解耀态。
- Leader 選舉:Raft 使用隨機計時器進行 leader 選舉轮傍。 這只需在任何一致性算法都需要的心跳(heartbeats)上增加少量機制,同時能夠簡單快速地解決沖突首装。
- 成員變更:Raft 使用了一種新的聯(lián)合一致性方法创夜,其中兩個不同配置的大多數(shù)在過渡期間重疊。 這允許集群在配置更改期間繼續(xù)正常運行仙逻。
我們認為驰吓,Raft 優(yōu)于 Paxos 和其他一致性算法,不僅在教學(xué)方面系奉,在工程實現(xiàn)方面也是檬贰。 它比其他算法更簡單且更易于理解; 它被描述得十分詳細足以滿足實際系統(tǒng)的需要; 它有多個開源實現(xiàn),并被多家公司使用; 它的安全性已被正式規(guī)定和驗證; 它的效率與其他算法相當喜最。
本文的剩余部分介紹了復(fù)制狀態(tài)機問題(第 2 節(jié)),討論了 Paxos 的優(yōu)點和缺點(第3節(jié))庄蹋,描述了我們實現(xiàn)易理解性的方法(第 4 節(jié))瞬内,提出了Raft一致性算法(第 5-8 節(jié))迷雪,評估Raft(第 9 節(jié)),并討論了相關(guān)工作(第 10 節(jié))虫蝶。
2 復(fù)制狀態(tài)機(Replicated state machines)
一致性算法是在復(fù)制狀態(tài)機[37]的背景下產(chǎn)生的章咧。 在這種方法中,一組服務(wù)器上的狀態(tài)機計算相同狀態(tài)的相同副本能真,并且即使某些服務(wù)器宕機赁严,也可以繼續(xù)運行。
復(fù)制狀態(tài)機用于解決分布式系統(tǒng)中的各種容錯問題粉铐。 例如疼约,具有單個 leader 的大規(guī)模系統(tǒng),如 GFS [8]蝙泼,HDFS [38] 和 RAMCloud [33] 程剥,通常使用單獨的復(fù)制狀態(tài)機來進行 leader 選舉和存儲 leader 崩潰后重新選舉需要的配置信息。Chubby [2] 和 ZooKeeper [11] 都是復(fù)制狀態(tài)機汤踏。
復(fù)制狀態(tài)機通常使用復(fù)制日志實現(xiàn)织鲸,如圖1所示。每個服務(wù)器存儲一個包含一系列命令的日志溪胶,其狀態(tài)機按順序執(zhí)行日志中的命令搂擦。 每個日志中命令都相同并且順序也一樣,因此每個狀態(tài)機處理相同的命令序列哗脖。 這樣就能得到相同的狀態(tài)和相同的輸出序列瀑踢。
一致性算法的工作就是保證復(fù)制日志的一致性。 每臺服務(wù)器上的一致性模塊接收來自客戶端的命令懒熙,并將它們添加到其日志中丘损。 它與其他服務(wù)器上的一致性模塊通信,以確保每個日志最終以相同的順序包含相同的命令工扎,即使有一些服務(wù)器失敗徘钥。 一旦命令被正確復(fù)制,每個服務(wù)器上的狀態(tài)機按日志順序處理它們肢娘,并將輸出返回給客戶端呈础。 這樣就形成了高可用的復(fù)制狀態(tài)機。
實際系統(tǒng)中的一致性算法通常具有以下屬性:
它們確保在所有非拜占庭條件下(包括網(wǎng)絡(luò)延遲橱健,分區(qū)和數(shù)據(jù)包丟失而钞,重復(fù)和亂序)的安全性(不會返回不正確的結(jié)果)。
只要任何大多數(shù)(過半)服務(wù)器都可以運行拘荡,并且可以相互通信和與客戶通信臼节,一致性算法就可用。 因此,五臺服務(wù)器的典型集群可以容忍任何兩臺服務(wù)器的故障网缝。 假設(shè)服務(wù)器突然宕機; 它們可以稍后從狀態(tài)恢復(fù)并重新加入群集巨税。
它們不依賴于時序來確保日志的一致性:錯誤的時鐘和極端消息延遲可能在最壞的情況下導(dǎo)致可用性問題。
在通常情況下粉臊,只要集群的大部分(過半服務(wù)器)已經(jīng)響應(yīng)了單輪遠程過程調(diào)用草添,命令就可以完成; 少數(shù)(一半以下)慢服務(wù)器不需要影響整個系統(tǒng)性能。
3 Paxos 存在的問題
在過去十年里扼仲,Leslie Lamport 的 Paxos 協(xié)議[15]幾乎成為一致性的同義詞:它是課堂上教授最多的一致性協(xié)議驰后,并且大多數(shù)一致性的實現(xiàn)也以它為起點。 Paxos 首先定義了能夠在單個決策(例如單個復(fù)制日志條目)上達成一致的協(xié)議。 我們將這個子集稱為 single-decree Paxos春塌。 然后 Paxos 組合該協(xié)議的多個實例以促進一系列決策,例如日志(multi-Paxos)。 Paxos能夠確保安全性和活性癞季,并且支持集群成員的變更。它的正確性已被證明瓤荔,并且在正常情況下是高效的。
不幸的是贮懈,Paxos 有兩個顯著的缺點魂拦。 第一個缺點是 Paxos 非常難以理解安疗。 Paxos 的描述晦澀難懂屈嗤,臭名昭著(譯者注:《The Part-time Parliament》比較晦澀難懂饶号,但是《Paxos Made Simple》就比較容易理解); 很少有人成功地理解它濒生,即使能理解也必須付出巨大的努力。 因此浴井,已有幾個嘗試以更簡單的方式來描述 Paxos [16,20,21] 洪囤。 這些描述集中在 single-degree Paxos ,但它們?nèi)匀痪哂刑魬?zhàn)性撕氧。 在對 NSDI 2012 參會者的非正式調(diào)查中瘤缩,我們發(fā)現(xiàn)很少有人喜歡 Paxos ,即使是經(jīng)驗豐富的研究人員伦泥。 我們自己也跟 Paxos 進行了艱苦的斗爭; 我們也無法完全理解整個協(xié)議剥啤,直到閱讀了幾個更簡單的描述和自己設(shè)計替代 Paxos 的協(xié)議锦溪,整個過程花了將近一年。
Paxos 晦澀難懂的原因是作者選擇了single-degree Paxos作為基礎(chǔ)府怯。Single-decree Paxos 分成兩個階段刻诊,這兩個階段沒有簡單直觀的說明,并且不能被單獨理解牺丙。因此则涯,很難理解為什么該算法能起作用。Multi-Paxos 的合成規(guī)則又增加了許多復(fù)雜性冲簿。我們相信是整,對多個決定(日志而不是單個日志條目)達成一致的總體問題可以用其他更直接和更明顯的方式進行分解。
Paxos的第二個問題是它不能為構(gòu)建實際的實現(xiàn)提供良好的基礎(chǔ)民假。 一個原因是沒有針對 multi-Paxos 的廣泛同意的算法。 Lamport的描述主要是關(guān)于 single-decree Paxos; 他描述了 multi-Paxos 的可能方法龙优,但缺少許多細節(jié)羊异。 已經(jīng)有幾個嘗試來具體化和優(yōu)化 Paxos ,例如[26]彤断,[39]和[13]野舶,但這些彼此各不相同并且跟 Lamport 描述的也不同。 像Chubby [4] 這樣的系統(tǒng)已經(jīng)實現(xiàn)了類 Paxos(Paxos-like)算法宰衙,但大多數(shù)情況下平道,它們的細節(jié)并沒有公布。
此外供炼,Paxos 的架構(gòu)對于構(gòu)建實際系統(tǒng)來說是一個糟糕的設(shè)計一屋,這是 single-decree 分解的另一個結(jié)果。 例如袋哼,獨立地選擇日志條目集合冀墨,然后再將它們合并到順序日志中幾乎沒有任何好處,這只會增加復(fù)雜性涛贯。 圍繞日志設(shè)計系統(tǒng)是更簡單和有效的方法诽嘉,新日志條目按照約束順序地添加到日志中。 Paxos 的做法適用于只需要做一次決策的情況弟翘,如果需要做一系列決策虫腋,更簡單和快速的方法是先選擇一個 leader ,然后讓該 leader 協(xié)調(diào)這些決策稀余。
因此悦冀,實際的系統(tǒng)跟 Paxos 相差很大。幾乎所有的實現(xiàn)都是從 Paxos 開始睛琳,然后發(fā)現(xiàn)很多實現(xiàn)上的難題雏门,接著就開發(fā)了一種和 Paxos 完全不一樣的架構(gòu)嘿歌。這樣既費時又容易出錯,而且 Paxos 本身晦澀難懂使得該問題更加嚴重茁影。Paxos 的公式可能可以很好地證明它的正確性宙帝,但是現(xiàn)實的系統(tǒng)和 Paxos 差別是如此之大,以至于這些證明并沒有什么太大的價值募闲。下面來自 Chubby 作者的評論非常典型:
在Paxos算法描述和實現(xiàn)現(xiàn)實系統(tǒng)中間有著巨大的鴻溝步脓。最終的系統(tǒng)往往建立在一個還未被證明的協(xié)議之上。
由于以上問題浩螺,我們得出的結(jié)論是 Paxos 算法沒有為系統(tǒng)實踐和教學(xué)提供一個良好的基礎(chǔ)靴患。考慮到一致性問題在大規(guī)模軟件系統(tǒng)中的重要性要出,我們決定嘗試設(shè)計一個能夠替代 Paxos 并且具有更好特性的一致性算法鸳君。Raft算法就是這次實驗的結(jié)果。
4 為可理解性而設(shè)計
在設(shè)計 Raft 算法過程中我們有幾個目標:它必須提供一個完整的實際的系統(tǒng)實現(xiàn)基礎(chǔ)患蹂,這樣才能大大減少開發(fā)者的工作或颊;它必須在任何情況下都是安全的并且在典型的應(yīng)用條件下是可用的;并且在正常情況下是高效的传于。但是我們最重要的目標也是最大的挑戰(zhàn)是可理解性囱挑。它必須保證能夠被大多數(shù)人容易地理解。另外沼溜,它必須能夠讓人形成直觀的認識平挑,這樣系統(tǒng)的構(gòu)建者才能夠在現(xiàn)實中進行擴展。
在設(shè)計 Raft 算法的時候系草,很多情況下我們需要在多個備選方案中進行選擇通熄。在這種情況下,我們基于可理解性來評估備選方案:解釋各個備選方案的難道有多大(例如找都,Raft 的狀態(tài)空間有多復(fù)雜棠隐,是否有微妙的含義)?對于一個讀者而言檐嚣,完全理解這個方案和含義是否容易助泽?
我們意識到這樣的分析具有高度的主觀性;但是我們使用了兩種通用的技術(shù)來解決這個問題嚎京。第一個技術(shù)就是眾所周知的問題分解:只要有可能嗡贺,我們就將問題分解成幾個相對獨立的,可被解決的鞍帝、可解釋的和可理解的子問題诫睬。例如,Raft 算法被我們分成 leader 選舉帕涌,日志復(fù)制摄凡,安全性和成員變更幾個部分续徽。
我們使用的第二個方法是通過減少狀態(tài)的數(shù)量來簡化狀態(tài)空間,使得系統(tǒng)更加連貫并且盡可能消除不確定性亲澡。特別的钦扭,所有的日志是不允許有空洞的,并且 Raft 限制了使日志之間不一致的方式床绪。盡管在大多數(shù)情況下我們都試圖去消除不確定性客情,但是在某些情況下不確定性可以提高可理解性。特別是癞己,隨機化方法雖然引入了不確定性膀斋,但是他們往往能夠通過使用相近的方法處理可能的選擇來減少狀態(tài)空間。我們使用隨機化來簡化 Raft 中的 leader 選舉算法痹雅。
5 Raft 一致性算法
Raft 是一種用來管理第 2 節(jié)中描述的復(fù)制日志的算法仰担。圖 2 是該算法的濃縮,可用作參考绩社,圖 3 列舉了該算法的一些關(guān)鍵特性摔蓝。圖中的這些內(nèi)容將在剩下的章節(jié)中逐一介紹。
Raft 通過首先選舉一個 distinguished leader铃将,然后讓它全權(quán)負責管理復(fù)制日志來實現(xiàn)一致性。Leader 從客戶端接收日志條目哑梳,把日志條目復(fù)制到其他服務(wù)器上劲阎,并且在保證安全性的時候通知其他服務(wù)器將日志條目應(yīng)用到他們的狀態(tài)機中。擁有一個 leader 大大簡化了對復(fù)制日志的管理鸠真。例如悯仙,領(lǐng)導(dǎo)人可以決定新的日志條目需要放在日志中的什么位置而不需要和其他服務(wù)器商議,并且數(shù)據(jù)都是從 leader 流向其他服務(wù)器吠卷。leader 可能宕機锡垄,也可能和其他服務(wù)器斷開連接,這時一個新的 leader 會被選舉出來祭隔。
通過選舉一個 leader 的方式货岭,Raft 將一致性問題分解成了三個相對獨立的子問題,這些問題將會在接下來的子章節(jié)中進行討論:
- Leader 選舉:當前的 leader 宕機時疾渴,一個新的 leader 必須被選舉出來千贯。(章節(jié) 5.2)
- 日志復(fù)制:Leader 必須從客戶端接收日志條目然后復(fù)制到集群中的其他節(jié)點,并且強制要求其他節(jié)點的日志和自己的保持一致搞坝。
- 安全性:Raft 中安全性的關(guān)鍵是圖 3 中狀態(tài)機的安全性:如果有任何的服務(wù)器節(jié)點已經(jīng)應(yīng)用了一個特定的日志條目到它的狀態(tài)機中搔谴,那么其他服務(wù)器節(jié)點不能在同一個日志索引位置應(yīng)用一條不同的指令。章節(jié) 5.4 闡述了 Raft 算法是如何保證這個特性的桩撮;該解決方案在選舉機制(5.2 節(jié))上增加了額外的限制敦第。
在展示一致性算法之后峰弹,本章節(jié)將討論可用性的一些問題以及時序在系統(tǒng)中的作用。
5.1 Raft 基礎(chǔ)
一個 Raft 集群包含若干個服務(wù)器節(jié)點芜果;通常是 5 個鞠呈,這樣的系統(tǒng)可以容忍 2 個節(jié)點的失效。在任何時刻师幕,每一個服務(wù)器節(jié)點都處于這三個狀態(tài)之一:leader粟按、follower 或者 candidate 。在正常情況下霹粥,集群中只有一個 leader 并且其他的節(jié)點全部都是 follower 灭将。Follower 都是被動的:他們不會發(fā)送任何請求,只是簡單的響應(yīng)來自 leader 和 candidate 的請求后控。Leader 處理所有的客戶端請求(如果一個客戶端和 follower 通信庙曙,follower 會將請求重定向給 leader)。第三種狀態(tài)浩淘,candidate 捌朴,是用來選舉一個新的 leader(章節(jié) 5.2)。圖 4 展示了這些狀態(tài)和他們之間的轉(zhuǎn)換關(guān)系张抄;這些轉(zhuǎn)換關(guān)系在接下來會進行討論砂蔽。
Raft 把時間分割成任意長度的任期(term),如圖 5 所示署惯。任期用連續(xù)的整數(shù)標記左驾。每一段任期從一次選舉開始,一個或者多個 candidate 嘗試成為 leader 极谊。如果一個 candidate 贏得選舉诡右,然后他就在該任期剩下的時間里充當 leader 。在某些情況下轻猖,一次選舉無法選出 leader 帆吻。在這種情況下,這一任期會以沒有 leader 結(jié)束咙边;一個新的任期(包含一次新的選舉)會很快重新開始猜煮。Raft 保證了在任意一個任期內(nèi),最多只有一個 leader 败许。
不同的服務(wù)器節(jié)點觀察到的任期轉(zhuǎn)換的次數(shù)可能不同友瘤,在某些情況下,一個服務(wù)器節(jié)點可能沒有看到 leader 選舉過程或者甚至整個任期全程檐束。任期在 Raft 算法中充當邏輯時鐘的作用辫秧,這使得服務(wù)器節(jié)點可以發(fā)現(xiàn)一些過期的信息比如過時的 leader 。每一個服務(wù)器節(jié)點存儲一個當前任期號被丧,該編號隨著時間單調(diào)遞增盟戏。服務(wù)器之間通信的時候會交換當前任期號绪妹;如果一個服務(wù)器的當前任期號比其他的小,該服務(wù)器會將自己的任期號更新為較大的那個值柿究。如果一個 candidate 或者 leader 發(fā)現(xiàn)自己的任期號過期了邮旷,它會立即回到 follower 狀態(tài)。如果一個節(jié)點接收到一個包含過期的任期號的請求蝇摸,它會直接拒絕這個請求婶肩。
Raft 算法中服務(wù)器節(jié)點之間使用 RPC 進行通信,并且基本的一致性算法只需要兩種類型的 RPC貌夕。請求投票(RequestVote) RPC 由 candidate 在選舉期間發(fā)起(章節(jié) 5.2)律歼,追加條目(AppendEntries)RPC 由 leader 發(fā)起,用來復(fù)制日志和提供一種心跳機制(章節(jié) 5.3)啡专。第 7 節(jié)為了在服務(wù)器之間傳輸快照增加了第三種 RPC险毁。當服務(wù)器沒有及時的收到 RPC 的響應(yīng)時,會進行重試们童, 并且他們能夠并行的發(fā)起 RPC 來獲得最佳的性能畔况。
5.2 Leader 選舉
Raft 使用一種心跳機制來觸發(fā) leader 選舉。當服務(wù)器程序啟動時慧库,他們都是 follower 跷跪。一個服務(wù)器節(jié)點只要能從 leader 或 candidate 處接收到有效的 RPC 就一直保持 follower 狀態(tài)。Leader 周期性地向所有 follower 發(fā)送心跳(不包含日志條目的 AppendEntries RPC)來維持自己的地位齐板。如果一個 follower 在一段選舉超時時間內(nèi)沒有接收到任何消息吵瞻,它就假設(shè)系統(tǒng)中沒有可用的 leader ,然后開始進行選舉以選出新的leader覆积。
要開始一次選舉過程薛窥,follower 先增加自己的當前任期號并且轉(zhuǎn)換到 candidate 狀態(tài)支示。然后投票給自己并且并行地向集群中的其他服務(wù)器節(jié)點發(fā)送 RequestVote RPC(讓其他服務(wù)器節(jié)點投票給它)。Candidate 會一直保持當前狀態(tài)直到以下三件事情之一發(fā)生:(a) 它自己贏得了這次的選舉(收到過半的投票)并级,(b) 其他的服務(wù)器節(jié)點成為 leader 庵朝,(c) 一段時間之后沒有任何獲勝者吗冤。這些結(jié)果會在下面的章節(jié)里分別討論。
當一個 candidate 獲得集群中過半服務(wù)器節(jié)點針對同一個任期的投票九府,它就贏得了這次選舉并成為 leader 椎瘟。對于同一個任期,每個服務(wù)器節(jié)點只會投給一個 candidate 侄旬,按照先來先服務(wù)(first-come-first-served)的原則(注意:5.4 節(jié)在投票上增加了額外的限制)肺蔚。要求獲得過半投票的規(guī)則確保了最多只有一個 candidate 贏得此次選舉(圖 3 中的選舉安全性)。一旦 candidate 贏得選舉儡羔,就立即成為 leader 宣羊。然后它會向其他的服務(wù)器節(jié)點發(fā)送心跳消息來確定自己的地位并阻止新的選舉璧诵。
在等待投票期間,candidate 可能會收到另一個聲稱自己是 leader 的服務(wù)器節(jié)點發(fā)來的 AppendEntries RPC 仇冯。如果這個 leader 的任期號(包含在RPC中)不小于 candidate 當前的任期號之宿,那么 candidate 會承認該 leader 的合法地位并回到 follower 狀態(tài)。 如果 RPC 中的任期號比自己的小苛坚,那么 candidate 就會拒絕這次的 RPC 并且繼續(xù)保持 candidate 狀態(tài)比被。
第三種可能的結(jié)果是 candidate 既沒有贏得選舉也沒有輸:如果有多個 follower 同時成為 candidate ,那么選票可能會被瓜分以至于沒有 candidate 贏得過半的投票泼舱。當這種情況發(fā)生時等缀,每一個候選人都會超時,然后通過增加當前任期號來開始一輪新的選舉柠掂。然而项滑,如果沒有其他機制的話,該情況可能會無限重復(fù)涯贞。
Raft 算法使用隨機選舉超時時間的方法來確保很少發(fā)生選票瓜分的情況枪狂,就算發(fā)生也能很快地解決。為了阻止選票一開始就被瓜分宋渔,選舉超時時間是從一個固定的區(qū)間(例如 150-300 毫秒)隨機選擇州疾。這樣可以把服務(wù)器都分散開以至于在大多數(shù)情況下只有一個服務(wù)器會選舉超時;然后該服務(wù)器贏得選舉并在其他服務(wù)器超時之前發(fā)送心跳皇拣。同樣的機制被用來解決選票被瓜分的情況严蓖。每個 candidate 在開始一次選舉的時候會重置一個隨機的選舉超時時間,然后一直等待直到選舉超時氧急;這樣減小了在新的選舉中再次發(fā)生選票瓜分情況的可能性颗胡。9.3 節(jié)展示了該方案能夠快速地選出一個 leader 。
選舉的例子可以很好地展示可理解性是如何指導(dǎo)我們選擇設(shè)計方案的吩坝。起初我們打算使用一種等級系統(tǒng)(ranking system):每一個 candidate 都被賦予一個唯一的等級(rank)毒姨,等級用來在競爭的 candidate 之間進行選擇。如果一個 candidate 發(fā)現(xiàn)另一個 candidate 擁有更高的等級钉寝,它就會回到 follower 狀態(tài)弧呐,這樣高等級的 candidate 能夠更加容易地贏得下一次選舉。但是我們發(fā)現(xiàn)這種方法在可用性方面會有一下小問題嵌纲。我們對該算法進行了多次調(diào)整俘枫,但是每次調(diào)整之后都會有新的小問題。最終我們認為隨機重試的方法更加顯然且易于理解逮走。
5.3 日志復(fù)制
Leader 一旦被選舉出來鸠蚪,就開始為客戶端請求提供服務(wù)。客戶端的每一個請求都包含一條將被復(fù)制狀態(tài)機執(zhí)行的指令茅信。Leader 把該指令作為一個新的條目追加到日志中去酣栈,然后并行的發(fā)起 AppendEntries RPC 給其他的服務(wù)器,讓它們復(fù)制該條目汹押。當該條目被安全地復(fù)制(下面會介紹)矿筝,leader 會應(yīng)用該條目到它的狀態(tài)機中(狀態(tài)機執(zhí)行該指令)然后把執(zhí)行的結(jié)果返回給客戶端。如果 follower 崩潰或者運行緩慢棚贾,或者網(wǎng)絡(luò)丟包窖维,領(lǐng)導(dǎo)人會不斷地重試 AppendEntries RPC(即使已經(jīng)回復(fù)了客戶端)直到所有的 follower 最終都存儲了所有的日志條目。
日志以圖 6 展示的方式組織妙痹。每個日志條目存儲一條狀態(tài)機指令和 leader 收到該指令時的任期號铸史。任期號用來檢測多個日志副本之間的不一致情況,同時也用來保證圖 3 中的某些性質(zhì)怯伊。每個日志條目都有一個整數(shù)索引值來表明它在日志中的位置琳轿。
Leader 決定什么時候把日志條目應(yīng)用到狀態(tài)機中是安全的;這種日志條目被稱為已提交的耿芹。Raft 算法保證所有已提交的日志條目都是持久化的并且最終會被所有可用的狀態(tài)機執(zhí)行崭篡。一旦創(chuàng)建該日志條目的 leader 將它復(fù)制到過半的服務(wù)器上,該日志條目就會被提交(例如在圖 6 中的條目 7)吧秕。同時琉闪,leader 日志中該日志條目之前的所有日志條目也都會被提交,包括由其他 leader 創(chuàng)建的條目砸彬。5.4 節(jié)討論在 leader 變更之后應(yīng)用該規(guī)則的一些細節(jié)颠毙,并且證明了這種提交的規(guī)則是安全的。Leader 追蹤將會被提交的日志條目的最大索引砂碉,未來的所有 AppendEntries RPC 都會包含該索引蛀蜜,這樣其他的服務(wù)器才能最終知道哪些日志條目需要被提交。Follower 一旦知道某個日志條目已經(jīng)被提交就會將該日志條目應(yīng)用到自己的本地狀態(tài)機中(按照日志的順序)增蹭。
我們設(shè)計了 Raft 的日志機制來維持不同服務(wù)器之間日志高層次的一致性滴某。這么做不僅簡化了系統(tǒng)的行為也使得系統(tǒng)行為更加可預(yù)測,同時該機制也是保證安全性的重要組成部分沪铭。Raft 維護著以下特性壮池,這些同時也構(gòu)成了圖 3 中的日志匹配特性:
- 如果不同日志中的兩個條目擁有相同的索引和任期號偏瓤,那么他們存儲了相同的指令杀怠。
- 如果不同日志中的兩個條目擁有相同的索引和任期號,那么他們之前的所有日志條目也都相同厅克。
Leader 在特定的任期號內(nèi)的一個日志索引處最多創(chuàng)建一個日志條目赔退,同時日志條目在日志中的位置也從來不會改變。該點保證了上面的第一條特性。第二個特性是由 AppendEntries RPC 執(zhí)行一個簡單的一致性檢查所保證的硕旗。在發(fā)送 AppendEntries RPC 的時候窗骑,leader 會將前一個日志條目的索引位置和任期號包含在里面。如果 follower 在它的日志中找不到包含相同索引位置和任期號的條目漆枚,那么他就會拒絕該新的日志條目创译。一致性檢查就像一個歸納步驟:一開始空的日志狀態(tài)肯定是滿足 Log Matching Property(日志匹配特性) 的,然后一致性檢查保證了日志擴展時的日志匹配特性墙基。因此软族,每當 AppendEntries RPC 返回成功時,leader 就知道 follower 的日志一定和自己相同(從第一個日志條目到最新條目)残制。
正常操作期間立砸,leader 和 follower 的日志保持一致,所以 AppendEntries RPC 的一致性檢查從來不會失敗初茶。然而颗祝,leader 崩潰的情況會使日志處于不一致的狀態(tài)(老的 leader 可能還沒有完全復(fù)制它日志里的所有條目)。這種不一致會在一系列的 leader 和 follower 崩潰的情況下加劇恼布。圖 7 展示了在什么情況下 follower 的日志可能和新的 leader 的日志不同螺戳。Follower 可能缺少一些在新 leader 中有的日志條目,也可能擁有一些新 leader 沒有的日志條目折汞,或者同時發(fā)生温峭。缺失或多出日志條目的情況可能會涉及到多個任期。
圖 7:當一個 leader 成功當選時(最上面那條日志)字支,follower 可能是(a-f)中的任何情況凤藏。每一個盒子表示一個日志條目;里面的數(shù)字表示任期號堕伪。Follower 可能會缺少一些日志條目(a-b)揖庄,可能會有一些未被提交的日志條目(c-d),或者兩種情況都存在(e-f)欠雌。例如蹄梢,場景 f 可能這樣發(fā)生,f 對應(yīng)的服務(wù)器在任期 2 的時候是 leader 富俄,追加了一些日志條目到自己的日志中禁炒,一條都還沒提交(commit)就崩潰了;該服務(wù)器很快重啟霍比,在任期 3 重新被選為 leader幕袱,又追加了一些日志條目到自己的日志中;在這些任期 2 和任期 3 中的日志都還沒被提交之前悠瞬,該服務(wù)器又宕機了们豌,并且在接下來的幾個任期里一直處于宕機狀態(tài)涯捻。
在 Raft 算法中,leader 通過強制 follower 復(fù)制它的日志來解決不一致的問題望迎。這意味著 follower 中跟 leader 沖突的日志條目會被 leader 的日志條目覆蓋障癌。5.4 節(jié)會證明通過增加一個限制可以保證安全性。
要使得 follower 的日志跟自己一致辩尊,leader 必須找到兩者達成一致的最大的日志條目(索引最大)涛浙,刪除 follower 日志中從那個點之后的所有日志條目,并且將自己從那個點之后的所有日志條目發(fā)送給 follower 摄欲。所有的這些操作都發(fā)生在對 AppendEntries RPCs 中一致性檢查的回復(fù)中蝗拿。Leader 針對每一個 follower 都維護了一個 nextIndex ,表示 leader 要發(fā)送給 follower 的下一個日志條目的索引蒿涎。當選出一個新 leader 時哀托,該 leader 將所有 nextIndex 的值都初始化為自己最后一個日志條目的 index 加1(圖 7 中的 11)。如果 follower 的日志和 leader 的不一致劳秋,那么下一次 AppendEntries RPC 中的一致性檢查就會失敗仓手。在被 follower 拒絕之后,leaer 就會減小 nextIndex 值并重試 AppendEntries RPC 玻淑。最終 nextIndex 會在某個位置使得 leader 和 follower 的日志達成一致嗽冒。此時,AppendEntries RPC 就會成功补履,將 follower 中跟 leader 沖突的日志條目全部刪除然后追加 leader 中的日志條目(如果有需要追加的日志條目的話)添坊。一旦 AppendEntries RPC 成功,follower 的日志就和 leader 一致箫锤,并且在該任期接下來的時間里保持一致贬蛙。
如果想要的話,該協(xié)議可以被優(yōu)化來減少被拒絕的 AppendEntries RPC 的個數(shù)谚攒。例如阳准,當拒絕一個 AppendEntries RPC 的請求的時候,follower 可以包含沖突條目的任期號和自己存儲的那個任期的第一個 index 馏臭。借助這些信息野蝇,leader 可以跳過那個任期內(nèi)所有沖突的日志條目來減小 nextIndex;這樣就變成每個有沖突日志條目的任期需要一個 AppendEntries RPC 而不是每個條目一次括儒。在實踐中绕沈,我們認為這種優(yōu)化是沒有必要的,因為失敗不經(jīng)常發(fā)生并且也不可能有很多不一致的日志條目帮寻。
通過這種機制乍狐,leader 在當權(quán)之后就不需要任何特殊的操作來使日志恢復(fù)到一致狀態(tài)。Leader 只需要進行正常的操作规婆,然后日志就能在回復(fù) AppendEntries 一致性檢查失敗的時候自動趨于一致澜躺。Leader 從來不會覆蓋或者刪除自己的日志條目(圖 3 的 Leader Append-Only 屬性)。
這樣的日志復(fù)制機制展示了第 2 節(jié)中描述的一致性特性:只要過半的服務(wù)器能正常運行抒蚜,Raft 就能夠接受掘鄙,復(fù)制并應(yīng)用新的日志條目;在正常情況下嗡髓,新的日志條目可以在一個 RPC 來回中被復(fù)制給集群中的過半機器操漠;并且單個運行慢的 follower 不會影響整體的性能。
5.4 安全性
前面的章節(jié)里描述了 Raft 算法是如何進行 leader 選舉和日志復(fù)制的饿这。然而浊伙,到目前為止描述的機制并不能充分地保證每一個狀態(tài)機會按照相同的順序執(zhí)行相同的指令。例如长捧,一個 follower 可能會進入不可用狀態(tài)嚣鄙,在此期間,leader 可能提交了若干的日志條目串结,然后這個 follower 可能會被選舉為 leader 并且用新的日志條目覆蓋這些日志條目哑子;結(jié)果,不同的狀態(tài)機可能會執(zhí)行不同的指令序列肌割。
這節(jié)通過對 leader 選舉增加一個限制來完善 Raft 算法卧蜓。這一限制保證了對于給定的任意任期號, leader 都包含了之前各個任期所有被提交的日志條目(圖 3 中的 Leader Completeness 性質(zhì))把敞。有了這一 leader 選舉的限制弥奸,我們也使得提交規(guī)則更加清晰。最后奋早,我們展示了對于 Leader Completeness 性質(zhì)的簡要證明并且說明該性質(zhì)是如何領(lǐng)導(dǎo)復(fù)制狀態(tài)機執(zhí)行正確的行為的盛霎。
5.4.1 選舉限制
在任何基于 leader 的一致性算法中,leader 最終都必須存儲所有已經(jīng)提交的日志條目耽装。在某些一致性算法中摩渺,例如 Viewstamped Replication[22],一開始并沒有包含所有已經(jīng)提交的日志條目的服務(wù)器也可能被選為 leader 剂邮。這種算法包含一些額外的機制來識別丟失的日志條目并將它們傳送給新的 leader 摇幻,要么是在選舉階段要么在之后很快進行。不幸的是挥萌,這種方法會導(dǎo)致相當大的額外的機制和復(fù)雜性绰姻。Raft 使用了一種更加簡單的方法,它可以保證新 leader 在當選時就包含了之前所有任期號中已經(jīng)提交的日志條目引瀑,不需要再傳送這些日志條目給新 leader 狂芋。這意味著日志條目的傳送是單向的,只從 leader 到 follower憨栽,并且 leader 從不會覆蓋本地日志中已經(jīng)存在的條目帜矾。
Raft 使用投票的方式來阻止 candidate 贏得選舉除非該 candidate 包含了所有已經(jīng)提交的日志條目翼虫。候選人為了贏得選舉必須與集群中的過半節(jié)點通信,這意味著至少其中一個服務(wù)器節(jié)點包含了所有已提交的日志條目屡萤。如果 candidate 的日志至少和過半的服務(wù)器節(jié)點一樣新(接下來會精確地定義“新”)珍剑,那么他一定包含了所有已經(jīng)提交的日志條目。RequestVote RPC 執(zhí)行了這樣的限制: RPC 中包含了 candidate 的日志信息死陆,如果投票者自己的日志比 candidate 的還新招拙,它會拒絕掉該投票請求。
Raft 通過比較兩份日志中最后一條日志條目的索引值和任期號來定義誰的日志比較新措译。如果兩份日志最后條目的任期號不同别凤,那么任期號大的日志更新。如果兩份日志最后條目的任期號相同领虹,那么日志較長的那個更新规哪。
5.4.2 提交之前任期內(nèi)的日志條目
如同 5.3 節(jié)描述的那樣,一旦當前任期內(nèi)的某個日志條目已經(jīng)存儲到過半的服務(wù)器節(jié)點上塌衰,leader 就知道該日志條目已經(jīng)被提交了由缆。如果某個 leader 在提交某個日志條目之前崩潰了,以后的 leader 會試圖完成該日志條目的復(fù)制猾蒂。然而均唉,如果是之前任期內(nèi)的某個日志條目已經(jīng)存儲到過半的服務(wù)器節(jié)點上,leader 也無法立即斷定該日志條目已經(jīng)被提交了肚菠。圖 8 展示了一種情況舔箭,一個已經(jīng)被存儲到過半節(jié)點上的老日志條目,仍然有可能會被未來的 leader 覆蓋掉蚊逢。
圖 8:如圖的時間序列展示了為什么 leader 無法判斷老的任期號內(nèi)的日志是否已經(jīng)被提交层扶。在 (a) 中,S1 是 leader 烙荷,部分地復(fù)制了索引位置 2 的日志條目镜会。在 (b) 中,S1 崩潰了终抽,然后 S5 在任期 3 中通過 S3戳表、S4 和自己的選票贏得選舉,然后從客戶端接收了一條不一樣的日志條目放在了索引 2 處昼伴。然后到 (c)匾旭,S5 又崩潰了;S1 重新啟動圃郊,選舉成功价涝,繼續(xù)復(fù)制日志。此時持舆,來自任期 2 的那條日志已經(jīng)被復(fù)制到了集群中的大多數(shù)機器上色瘩,但是還沒有被提交伪窖。如果 S1 在 (d) 中又崩潰了,S5 可以重新被選舉成功(通過來自 S2居兆,S3 和 S4 的選票)覆山,然后覆蓋了他們在索引 2 處的日志。但是史辙,在崩潰之前汹买,如果 S1 在自己的任期里復(fù)制了日志條目到大多數(shù)機器上佩伤,如 (e) 中聊倔,然后這個條目就會被提交(S5 就不可能選舉成功)。 在這種情況下生巡,之前的所有日志也被提交了耙蔑。
為了消除圖 8 中描述的問題,Raft 永遠不會通過計算副本數(shù)目的方式來提交之前任期內(nèi)的日志條目孤荣。只有 leader 當前任期內(nèi)的日志條目才通過計算副本數(shù)目的方式來提交甸陌;一旦當前任期的某個日志條目以這種方式被提交,那么由于日志匹配特性盐股,之前的所有日志條目也都會被間接地提交钱豁。在某些情況下,領(lǐng)導(dǎo)人可以安全地斷定一個老的日志條目已經(jīng)被提交(例如疯汁,如果該條目已經(jīng)存儲到所有服務(wù)器上)牲尺,但是 Raft 為了簡化問題使用了一種更加保守的方法。
Raft 會在提交規(guī)則上增加額外的復(fù)雜性是因為當 leader 復(fù)制之前任期內(nèi)的日志條目時幌蚊,這些日志條目都保留原來的任期號谤碳。在其他的一致性算法中,如果一個新的 leader 要重新復(fù)制之前的任期里的日志時溢豆,它必須使用當前新的任期號蜒简。Raft 的做法使得更加容易推導(dǎo)出(reason about)日志條目,因為他們自始至終都使用同一個任期號漩仙。另外搓茬,和其他的算法相比,Raft 中的新 leader 只需要發(fā)送更少的日志條目(其他算法中必須在它們被提交之前發(fā)送更多的冗余日志條目來給它們重新編號)队他。
5.4.3 安全性論證
在給出了完整的 Raft 算法之后垮兑,我們現(xiàn)在可以更加精確的討論領(lǐng)導(dǎo)人完整性特性(Leader Completeness Prop-erty)(這一討論基于 9.2 節(jié)的安全性證明)。我們假設(shè)領(lǐng)導(dǎo)人完全性特性是不滿足的漱挎,然后我們推出矛盾來系枪。假設(shè)任期 T 的 leader(leader T)在任期內(nèi)提交了一個日志條目,但是該日志條目沒有被存儲到未來某些任期的 leader 中磕谅。假設(shè) U 是大于 T 的沒有存儲該日志條目的最小任期號私爷。
圖 9:如果 S1 (任期 T 的 leader)在它的任期里提交了一個新的日志條目雾棺,然后 S5 在之后的任期 U 里被選舉為 leader ,那么肯定至少會有一個節(jié)點衬浑,如 S3捌浩,既接收了來自 S1 的日志條目,也給 S5 投票了工秩。
U 一定在剛成為 leader 的時候就沒有那條被提交的日志條目了(leader 從不會刪除或者覆蓋任何條目)尸饺。
Leader T 復(fù)制該日志條目給集群中的過半節(jié)點,同時助币,leader U 從集群中的過半節(jié)點贏得了選票浪听。因此,至少有一個節(jié)點(投票者)同時接受了來自 leader T 的日志條目和給 leader U 投票了眉菱,如圖 9迹栓。該投票者是產(chǎn)生矛盾的關(guān)鍵。
該投票者必須在給 leader U 投票之前先接受了從 leader T 發(fā)來的已經(jīng)被提交的日志條目俭缓;否則它就會拒絕來自 leader T 的 AppendEntries 請求(因為此時它的任期號會比 T 大)克伊。
該投票者在給 leader U 投票時依然保有這該日志條目,因為任何 U 华坦、T 之間的 leader 都包含該日志條目(根據(jù)上述的假設(shè))愿吹,leader 從不會刪除條目,并且 follower 只有跟 leader 沖突的時候才會刪除條目惜姐。
該投票者把自己選票投給 leader U 時犁跪,leader U 的日志必須至少和投票者的一樣新。這就導(dǎo)致了以下兩個矛盾之一载弄。
首先耘拇,如果該投票者和 leader U 的最后一個日志條目的任期號相同,那么 leader U 的日志至少和該投票者的一樣長宇攻,所以 leader U 的日志一定包含該投票者日志中的所有日志條目惫叛。這是一個矛盾,因為該投票者包含了該已被提交的日志條目逞刷,但是在上述的假設(shè)里嘉涌,leader U 是不包含的。
否則夸浅,leader U 的最后一個日志條目的任期號就必須比該投票者的大了仑最。此外,該任期號也比 T 大帆喇,因為該投票者的最后一個日志條目的任期號至少和 T 一樣大(他包含了來自任期 T 的已提交的日志)警医。創(chuàng)建了 leader U 最后一個日志條目的之前的 leader 一定已經(jīng)包含了該已被提交的日志條目(根據(jù)上述假設(shè),leader U 是第一個不包含該日志條目的 leader)。所以预皇,根據(jù)日志匹配特性侈玄,leader U 一定也包含該已被提交的日志條目,這里產(chǎn)生了矛盾吟温。
因此序仙,所有比 T 大的任期的 leader 一定都包含了任期 T 中提交的所有日志條目。
日志匹配特性保證了未來的 leader 也會包含被間接提交的日志條目鲁豪,例如圖 8 (d) 中的索引 2潘悼。
通過 Leader Completeness 特性,我們就能證明圖 3 中的狀態(tài)機安全特性爬橡,即如果某個服務(wù)器已經(jīng)將某個給定的索引處的日志條目應(yīng)用到自己的狀態(tài)機里了治唤,那么其他的服務(wù)器就不會在相同的索引處應(yīng)用一個不同的日志條目。在一個服務(wù)器應(yīng)用一個日志條目到自己的狀態(tài)機中時堤尾,它的日志和 leader 的日志從開始到該日志條目都相同肝劲,并且該日志條目必須被提交∏停現(xiàn)在考慮如下最小任期號:某服務(wù)器在該任期號中某個特定的索引處應(yīng)用了一個日志條目郭宝;日志完整性特性保證擁有更高任期號的 leader 會存儲相同的日志條目,所以之后任期里服務(wù)器應(yīng)用該索引處的日志條目也會是相同的值掷漱。因此粘室,狀態(tài)機安全特性是成立的。
最后卜范,Raft 要求服務(wù)器按照日志索引順序應(yīng)用日志條目衔统。再加上狀態(tài)機安全特性,這就意味著所有的服務(wù)器都會按照相同的順序應(yīng)用相同的日志條目到自己的狀態(tài)機中海雪。
5.5 Follower 和 candidate 崩潰
到目前為止锦爵,我們只關(guān)注了 leader 崩潰的情況。Follower 和 candidate 崩潰后的處理方式比 leader 崩潰要簡單的多奥裸,并且兩者的處理方式是相同的险掀。如果 follower 或者 candidate 崩潰了,那么后續(xù)發(fā)送給他們的 RequestVote 和 AppendEntries RPCs 都會失敗湾宙。Raft 通過無限的重試來處理這種失斦燎狻;如果崩潰的機器重啟了侠鳄,那么這些 RPC 就會成功地完成埠啃。如果一個服務(wù)器在完成了一個 RPC,但是還沒有響應(yīng)的時候崩潰了伟恶,那么在它重啟之后就會再次收到同樣的請求碴开。Raft 的 RPCs 都是冪等的,所以這樣的重試不會造成任何傷害博秫。例如潦牛,一個 follower 如果收到 AppendEntries 請求但是它的日志中已經(jīng)包含了這些日志條目鹃骂,它就會直接忽略這個新的請求中的這些日志條目。
5.6 定時(timing)和可用性
Raft 的要求之一就是安全性不能依賴定時:整個系統(tǒng)不能因為某些事件運行得比預(yù)期快一點或者慢一點就產(chǎn)生錯誤的結(jié)果罢绽。但是畏线,可用性(系統(tǒng)能夠及時響應(yīng)客戶端)不可避免的要依賴于定時。例如良价,當有服務(wù)器崩潰時寝殴,消息交換的時間就會比正常情況下長,candidate 將不會等待太長的時間來贏得選舉明垢;沒有一個穩(wěn)定的 leader 蚣常,Raft 將無法工作。
Leader 選舉是 Raft 中定時最為關(guān)鍵的方面痊银。 只要整個系統(tǒng)滿足下面的時間要求抵蚊,Raft 就可以選舉出并維持一個穩(wěn)定的 leader:
廣播時間(broadcastTime) << 選舉超時時間(electionTimeout) << 平均故障間隔時間(MTBF)
在這個不等式中,廣播時間指的是一個服務(wù)器并行地發(fā)送 RPCs 給集群中所有的其他服務(wù)器并接收到響應(yīng)的平均時間溯革;選舉超時時間就是在 5.2 節(jié)中介紹的選舉超時時間贞绳;平均故障間隔時間就是對于一臺服務(wù)器而言,兩次故障間隔時間的平均值致稀。廣播時間必須比選舉超時時間小一個量級冈闭,這樣 leader 才能夠可靠地發(fā)送心跳消息來阻止 follower 開始進入選舉狀態(tài);再加上隨機化選舉超時時間的方法抖单,這個不等式也使得選票瓜分的情況變得不可能萎攒。選舉超時時間需要比平均故障間隔時間小上幾個數(shù)量級,這樣整個系統(tǒng)才能穩(wěn)定地運行矛绘。當 leader 崩潰后耍休,整個系統(tǒng)會有大約選舉超時時間不可用色乾;我們希望該情況在整個時間里只占一小部分剧包。
廣播時間和平均故障間隔時間是由系統(tǒng)決定的苞俘,但是選舉超時時間是我們自己選擇的矛纹。Raft 的 RPCs 需要接收方將信息持久化地保存到穩(wěn)定存儲中去驹尼,所以廣播時間大約是 0.5 毫秒到 20 毫秒之間唱较,取決于存儲的技術(shù)天梧。因此戚丸,選舉超時時間可能需要在 10 毫秒到 500 毫秒之間劫灶。大多數(shù)的服務(wù)器的平均故障間隔時間都在幾個月甚至更長裸违,很容易滿足時間的要求。
6 集群成員變更
到目前為止本昏,我們都假設(shè)集群的配置(參與一致性算法的服務(wù)器集合)是固定不變的供汛。但是在實踐中,偶爾會改變集群的配置的,例如替換那些宕機的機器或者改變復(fù)制程度怔昨。盡管可以通過使整個集群下線雀久,更新所有配置,然后重啟整個集群的方式來實現(xiàn)趁舀,但是在更改期間集群會不可用赖捌。另外,如果存在手工操作步驟矮烹,那么就會有操作失誤的風險越庇。為了避免這樣的問題,我們決定將配置變更自動化并將其納入到 Raft 一致性算法中來奉狈。
為了使配置變更機制能夠安全卤唉,在轉(zhuǎn)換的過程中不能夠存在任何時間點使得同一個任期里可能選出兩個 leader 。不幸的是仁期,任何服務(wù)器直接從舊的配置轉(zhuǎn)換到新的配置的方案都是不安全的桑驱。一次性自動地轉(zhuǎn)換所有服務(wù)器是不可能的,所以在轉(zhuǎn)換期間整個集群可能劃分成兩個獨立的大多數(shù)(見圖 10)跛蛋。
圖 10:直接從一種配置轉(zhuǎn)到另一種配置是不安全的熬的,因為各個機器會在不同的時候進行轉(zhuǎn)換。在這個例子中问芬,集群從 3 臺機器變成了 5 臺悦析。不幸的是寿桨,存在這樣的一個時間點此衅,同一個任期里兩個不同的 leader 會被選出。一個獲得舊配置里過半機器的投票亭螟,一個獲得新配置里過半機器的投票挡鞍。
為了保證安全性,配置變更必須采用一種兩階段方法预烙。目前有很多種兩階段的實現(xiàn)墨微。例如,有些系統(tǒng)(比如扁掸,[22])在第一階段停掉舊的配置所以不能處理客戶端請求翘县;然后在第二階段在啟用新的配置。在 Raft 中谴分,集群先切換到一個過渡的配置锈麸,我們稱之為聯(lián)合一致(joint consensus);一旦聯(lián)合一致已經(jīng)被提交了牺蹄,那么系統(tǒng)就切換到新的配置上忘伞。聯(lián)合一致結(jié)合了老配置和新配置:
- 日志條目被復(fù)制給集群中新、老配置的所有服務(wù)器。
- 新氓奈、舊配置的服務(wù)器都可以成為 leader 翘魄。
- 達成一致(針對選舉和提交)需要分別在兩種配置上獲得過半的支持。
聯(lián)合一致允許獨立的服務(wù)器在不妥協(xié)安全性的前提下舀奶,在不同的時刻進行配置轉(zhuǎn)換過程暑竟。此外,聯(lián)合一致允許集群在配置變更期間依然響應(yīng)客戶端請求育勺。
集群配置在復(fù)制日志中以特殊的日志條目來存儲和通信光羞;圖 11 展示了配置變更過程。當一個 leader 接收到一個改變配置從 C-old 到 C-new 的請求怀大,它就為聯(lián)合一致將該配置(圖中的 C-old,new)存儲為一個日志條目纱兑,并以前面描述的方式復(fù)制該條目。一旦某個服務(wù)器將該新配置日志條目增加到自己的日志中化借,它就會用該配置來做出未來所有的決策(服務(wù)器總是使用它日志中最新的配置潜慎,無論該配置日志是否已經(jīng)被提交)。這就意味著 leader 會使用 C-old,new 的規(guī)則來決定 C-old,new 的日志條目是什么時候被提交的蓖康。如果 leader 崩潰了铐炫,新 leader 可能是在 C-old 配置也可能是在 C-old,new 配置下選出來的,這取決于贏得選舉的 candidate 是否已經(jīng)接收到了 C-old,new 配置蒜焊。在任何情況下倒信, C-new 在這一時期都不能做出單方面決定。
一旦 C-old,new 被提交泳梆,那么 C-old 和 C-new 都不能在沒有得到對方認可的情況下做出決定鳖悠,并且 leader 完整性特性保證了只有擁有 C-old,new 日志條目的服務(wù)器才能被選舉為 leader 。現(xiàn)在 leader 創(chuàng)建一個描述 C-new 配置的日志條目并復(fù)制到集群其他節(jié)點就是安全的了优妙。此外乘综,新的配置被服務(wù)器收到后就會立即生效。當新的配置在 C-new 的規(guī)則下被提交套硼,舊的配置就變得無關(guān)緊要卡辰,同時不使用新配置的服務(wù)器就可以被關(guān)閉了。如圖 11 所示邪意,任何時刻 C-old 和 C-new 都不能單方面做出決定九妈;這保證了安全性。
在關(guān)于配置變更還有三個問題需要解決雾鬼。第一個問題是萌朱,新的服務(wù)器開始時可能沒有存儲任何的日志條目。當這些服務(wù)器以這種狀態(tài)加入到集群中呆贿,它們需要一段時間來更新來趕上其他服務(wù)器嚷兔,這段它們無法提交新的日志條目森渐。為了避免因此而造成的系統(tǒng)短時間的不可用,Raft 在配置變更前引入了一個額外的階段冒晰,在該階段同衣,新的服務(wù)器以沒有投票權(quán)身份加入到集群中來(leader 也復(fù)制日志給它們,但是考慮過半的時候不用考慮它們)壶运。一旦該新的服務(wù)器追趕上了集群中的其他機器耐齐,配置變更就可以按上面描述的方式進行。
第二個問題是蒋情,集群的 leader 可能不是新配置中的一員埠况。在這種情況下,leader 一旦提交了 C-new 日志條目就會退位(回到 follower 狀態(tài))棵癣。這意味著有這樣的一段時間(leader 提交 C-new 期間)辕翰,leader 管理著一個不包括自己的集群;它復(fù)制著日志但不把自己算在過半里面狈谊。Leader 轉(zhuǎn)換發(fā)生在 C-new 被提交的時候喜命,因為這是新配置可以獨立運轉(zhuǎn)的最早時刻(將總是能夠在 C-new 配置下選出新的領(lǐng)導(dǎo)人)。在此之前河劝,可能只能從 C-old 中選出領(lǐng)導(dǎo)人壁榕。
第三個問題是,那些被移除的服務(wù)器(不在 C-new 中)可能會擾亂集群赎瞎。這些服務(wù)器將不會再接收到心跳牌里,所以當選舉超時,它們就會進行新的選舉過程务甥。它們會發(fā)送帶有新任期號的 RequestVote RPCs 牡辽,這樣會導(dǎo)致當前的 leader 回到 follower 狀態(tài)。新的 leader 最終會被選出來缓呛,但是被移除的服務(wù)器將會再次超時催享,然后這個過程會再次重復(fù),導(dǎo)致系統(tǒng)可用性很差哟绊。
為了防止這種問題,當服務(wù)器認為當前 leader 存在時痰憎,服務(wù)器會忽略RequestVote RPCs 票髓。特別的,當服務(wù)器在最小選舉超時時間內(nèi)收到一個 RequestVote RPC铣耘,它不會更新任期號或者投票洽沟。這不會影響正常的選舉,每個服務(wù)器在開始一次選舉之前蜗细,至少等待最小選舉超時時間裆操。相反怒详,這有利于避免被移除的服務(wù)器的擾亂:如果 leader 能夠發(fā)送心跳給集群,那它就不會被更大的任期號廢黜踪区。
7 日志壓縮
Raft 的日志在正常操作中隨著包含更多的客戶端請求不斷地增長昆烁,但是在實際的系統(tǒng)中,日志不能無限制地增長缎岗。隨著日志越來越長静尼,它會占用越來越多的空間,并且需要花更多的時間來回放传泊。如果沒有一定的機制來清除日志中積累的過期的信息鼠渺,最終就會帶來可用性問題。
快照技術(shù)是日志壓縮最簡單的方法眷细。在快照技術(shù)中拦盹,整個當前系統(tǒng)的狀態(tài)都以快照的形式持久化到穩(wěn)定的存儲中,該時間點之前的日志全部丟棄溪椎≌凭矗快照技術(shù)被使用在 Chubby 和 ZooKeeper 中,接下來的章節(jié)會介紹 Raft 中的快照技術(shù)池磁。
增量壓縮方法奔害,例如日志清理或者日志結(jié)構(gòu)合并樹(log-structured merge trees,LSM 樹)地熄,都是可行的华临。這些方法每次只對一小部分數(shù)據(jù)進行操作,這樣就分散了壓縮的負載壓力端考。首先雅潭,它們先選擇一個積累了大量已經(jīng)被刪除或者被覆蓋的對象的數(shù)據(jù)區(qū)域,然后重寫該區(qū)域還活著的對象却特,之后釋放該區(qū)域扶供。和快照技術(shù)相比,它們需要大量額外的機制和復(fù)雜性裂明,快照技術(shù)通過操作整個數(shù)據(jù)集來簡化該問題椿浓。狀態(tài)機可以用和快照技術(shù)相同的接口來實現(xiàn) LSM 樹,但是日志清除方法就需要修改 Raft 了闽晦。
一臺服務(wù)器用一個新快照替代了它日志中已經(jīng)提交了的條目(索引 1 到 5)扳碍,該快照只存儲了當前的狀態(tài)(變量 x 和 y 的值)∠沈龋快照的 last included index 和 last included term 被保存來定位日志中條目 6 之前的快照
圖 12 展示了 Raft 中快照的基本思想笋敞。每個服務(wù)器獨立地創(chuàng)建快照,快照只包括自己日志中已經(jīng)被提交的條目荠瘪。主要的工作是狀態(tài)機將自己的狀態(tài)寫入快照中夯巷。Raft 快照中也包含了少量的元數(shù)據(jù):the last included index 指的是最后一個被快照取代的日志條目的索引值(狀態(tài)機最后應(yīng)用的日志條目)赛惩,the last included term 是該條目的任期號。保留這些元數(shù)據(jù)是為了支持快照后第一個條目的 AppendEntries 一致性檢查趁餐,因為該條目需要之前的索引值和任期號喷兼。為了支持集群成員變更(第 6 節(jié)),快照中也包括日志中最新的配置作為 last included index 澎怒。一旦服務(wù)器完成寫快照褒搔,他就可以刪除 last included index 之前的所有日志條目,包括之前的快照喷面。
盡管通常服務(wù)器都是獨立地創(chuàng)建快照星瘾,但是 leader 必須偶爾發(fā)送快照給一些落后的跟隨者。這通常發(fā)生在 leader 已經(jīng)丟棄了需要發(fā)送給 follower 的下一條日志條目的時候惧辈。幸運的是這種情況在常規(guī)操作中是不可能的:一個與 leader 保持同步的 follower 通常都會有該日志條目琳状。然而一個例外的運行緩慢的 follower 或者新加入集群的服務(wù)器(第 6 節(jié))將不會有這個條目。這時讓該 follower 更新到最新的狀態(tài)的方式就是通過網(wǎng)絡(luò)把快照發(fā)送給它盒齿。
Leader 使用 InstallSnapshot RPC 來發(fā)送快照給太落后的 follower 念逞;見圖 13。當 follower 收到帶有這種 RPC 的快照時边翁,它必須決定如何處理已經(jīng)存在的日志條目翎承。通常該快照會包含接收者日志中沒有的信息。在這種情況下符匾,follower 丟棄它所有的日志叨咖;這些會被該快照所取代,并且可能一些沒有提交的條目會和該快照產(chǎn)生沖突啊胶。如果接收到的快照是自己日志的前面部分(由于網(wǎng)絡(luò)重傳或者錯誤)甸各,那么被快照包含的條目將會被全部刪除,但是快照之后的條目仍然有用并保留焰坪。
這種快照的方式違反了 Raft 的 strong leader 原則趣倾,因為 follower 可以在不知道 leader 狀態(tài)的情況下創(chuàng)建快照。但是我們認為這種違背是合乎情理的某饰。Leader 的存在儒恋,是為了防止在達成一致性的時候的沖突,但是在創(chuàng)建快照的時候露乏,一致性已經(jīng)達成碧浊,因此沒有決策會沖突。數(shù)據(jù)依然只能從 leader 流到 follower 瘟仿,只是 follower 可以重新組織它們的數(shù)據(jù)了。
我們考慮過一種可替代的基于 leader 的快照方案比勉,在該方案中劳较,只有l(wèi)eader 會創(chuàng)建快照驹止,然后 leader 會發(fā)送它的快照給所有的 follower 。但是這樣做有兩個缺點观蜗。第一臊恋,發(fā)送快照會浪費網(wǎng)絡(luò)帶寬并且延緩了快照過程。每個 follower 都已經(jīng)擁有了創(chuàng)建自己的快照所需要的信息墓捻,而且很顯然抖仅,follower 從本地的狀態(tài)中創(chuàng)建快照遠比通過網(wǎng)絡(luò)接收別人發(fā)來的要來得經(jīng)濟。第二砖第,leader 的實現(xiàn)會更加復(fù)雜撤卢。例如,leader 發(fā)送快照給 follower 的同時也要并行地將新的日志條目發(fā)送給它們梧兼,這樣才不會阻塞新的客戶端請求放吩。
還有兩個問題會影響快照的性能。首先羽杰,服務(wù)器必須決定什么時候創(chuàng)建快照渡紫。如果快照創(chuàng)建過于頻繁,那么就會浪費大量的磁盤帶寬和其他資源考赛;如果創(chuàng)建快照頻率太低惕澎,就要承擔耗盡存儲容量的風險,同時也增加了重啟時日志回放的時間颜骤。一個簡單的策略就是當日志大小達到一個固定大小的時候就創(chuàng)建一次快照唧喉。如果這個閾值設(shè)置得顯著大于期望的快照的大小,那么快照的磁盤帶寬負載就會很小复哆。
第二個性能問題就是寫入快照需要花費一段時間欣喧,并且我們不希望它影響到正常的操作。解決方案是通過寫時復(fù)制的技術(shù)梯找,這樣新的更新就可以在不影響正在寫的快照的情況下被接收唆阿。例如,具有泛函數(shù)據(jù)結(jié)構(gòu)的狀態(tài)機天然支持這樣的功能锈锤。另外驯鳖,操作系統(tǒng)對寫時復(fù)制技術(shù)的支持(如 Linux 上的 fork)可以被用來創(chuàng)建整個狀態(tài)機的內(nèi)存快照(我們的實現(xiàn)用的就是這種方法)。
8 客戶端交互
本節(jié)介紹客戶端如何和 Raft 進行交互久免,包括客戶端如何找到 leader 和 Raft 是如何支持線性化語義的浅辙。這些問題對于所有基于一致性的系統(tǒng)都存在,并且 Raft 的解決方案和其他的也差不多阎姥。
Raft 的客戶端發(fā)送所有的請求給 leader 记舆。當客戶端第一次啟動的時候,它會隨機挑選一個服務(wù)器進行通信呼巴。如果客戶端第一次挑選的服務(wù)器不是 leader 泽腮,那么該服務(wù)器會拒絕客戶端的請求并且提供關(guān)于它最近接收到的領(lǐng)導(dǎo)人的信息(AppendEntries 請求包含了 leader 的網(wǎng)絡(luò)地址)御蒲。如果 leader 已經(jīng)崩潰了,客戶端請求就會超時诊赊;客戶端之后會再次隨機挑選服務(wù)器進行重試厚满。
我們 Raft 的目標是要實現(xiàn)線性化語義(每一次操作立即執(zhí)行,只執(zhí)行一次碧磅,在它的調(diào)用和回復(fù)之間)碘箍。但是,如上述鲸郊,Raft 可能執(zhí)行同一條命令多次:例如丰榴,如果 leader 在提交了該日志條目之后,響應(yīng)客戶端之前崩潰了严望,那么客戶端會和新的 leader 重試這條指令多艇,導(dǎo)致這條命令被再次執(zhí)行。解決方案就是客戶端對于每一條指令都賦予一個唯一的序列號像吻。然后峻黍,狀態(tài)機跟蹤每個客戶端已經(jīng)處理的最新的序列號以及相關(guān)聯(lián)的回復(fù)。如果接收到一條指令拨匆,該指令的序列號已經(jīng)被執(zhí)行過了姆涩,就立即返回結(jié)果,而不重新執(zhí)行該請求惭每。
只讀的操作可以直接處理而不需要記錄日志骨饿。但是,如果不采取任何其他措施台腥,這么做可能會有返回過時數(shù)據(jù)(stale data)的風險宏赘,因為 leader 響應(yīng)客戶端請求時可能已經(jīng)被新的 leader 替代了,但是它還不知道自己已經(jīng)不是最新的 leader 了黎侈。線性化的讀操作肯定不會返回過時數(shù)據(jù)察署,Raft 需要使用兩個額外的預(yù)防措施來在不使用日志的情況下保證這一點。首先峻汉,leader 必須有關(guān)于哪些日志條目被提交了的最新信息贴汪。Leader 完整性特性保證了 leader 一定擁有所有已經(jīng)被提交的日志條目,但是在它任期開始的時候休吠,它可能不知道哪些是已經(jīng)被提交的扳埂。為了知道這些信息,它需要在它的任期里提交一個日志條目瘤礁。Raft 通過讓 leader 在任期開始的時候提交一個空的沒有任何操作的日志條目到日志中來處理該問題阳懂。第二,leader 在處理只讀請求之前必須檢查自己是否已經(jīng)被替代了(如果一個更新的 leader 被選舉出來了,它的信息就是過時的了)希太。Raft 通過讓 leader 在響應(yīng)只讀請求之前克饶,先和集群中的過半節(jié)點交換一次心跳信息來處理該問題酝蜒。另一種可選的方案誊辉,leader 可以依賴心跳機制來實現(xiàn)一種租約的形式,但是這種方法依賴 timing 來保證安全性(假設(shè)時間誤差是有界的)亡脑。
可進入我的博客查看原文堕澄。