《In Search of an Understandable Consensus Algorithm》翻譯


Abstract

Raft是一種用於管理replicated log的consensus algorithm。它能和Paxos產生同樣的結果,有着和Paxos同樣的性能,但是結構卻不同於Paxos;它讓Raft比Paxos更易於理解,並且也為用它構建實際的系統提供了更好的基礎。為了增強可理解性,Raft將例如leader election, log replication以及safety等共識的關鍵元素進行了分離,並且提供了更強的一致性用於減少必須考慮的狀態。從用戶調查的結果來看,Raft比Paxos更易於學生學習。Raft包含了一種新的機制用於改變cluster membership,並通過overlapping majority來保證安全性。

1 Introduction

Consensus algorithm允許一群機器像一個整體一樣工作,即使其中的一些成員發生故障也不會出現問題。正因為這一點,它在構建可靠的大規模軟件系統的過程中起着關鍵的作用。Paxos一直主導着過去十年對consensus algorithm的討論:許多共識的實現都基於Paxos或者受到它的影響,並且Paxos已經成為了用來教授學生關於共識的主要工具。

不幸的是,Paxos太難以理解了,盡管已經做了很多嘗試想使它變得更加平易近人。另外,為了支持實際的系統,它的結構需要做非常復雜的改變。因此,系統架構師和學生都對Paxos感到很痛苦。

在我們自己和Paxos經歷了一番痛苦掙扎之后,我們決定發明一種新的consensus algorithm來為系統的構建和教學提供更好的基礎。我們的主要目標有點特別,是為了讓它更加易於理解:我們是否能為實際的系統定義一個consensus algorithm,並且描述它的方式能比Paxos更加易於理解?此外,我們想要該算法有利於the development of intuitions,而這對系統構建者是必不可少的。算法能工作很重要,但是能清楚地顯示它為什么能工作同樣非常重要。

這項工作的結果就是一個叫做Raft的consensus algorithm。在設計Raft的時候,我們使用了一些額外的技術用於提供可理解性,包括decomposition(Raft分離了leader election, log replication和safety)以及狀態空間的減少(和Paxos相比,Raft降低了不確定性以及sever之間能達到一致的方法)。一個由來自兩個大學的43位學生組成的用戶調查顯示Raft要比Paxos易於理解的多;在同時學習了兩種方法之后,其中的33名學生回答Raft的問題要比回答Paxos更好。

Raft在很多方面和現存的consensus algorithm類似,但是它有以下這些獨特的特性:

  • Strong leader:Raft比其他consensus algorithm使用了更強形式的leadership。比如,log entry只能從leader流向其他server。這簡化了對於replicated log的管理並且使Raft更加易於理解。
  • Leader election:Raft使用隨機的時鍾來選舉leader。這只是在原來所有的consensus algorithm都需要的heartbeats的基礎上增加了小小的一點東西,但是卻簡單快速地解決了沖突。
  • Membership changes:Raft通過一種新的joint consensus的方法來實現server集合的改變,其中兩個不同配置下的majority在過度階段會發生重合。這能讓集群在配置改變時也能繼續正常運行。

我們相信不論對於教學還是用於真實系統實現的基礎,Raft都要優於Paxos和其他的共識算法。它比其他算法更簡答也更加易於理解;它能完全滿足實際系統的需求;它有很多開源的實現並且被很多公司使用;它的安全性已經被完全證實了;並且它的效率也完全可以和其他算法相媲美。

論文的剩余部分介紹了replicated state machine問題(Section 2),討論了Paxos的優缺點(Section 3),描述了可理解性的一般方法(Section 4),描述了Raft consensus algorithm(Section 5-7),評估Raft(Section 8),並且討論了相關工作(Section 9)。Raft中的一小部分元素在這里因為篇幅的原因省略了,但是它們可以在一份擴展的技術報告里找到。其余的內容描述了client怎么和系統進行交互以及Raft log的空間怎么被回收。

2 Replicated state machines

consensus algorithm通常在replicated state machine的上下文中出現。在這種方法中,一群server上的state machine對同一個狀態的拷貝進行計算,即使其中一些server宕機了也能正常運行。Replicated state machine通常用於解決分布式系統中的容錯問題。例如,擁有單一的cluster leader的大規模系統,例如GFS,HDFS和RAMCloud通常會用一個單獨的replicated state machine來管理leader的選舉以及存儲一些在拯救leader崩潰的配置信息。Replicated state machine典型的例子包括Chubby和ZooKeeper。

Replicated state machine通常用一個replicated log來實現,如Figure 1所示。每一個server存儲了一個包含一系列命令的log,而它的state machine順序執行這些命令。每一個log以同樣的順序包含了同樣的指令,所以每一個state machine都會處理相同的命令。因為每一個state machine都是確定性的,因此計算將得到同樣的狀態和輸出結果。

Consensus algorithm的作用是保證replicated log的一致性。server中的consensus module用於從client處接受命令並且將它們加入log。它會和其他serer的consensus module進行通信,從而確保每一個log都以相同的順序包含相同的請求,每一個server的state machine都按順序處理它們的log,並且將輸出返回給client。最終,這些server呈現出的是一個單一的,高度可靠的state machine。

用於實際系統的consensus algorithm通常具有以下特性:

  • 在所有的non-Byzantine條件下要確保正確性(從不返回一個錯誤的結果),包括網絡延遲,分區以及網絡包的丟失,重復和亂序。
  • 只要majority of servers是可操作的並且能相互之間進行通信,以及和client進行通信,那么系統必須是可用的。因此,一個由五台server組成的集群必須能忍受兩台server的故障。Server發生故障時,可以認為是暫停了;它們可能稍后會從恢復到存儲在stable storage中的狀態並且重新加入集群。
  • 它們不依賴於時間來確保log的一致性:fault clocks和extreme message delays在最差的情況下也只能導致系統不可用的問題。
  • 通常情況下,當集群中的大多數server已經對單一的RPC做出相應時,可以認為一個命令完成了。少數慢的server不應該影響整個系統的性能。

3 What's wrong with Paxos?

在過去的十年中,Leslie Lamport的Paxos協議幾乎成了consensus的同義詞:它是在課程中最常被教授的協議,並且很多consensus的實現都以它作為起點。Paxos首先定義了一個在單一的decision上能達到agreement的協議,例如一個單一的replicated log entry。我們將這樣的一個子集稱之為single-decree Paxos。之后Paxos可以將該協議的多個實例組合在一起去形成一系列的decision作為log(multi-Paxos)。Paxos保證了safety和liveness,並且它支持cluster membership的改變。它的正確性已經被證明了並且在一般的情況下也被證明是高效的。

不幸的是,Paxos有兩個重要的缺陷。第一個缺陷是Paxos太難以理解了。它的完整描述是出了名的晦澀;很少有人能成功理解它,即使能也是花了很大的功夫。因此,已經做了很多嘗試,試圖用一個更簡單的版本解釋Paxos。雖然它們都着力於single-decree版本,但是仍然非常具有挑戰性。在一項針對NSDI 2012與會者的調查中,我們發現很少有人對Paxos感到舒服,即使是那些經驗豐富的研究人員。我們自己也對Paxos感到非常痛苦,我們在不能理解完整的協議,直到我們閱讀了幾個簡化版的描述以及設計了我們自己的替代協議,而這整個過程持續了將近一年。

我們認為Paxos的晦澀來源於它將single-decree subset作為自己的基礎。Single-decree Paxos被認為是微妙的:它被划分為兩個不能用直覺來顯示的階段並且不能單獨理解。因此,這就導致了很難對single-decree protocol是如何工作的建立起直覺。而multi-Paxos的composition rule則更加添加了復雜性。我們堅信對於在multiple decision的情況下到達consensus這個問題肯定能以其他更直接,更明顯的方式被分解。

Paxos的第二個問題是它並沒有為實際的實現提供一個很好的基礎。一大原因是對於multi-Paxos沒有一個廣受認可的算法。Lamport的描述主要針對的是single-decree Paxos;它為multi-Paxos提供了一個大概的框架,但是很多細節並沒有提及。對於充實以及優化Paxos已經做了很多努力,但是它們各自之間,以及和Lamport的概述都不相同。像Chubby這樣的系統已經實現了類Paxos算法,但是它的很多細節並沒有公開。

另外,Paxos的架構也不利於構建實際系統;這是它按single-decree分解的另一個后果。例如,獨立地選取一系列的log entry並且將它們融合成一個順序的log並沒有太多好處,僅僅只是增加了復雜度。相反,構建一個圍繞按順序擴展log的系統是更簡單和高效的。Paxos的另一個問題是它將對稱的peer-to-peer作為核心(雖然在最后為了優化性能建議了一種弱形式的leadership)。這在只需要做一個decision的簡單場景中是可行的,但是很少有實際的系統會使用這種方法。如果要有一系列的decision要決定,那么先選擇一個leader,然后再讓leader去協調decision。

因此,實際系統很少和Paxos類似。各種實現都以Paxos開始,然后發現實現起來很困難,於是最后開發出了一個完全不同的架構。這是極其費時並且容易出錯的,而Paxos的難以理解則更加加劇了這個問題。Paxos的正確性理論很好證明,但是實際的實現和Paxos太過不同,因此這些證明就沒什么價值了。接下來這段來自Chubby的評論是非常典型的:

Paxos算法的描述和現實世界的系統的需求之間有巨大的矛盾....而最終的系統都將建立在一個未經證明的協議之上

因為這些問題的存在,我們得出這樣的結論,Paxos並沒有為實際系統的構建或者是教學提供一個很好的基礎。基於在大規模軟件系統中consensus的重要性,我們決定嘗試能否設計出另外一種比Paxos有着更好性質的consensus algorithm。而Raft就是我們實驗得到的結果。

4 Designing for understandability

我們在設計Raft的時候有以下幾個目標:它必須為系統的構建提供完整並且實際有效的基礎,而這能極大地減少開發者的設計工作;它必須在所有條件下都是安全的,在典型的操作條件下是可用的,在通常的操作中是高效的。但我們最重要的目標,也是最大的挑戰,就是可理解性。我們必須讓廣大的讀者能比較容易地理解這個算法。並且要能夠建立對這個算法的直覺,從而讓系統構建者能做一些實際實現中必要的擴展。在設計Raft的很多節點上,我們要在很多可選方法之間做出選擇。在這些情況下,我們基於可理解性對這些方法進行評估:對於每一個可選方案的描述是否困難(比如,它的狀態空間的復雜度是多少,以及它是否有subtle implication?)以及讀者是否能輕松地完全理解這種方法。

后來我們意識到這種分析方法具有很強的主觀性;於是我們使用了兩種方法讓分析變得更具通用性。第一種是關於問題分解的眾所周知的方法:是否有可能,我們可以將問題分解為可以被相對獨立地解釋,理解並且被解決的幾部分。例如,在Raft中,我們分解了leader election, log replication, safety和membership changes這幾部分。

我們的第二種方法是通過減少需要考慮的狀態數,盡量讓系統更一致以及盡可能地減少非確定性,來簡化state space。另外,log不允許存在hole,Raft限制了log之間存在不一致的可能。雖然在大多數情況下,我們都要減少不確定性,但是在某些情況下,不確定性確實提高了可理解性。特別地,隨機化的方法會引入不確定性,但是通過以相同的方式處理所有可能的選擇(choose any; it doesn't matter),確實減少了state space。我們就使用了隨機化來減少了Raft的leader election algorithm。

5 The Raft consensus algorithm

Raft是一種用於管理Section 2中所描述的形式的replicated log的算法。Figure 2以精簡的形式概述了這一算法,而Figure 3列出了該算法的關鍵特性,而這些特性將在本節的剩余部分分別進行討論。

Raft首先通過選舉一個distinguished leader來實現consensus,然后將管理replicated log的責任全部給予這個leader。leader從client處接收log entry,再將它備份到其他server中,接着告訴server什么時候能安全地將log entry加入state machine中。leader的存在簡化了replicated log的管理。比如,leader可以在不詢問其他leader的情況下決定將新的entry存放在log的什么位置並且數據簡單地從leader流向其他server。leader可能會發生故障或者和其他server斷開,在這種情況下,會有新的leader被選舉出來。

通過選舉leader,Raft將consesus problem分解成三個相對獨立的子問題,它們會在接下來的子章節中討論:

  • Leader election:在一個已有的leader故障之后,必須要有一個新的leader被選舉出來(Section 5.2)
  • Log replication:leader必須從client處接收log entry並且將它們在集群中進行備份,強制使其他log與它自己一致(Section 5.3)
  • Safety:Raft中最關鍵的safety property就是Figure 3所示的State Machine Safety Property:如果有任何的server已經將一個特定的log entry加入它的state machine中,那么其他的server對於同一個log index的log entry必須相同

在展示了consensus algorithm之后,本節將討論可用性以及時間在系統中扮演的角色

5.1 Raft basics

一個Raft集群包含多個server;一般都是五個,因此系統能忍受兩台機器的故障。在任意給定時刻,每個server都處於以下三個狀態中的一個:leader,follower,或者candidate。在正常情況下,只有一個leader,其他都是follower。follower是很被動的,它們不會自己發送請求,只是簡單地對來自leader和candidate的請求進行回復。leader對所有來自client的請求進行處理(如果一個client和follower進行交互,follower會將它重定向給leader),第三種狀態,candidate,是用來選舉Section 5.2中描述的新的leader。Figure 4顯示了各種狀態以及它們之間的轉換;關於轉換將在下文進行討論。

 

Raft將時間划分成任意長度的term,如Figure 5所示。Term以連續的整數進行編號。每個term以一個election開始,這個階段會有一個或多個candidate競選leader,如Section 5.2所示。 如果一個candidate競選成功,那么它將在term剩下的時間里作為leader。在有些情況下,一個election可能導致一個split vote。在這種情況下,term將以一種沒有leader的狀態結束;而一個新的term(伴隨着一個新的選舉)將馬上開始。Raft將保證在給定的一個term中,總是最多只有一個leader。

不同的server可能在不同的時間觀察到term的轉換,而在有些情況下,一個server可能會觀察不到election甚至是一個完整的term。term在Raft中扮演的是一個logical clock的角色,它能夠讓server去檢測那些需要淘汰的信息,例如過時的leader。每個server都存儲了一個current term number,它會隨着時間單調遞增。current term會隨着server之間的交互而改變;如果一個server的current term比其他的小,那么它就會將自己的current term更新到更大的值。如果一個candidate或者leader發現它的term已經過時了,那么它就會立即恢復到follower state。如果一個server接收到一個來自過時的term的請求,那么拒絕它。

Raft servers之間通過RPC進行通信,而consensus algorithm需要兩種類型的RPC。RequestVote RPC由candidate在election期間發起(Section 5.2),AppendEntries RPC由leader發起,用於備份log entry和提供heartbeat(Section 5.3)。如果一個server沒有收到回復,那么它會及時重發RPC,並且它們會並行發送RPC用於提高性能。

5.2 Leader election

Raft使用一種heartbeat mechanism 來觸發leader election。當server啟動的時候,默認作為follower。server如果能持續地從leader或者candidate處獲取合法的RPC,那么它將始終保持follower狀態。為了保持自己的權威性,leader會階段性地發送heartbeats(不帶有log entry的AppendEntry RPC)給所有的follower。如果一個server在一個叫做election timeout的時間段中沒有收到交互信息,那么它就會認為不存在一個viable leader,並且開始一輪新的election選出新的leader。

為了開始一個election,follower會增加它的current term並且轉換為candidate state。接着它會投票給自己,並且並行地給集群中的其他server發送RequestVote RPC。candidate將持續保持這種狀態,直到以下三個條件中的一個被觸發:(a) 它贏得了選舉,(b) 另一個server宣布它自己是leader,或者(c) 過了一段時間之后也沒有winner。這些情況將在接下來分別進行討論。

如果一個candidate收到了來自集群中的majority個server對於同一個term的投票,那么它將贏得election。每一個server在給定的term中都最多只會投票給一個candidate,並且基於first-come-first-serverd原則(Section 5.4中將對於投票添加一個額外的約束)。majority原則確保了在一個給定的term中最多只有一個candidate可以贏得election(Figure 3中的Election Safety Property)。一旦一個candidate贏得了election,它將成為leader。之后它將向所有其他的server發送hearbeat用以確保自己的權威並且防止新一輪的election。

當在等待投票時,一個candidate可能會收到來自另一個server的AppendEntry RPC聲稱自己是leader。如果該leader的term(包含在該RPC中)至少和candidate的current term一樣大,那么candiate認為該leader是合法的並且返回到follower的狀態。如果RPC中的term比candidate的current term要小,那么candidate會拒絕該RPC並且依然保持為candidate狀態。

第三種可能的情況是一個candidate在election中既沒有贏也沒有輸:如果在同一時刻有很多follower成為了candidate,投票將會分裂因此沒有candidate會獲得majority。當這種情況發生時,每個candidate都會timeout並且通過增加term和發送新一輪的RequestVote RPC來開始新的election。然而,如果沒有額外的措施,splite vote可能會一直重復下去。

 Raft使用隨機的election timeout來確保split vote很少會發生並且保證即使發生了也很快會被解決。為了在一開始就避免split ovte,election timeout會在一個固定區間內隨機選擇(e.g., 150-300ms)。這就將server鋪散開來從而保證在大多數情況下只有一個server會timeout;它將贏得election並且在其他的server timeout之前發送heartbeat。同樣的機制也被用在處理split vote上。每個candidate在election開始的時候重新隨機確定一個election timeout並在下一次election開始前靜靜等待timeout的到來;這就減少了在下一個新的election的時候發生split vote的可能。Section 8.3展示了使用這種方法快速選擇一個leader的過程。

Election是一個展示可理解性作為指導我們做出設計選擇的一個很好的例子。一開始我們計划使用一個rank system:每個candidate都會賦予一個唯一的rank,它會被用來在相互競爭的candidate之中做出選擇。當一個candidate發現另一個candidate有更高的rank,那么它就會退回到follower的狀態,從而讓有更高rank的candidate能更容易贏得下一輪election。但我們發現這種方法會在可用性方面產生一些微妙的問題(如果有着更高rank的server發生了故障,一個低rank的server可能需要timeout並且重新成為candidate,但是這個過程發生地太快,則會引發新的leader選擇過程)。我們對這一算法做了多次調整,但是每次調整之后都有新的corner cases產生。最后我們得出結論,隨機重試的方法是更明顯也更易理解的方法。

5.3 Log replication

一旦一個leader被選擇出來以后,它開始處理client request。每個client request都包含了需要由replicated state machine執行的command。leader用command擴展log,作為新的entry,接着並行地給其他server發生AppendEntry RPC來備份entry。當該entry被安全地備份之后(如下所述),leader會讓它的state machine執行該entry,並且將執行結果返回給client。如果follower崩潰了或者運行很慢,抑或是丟包了,leader會不停地重發AppendEntry RPC直到所有的follower最終都保存了所有的log entry。

Log以Figure 6中的形式被組織。當一個entry被leader接收的時候,每個log entry都會包含一個state machine command和term number。log entry中的term number是用來檢測log之間的不一致性並且確保Figure 3中的一些特性的。同時,每個log entry都有一個整數的index用於標示它在log中的位置。

leader決定何時讓state machine執行log entry是安全的,而這樣的entry叫做committed。Raft保證所有committed entry都是durable並且最終會被所有可用的state machine執行。一旦創建它的leader已經將它備份到majority個server中,log entry就會被committed(e.g., Figure 6中的entry 7)。同時它也會commit leader的log中所有前綴的entry,包括那些由之前的leader創建的entry。Section 5.4中會討論在leader改變之后應用這條規則會產生的一些微妙的問題,同時它也會展示這樣的關於的commitment的定義是安全的。leader會追蹤它已知被committed最高的index,並且會在之后的AppendEntry RPC(包括heartbeat)包含這個index從而讓其他server能發現它。一旦follower知道了一個log entry被committed,它最終會讓本地的state machine運行這個entry(以log的順序)。

我們設計了Raft log mechanism來保持不同server的log之間的高度一致性。這不僅簡化了系統行為讓它們變得可預測,並且這也是確保安全性的重要組件。Raft維護了以下特性,它們合起來構成了Figure 3所示的Log Matching Property:

  • 如果不同的log中的兩個entry有相同的index和term,那么它們存儲相同的command
  • 如果不同的log中的兩個entry有相同的index和term,那么它們前綴的entry都是相同的

第一個特性確保了leader對於給定的log index和term,它最多產生一個entry,並且log entry永遠不會改變它在log中的位置。第二個特性則由AppendEntry一個簡單的一致性檢查來保證。在發送一個AppendEntry RPC的時候,leader會在其中包含新的entry之前的那個entry的index和term。如果follower沒有在log中有同樣index和term的entry,那么它就會拒絕新的entry。一致性檢查扮演了induction step:log的initial empty state是滿足Log Matching Property的,而一致性檢查則在log擴展的時候保證了Log Matching Property。因此,當AppendEntry成功返回的時候,leader就知道該follower的log和它自己是一致的。

在進行正常操作的時候,leader和follower的操作始終是一致的,因此AppendEntry的一致性檢查用於不會失敗。但是,leader崩潰會導致log處於不一致的狀態(老的leader可能還沒有將它log中的所有entry完全備份)。而這些不一致性可能隨着一系列的leader和follower的崩潰而疊加。Figure 7說明了follower的log可能和新的leader不一致的情況。follower中可能會遺漏一些leader中的entry,同時它里面也可能有一些leader中沒有的額外的entry,或者兩者都有。log中遺失的或者額外的entry中可能跨越多個term。

為了讓follower的log和自己保持一致,leader必須找到兩個log一致的最遠的entry,並且刪除follower該entry之后所有的entry。所有這些操作都用於回應AppendEntry RPC的一致性檢查。leader為每一個follower維護了一個nextIndex,它代表了leader將會發送給follower的下一個log entry。當一個leader剛剛開始執行的時候,它會將所有的nextIndex都初始化為它自己log的最后一個entry的index加一(Figure 7中的11)。如果follower和leader的log不一致,AppendEntry RPC的一致性檢查會在下一個AppendEntry RPC的時候失敗。在收到一個rejection之后,leader會減小它的nextIndex並且重發AppendEntry RPC。最終nextIndex會達到leader和follower的log匹配的狀態。此時,AppendEntry會成功返回,移除了follower的log中沖突的entry並且會根據leader的log進行擴展(如果有的話)。一旦AppendEntry成功,follower已經和leader的log一致了,而且將在term的接下來部分保持。該協議可以通過減少rejected AppendEntry RPC的數目來優化。

在這種機制下,leader不用在它剛剛成為leader的時候執行任何額外的動作用於恢復log的一致性。它只是正常地開始執行,並且log會隨着AppendEntry一致性檢查的失敗而不斷收斂。leader從來不會覆寫或者刪除它自己log的entry(Figure 3中的Leader Append-Only Property)。

該log replication mechanism展示了Section 2中想要達到的consensus property:Raft可以接收,備份,並且執行新的log entry只要有majority個server活着;在正常情況下,一個新的entry會在單一的一輪RPC中被備份到cluster的一個majority中;因此一個運行較慢的follower並不會影響性能。

5.4 Safety

在前面的章節中描述了Raft如何選舉leader以及備份log entry。但是之前描述的機制並不足以保證每個state machine以同樣的順序執行同樣的command。比如,follower可能在leader commit多個log entry的時候一直處於不可用的狀態,而之后它可能被選作leader並且用新的entry覆寫這些entry;因此,不同的state machine可能會執行不同的command sequences。

本節中我們通過給哪些server能被選舉為leader增加約束來完善Raft算法。該約束確保任何給定的term的leader會包含之前term所有commit的entry(Figure 3中的Leader Completeness Property)。通過增加election restriction,我們更加細化了commitment的規則。最后,我們展示了Leader Completeness Property的證明草圖並且展示了它如何能讓replicated state machine正確操作。

5.4.2 Election restriction

任何leader-based consensus algorithm,leader最終都必須存儲所有的committed log entry。在一些consensus algorithm中,例如Viewstamped Replication,即使一開始沒有包含全部的committed entry也能被選為leader。這些算法都會包含額外的機制用於識別遺失的entry並且將它們傳輸給新的leader,要么在election期間,要么在這不久之后。不幸的是,這需要額外的機制以及復雜度。Raft使用了一種更簡單的方法,它保證在選舉期間每個新的leader都包含了之前term都包含的所有entry,從而不需要將這些entry傳輸到leader。這意味着log entry的流動是單方向的,只從leader流向follower,而leader從不會覆寫log中已有的entry。

Raft使用voting process來防止那些log不包含全部committed entry的candidate贏得election。candidate為了贏得選舉必須和cluster的majority進行交互,這意味着每個committed entry必須都在其中的一個majority存在。如果一個candidate的log至少和任何majority中的log保持up-to-date("up-to-date"將在下文精確定義),那么它就包含了所有committed entry。RequestVote RPC實現了這一約束:RPC中包含了candidate的log信息,voter會拒絕投票,如果它自己的log比該candidate的log更up-to-date。

Raft通過比較log中last entry的index和term來確定兩個log哪個更up-to-date。如果兩個log的last entry有不同的term,那么擁有較大term的那個log更up-to-date。如果兩個log以相同的term結束,那么哪個log更長就更up-to-date。

5.4.2 Committing entries from previous terms

如Section 5.3中所述,leader知道current term中的entry已經被提交了,一旦該term已經被majority個server存儲了。如果一個leader在committing an entry之前就崩潰了,那么未來的leader就會試着完成該entry的備份。但是leader很難馬上確認之前term的entry已經commited一旦它被存儲於majority個server中。Figure 8展示了這樣一種情況,一個old log entry已經被存儲在majority個server中,但是它仍然可以被future leader覆寫。

為了防止Figure 8中這樣問題的發生,Raft從不會通過計算備份的數目來提交之前term的log entry。只有leader的當前term的log entry才通過計算備份數committed;一旦當前term的entry以這種方式被committed了,那么之前的所有entry都將因為Log Matching Property而被間接committed。其實在很多情況下,leader可以非常安全地確定一個old entry已經被committed了(比如,如果該entry已經被存儲在所有server中了),但是Raft為了簡單起見使用了一種更保守的方法。

因為leader從之前的term備份entry時,log要保留之前的term number,這會讓Raft在commitment rule中引入額外的復雜度。在其他consensus algorithm中,如果一個新的leader從之前的term備份entry時,它必須使用它自己的新的term number。因為log entry的term number不隨時間和log的不同而改變,這就能讓Raft更加容易地進行推導。另外,Raft中的新的leader與其他算法相比只需要從之前的term傳輸更少的log entry(其他的算法必須傳輸備份的log entry進行重新編號在它們被committed之前)。

5.4.3 Safety argument

給出了完整的Raft算法之后,我們可以進一步論證Leader Completeness Property成立(該論據基於safety proof;參見Section 8.2)。我們假設Leader Completeness Property是不成立的,接着推出矛盾。假設term T的leader(leaderT) commit了一個該term的log entry,但是該log entry並沒有被未來的term的leader存儲。考慮滿足大於T的最小的term U,它的leader(leaderU)沒有存儲該entry。

1、該committed entry在leaderU選舉期間一定不存在於它的log中(leader從不刪除或者覆寫entry)。

2、leaderT將entry備份到了集群的majority中,並且leaderU獲取了來自集群的majority的投票,如Figure 9所示。而voter是達到矛盾的關鍵。

3、voter一定在投票給leaderU之前已經接受了來自leaderT的committed entry;否則它將拒絕來自leaderT的AppendEntry request(因為它的current term高於T)。

4、當voter投票給leaderU的時候它依然保有該entry,因為每個intervening leader都包含該entry(根據假設),leader從不刪除entry,而follower只刪除它們和leader矛盾的entry。

5、voter投票給leaderU,因此leaderU的log一定和voter的log一樣up-to-date。這就導致了兩個矛盾中的其中一個矛盾。

6、首先,如果voter和leaderU共享同一個last log term,那么leaderu的log至少要和voter的log一樣長,因此它的log包含了voter的log中的每一個entry。這是一個矛盾,因為voter包含了committed entry而leaderU假設是不包含的。

7、除非,leaderU的last log term必須比voter的大。進一步說,它必須大於T,因為voter的last log term至少是T(它包含了term T的committed entry)。之前創建leaderU的last log entry的leader必須在它的log中包含了committed entry(根據假設)。那么,根據Log Matching Property,leaderU的log必須包含committed entry,這也是一個矛盾。

8、這完成了矛盾。因此,所有term大於T的leader必須包含所有來自於T並且在term T提交的entry。

9、Log Matching Property確保了future leader也會包含那些間接committed的entry,例如Figure 8(d)中的index 2。

給定Leader Completeness Property,證明Figure 3中的State Machine Safety Property就比較容易,即讓所有的state machine以相同的順序執行同樣的log entry。

5.5 Follower and candidate crashes

直到現在我們一直關注leader failures。follower和candidate的崩潰比起leader的崩潰要容易處理得多,而且它們的處理方式是相同的。如果一個follower或者candidate崩潰了,那么之后發送給它的RequestVote和AppendEntry RPC都會失敗。Raft通過不斷地重試來處理這些故障;如果崩潰的服務器重啟了,之后RPC就會成功完成。如果server在完成了RPC但是在回復之前崩潰了,那么它會在重啟之后收到一個同樣的RPC。但是Raft的RPC是冪等的,因此不會造成什么問題。比如一個follower接收了包含一個已經在log中存在的entry的AppendEntry request,它會直接忽略。

5.6 Timing and availability

我們對於Raft的一個要求是,它的安全性不能依賴於時間:系統不會因為有些事件發生地比預期慢了或快了而產生錯誤的結果。然而,可用性(系統及時響應client的能力)將不可避免地依賴於時間。比如,因為server崩潰造成的信息交換的時間比通常情況下來得長,candidate就不能停留足夠長的時間來贏得election;而沒有一個穩定的leader,Raft將不能進一步執行。

leader election是Raft中時間起最重要作用的地方。當系統滿足以下的timing requirement的時候,Raft就能夠選舉並且維護一個穩定的leader:

broadcastTime << electionTimeout << MTBF

在這個不等式中,broadcastTime是server並行地向集群中的每個server發送RPC並且收到回復的平均時間;electionTimeout就是如Section 5.2中描述的選舉超時;MTBF是單個server發生故障的時間間隔。broadcastTime必須比electionTimeout小幾個數量級,這樣leader就能可靠地發送heartbeat message從而防止follower開始選舉;通過隨機化的方法確定electionTimeout,該不等式又讓split vote不太可能出現。electionTimeout必須比MTBF小幾個數量級,從而讓系統能穩定運行。當leader崩潰時,系統會在大概一個electionTimeout里不可用;我們希望這只占整個時間的很小一部分。

broadcastTime和MTBF都是底層系統的特性,而electionTimeout是我們必須選擇的。Raft的RPC通常要求接收者持久化信息到stable storage,因此broadcastTime的范圍在0.5ms到20ms之間,這取決於存儲技術。因此,electionTimeout可以取10ms到500ms。通常,server的MTBF是幾個月或者更多,因此很容易滿足timing requirement。

6 Cluster membership changes

 直到現在為止,我們都假設集群的configuration(參與consensus algorithm的server集合)是固定的。但實際上,偶爾改變configuration是必要的,比如在server發生故障時將其移除或者改變the degree of replication。雖然這可以通過停止整個集群,更新configuration file,再重啟集群實現,但是這會讓集群在轉換期間變得不可用。另外,如果這其中存在手動操作的話,還會有操作失誤的風險。為了防止這些情況的發生,我們決定自動化configuration change並且將它們和Raft consensus algorithm結合起來。

為了保證configuration change mechanism的安全,在轉換期間不能有任意時刻對於同一個term有兩個leader。不幸的是,任何從old configuration轉換到new configuration的方法都是不安全的。不可能一次性對所有server進行自動轉換,所以在轉換期間集群會被潛在地分隔為兩個獨立的majority(見Figure 10)。

為了保證安全性,configuration change必須使用two-phase的方法。有很多種方法實現two-phase,比如有些系統使用first phase來禁用old configuration,從而不能處理client request;然后在second phase中使用new configuration。在Raft中,集群首先轉換到一個transitional configuration,我們稱作joint consensus;一旦joint consensus被committed之后,系統就過渡到new configuration。joint consensus 同時結合了old configuration和new configuration。

  • log entry會被備份到兩個configuration的所有server中
  • 來自任意一個configuration的server都可能會成為leader
  • Agreement(election和entry的commitment)需要同時得到old configuration和new configuration的majority

joint consensus允許單個的server在不妥協安全性的情況下,在不同的時間對進行configuration的過渡。另外,joint consensus允許集群在configuration轉換期間依舊能夠處理來自client的請求。

集群的configuration被存儲在replicated log的special entry中,並且通過它來通信;Figure 11說明了configuration改變的過程。當leader收到了一個將configuration從Cold轉換到Cnew請求,它會將joint consensus的configuration(figure中的Cold,new)作為一個log entry存儲並且使用上文描述的機制進行備份。一旦一個給定的server將一個new configuration entry加入它的log中,它就會在以后所有的decision中使用該configuration(server總是使用它log中的latest configuration,而不管該entry是否被committed)。這意味着leader會使用Cold,new來決定何時Cold,new被committed。如果該leader崩潰了,一個新的leader可能使用Cold或者Cold,new,這取決於winning candidate是否收到了Cold,new。在任何情況下,Cnew都不能在這個階段做單方面的決定。

 一旦Cold,new被committed,Cold或者Cnew就不能在沒有對方同意的情況下單獨做decision了,而Leader Completeness Property則確保了Cold,new的log entry的server才能被選作leader。現在leader創建一個描述Cnew的log entry並且將它備份到整個集群是安全的。同樣,這個configuration只要server看到它就會生效。當新的configuration在Cnew的規則被committed時,old configuration就不再有效了,而那些不在new configuration中的server就會被關閉。如Figure 11所示,沒有一個時刻,Cold或者Cnew會單方面做決定;這就保證了安全性。

對於reconfiguration還有三個問題需要處理。第一個問題是新加入的server可能初始的時候沒有存儲任何log entry。如果它們以這種狀態直接添加進集群,可能會花費相當多的時間讓它們趕上來,而在這期間就不能commit新的log entry了。為了避免availability gaps,Raft在configuration change之前引入了一個additional phase,在這期間新的server作為non-voting member(leader將log entry向它們備份,但是在計算majority時,並不考慮它們)加入集群。一旦新加入的server趕上了集群中的其他server之后,reconfiguration就會按照上面描述的步驟進行。

第二個問題是集群的leader可能並不包含在new configuration中。在這種情況下,leader一旦commit了Cnew log entry之后leader就會step down(返回follower的狀態)。這意味着會有這樣一段時間(當在commit Cnew)時,leader可能會管理一個並不包含它自己的集群;它備份log entry,但是並不把它自己考慮在majority的計算中。leader的轉換會在Cnew被committed之后發生,因為這是第一次new configuration可以獨立運行(總是可以在Cnew中選出一個leader)。在這之前,只有Cold中的server能被選為leader。

第三個問題是removed server(那些不在Cnew中的server)可能會破壞集群。這些server不會收到heartbeats,所以它們會timeout並且開始new election。於是它們會用新的term number發送RequestVote RPC,這會導致current leader恢復到follower的狀態。一個新的leader最終會被選舉出來,但是removed server還會再次timeout,而這個過程會不斷重復,最終導致可用性非常差。

為了防止這樣的情況發生,server會無視RequestVote RPC,如果它們認為current leader依舊存在的話。特別地,如果一個server在election timeout內收到了一個RequestVote RPC,它不會更新它的term或者進行投票。這不會影響正常的election,在開始election之前每個server都至少等待一個最小的election timeout。然而,這避免了removed server帶來的破壞;如果一個leader能夠從它的集群中得到heartbeat,那么它就不會受到更大的term number的影響。

7 Clients and log compaction

由於篇幅的原因本章就略過了,但是相關的資料在本論文的擴展版中可以獲得。其中描述了client如何和Raft進行交互,包括client怎么找到cluster leader以及Raft如何支持linearizable semantics。擴展版本中還描述了如何利用snapshotting的方法回收replicated log的空間。這些問題在所有consensus-based system中都會出現,Raft的解決方案和它們是類似的。

8 Implementation and evalution

我們已經將Raft作為存儲RAMCloud配置信息的replicated state machine實現並且協助RAMCloud coordinator的故障轉移。Raft的實現大概包含2000行C++代碼,不包括測試,注釋以及空白行。源代碼可以自由獲取。同時還有25個基於本論文的關於Raft的獨立第三方開源實現。同時,還有各種公司在部署Raft-based systems。本節的剩余部分將從可理解性,正確性以及性能三個標准來評估Raft。

....

10 Conclusion

算法的設計通常以正確性,效率以及簡潔作為主要目標。雖然這些目標都是非常有意義的,但是我們認為可理解性同樣重要。在開發者將算法實際實現以前,這一切都不可能實現,而這些實現往往都會偏離或者擴展算法的本意。除非開發者對算法有了深刻的理解並且能夠對它建立直覺,否則將很難從它們的實現中獲得想要的特性。

一個被普遍接收但是難以理解的算法Paxos已經困擾了學生和開發者很多年了,而在本篇論文中,我們解決了distributed consensus的這個問題。我們開發了一種新的算法,Raft,就像上面展示的,它比Paxos更加易於理解。我們同樣相信Raft為實際系統的構建提供了一個更好的基礎。以可理解性作為主要目標改變了我們對Raft的設計;隨着設計的進行我們發現我們在不斷重用一些技術,例如解構問題,以及簡化狀態空間。這些技術不僅提高了Raft的可理解性,同時也更讓我們相信它的正確性。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM