摘要算法
Raft 是一種爲了管理複製日誌的一致性算法。它提供了和 Paxos 算法相同的功能和性能,可是它的算法結構和 Paxos 不一樣,使得 Raft 算法更加容易理解而且更容易構建實際的系統。爲了提高可理解性,Raft 將一致性算法分解成了幾個關鍵模塊,例如領導人選舉、日誌複製和安全性。同時它經過實施一個更強的一致性來減小須要考慮的狀態的數量。從一個用戶研究的結果能夠證實,對於學生而言,Raft 算法比 Paxos 算法更加容易學習。Raft 算法還包括一個新的機制來容許集羣成員的動態改變,它利用重疊的大多數來保證安全性。安全
一致性算法容許一組機器像一個總體同樣工做,即便其中一些機器出現故障也可以繼續工做下去。正由於如此,一致性算法在構建可信賴的大規模軟件系統中扮演着重要的角色。在過去的 10 年裏,Paxos 算法統治着一致性算法這一領域:絕大多數的實現都是基於 Paxos 或者受其影響。同時 Paxos 也成爲了教學領域裏講解一致性問題時的示例。服務器
可是不幸的是,儘管有不少工做都在嘗試下降它的複雜性,可是 Paxos 算法依然十分難以理解。而且,Paxos 自身的算法結構須要進行大幅的修改纔可以應用到實際的系統中。這些都致使了工業界和學術界都對 Paxos 算法感到十分頭疼。網絡
和 Paxos 算法進行過努力以後,咱們開始尋找一種新的一致性算法,能夠爲構建實際的系統和教學提供更好的基礎。咱們的作法是不尋常的,咱們的首要目標是可理解性:咱們是否能夠在實際系統中定義一個一致性算法,而且可以比 Paxos 算法以一種更加容易的方式來學習。此外,咱們但願該算法方便系統構建者的直覺的發展。不只一個算法可以工做很重要,並且可以顯而易見的知道爲何能工做也很重要。併發
Raft 一致性算法就是這些工做的結果。在設計 Raft 算法的時候,咱們使用一些特別的技巧來提高它的可理解性,包括算法分解(Raft 主要被分紅了領導人選舉,日誌複製和安全三個模塊)和減小狀態機的狀態(相對於 Paxos,Raft 減小了非肯定性和服務器互相處於非一致性的方式)。一份針對兩所大學 43 個學生的研究代表 Raft 明顯比 Paxos 算法更加容易理解。在這些學生同時學習了這兩種算法以後,和 Paxos 比起來,其中 33 個學生可以回答有關於 Raft 的問題。分佈式
Raft 算法在許多方面和現有的一致性算法都很類似(主要是 Oki 和 Liskov 的 Viewstamped Replication),可是它也有一些獨特的特性:性能
咱們相信,Raft 算法不論出於教學目的仍是做爲實踐項目的基礎都是要比 Paxos 或者其餘一致性算法要優異的。它比其餘算法更加簡單,更加容易理解;它的算法描述足以實現一個現實的系統;它有好多開源的實現而且在不少公司裏使用;它的安全性已經被證實;它的效率和其餘算法比起來也不相上下。學習
接下來,這篇論文會介紹如下內容:複製狀態機問題(第 2 節),討論 Paxos 的優勢和缺點(第 3 節),討論咱們爲了可理解性而採起的方法(第 4 節),闡述 Raft 一致性算法(第 5-8 節),評價 Raft 算法(第 9 節),以及一些相關的工做(第 10 節)。區塊鏈
一致性算法是從複製狀態機的背景下提出的(參考英文原文引用37)。在這種方法中,一組服務器上的狀態機產生相同狀態的副本,而且在一些機器宕掉的狀況下也能夠繼續運行。複製狀態機在分佈式系統中被用於解決不少容錯的問題。例如,大規模的系統中一般都有一個集羣領導者,像 GFS、HDFS 和 RAMCloud,典型應用就是一個獨立的的複製狀態機去管理領導選舉和存儲配置信息而且在領導人宕機的狀況下也要存活下來。好比 Chubby 和 ZooKeeper。優化
圖 1 :複製狀態機的結構。一致性算法管理着來自客戶端指令的複製日誌。狀態機從日誌中處理相同順序的相同指令,因此產生的結果也是相同的。
複製狀態機一般都是基於複製日誌實現的,如圖 1。每個服務器存儲一個包含一系列指令的日誌,而且按照日誌的順序進行執行。每個日誌都按照相同的順序包含相同的指令,因此每個服務器都執行相同的指令序列。由於每一個狀態機都是肯定的,每一次執行操做都產生相同的狀態和一樣的序列。
保證複製日誌相同就是一致性算法的工做了。在一臺服務器上,一致性模塊接收客戶端發送來的指令而後增長到本身的日誌中去。它和其餘服務器上的一致性模塊進行通訊來保證每個服務器上的日誌最終都以相同的順序包含相同的請求,儘管有些服務器會宕機。一旦指令被正確的複製,每個服務器的狀態機按照日誌順序處理他們,而後輸出結果被返回給客戶端。所以,服務器集羣看起來造成一個高可靠的狀態機。
實際系統中使用的一致性算法一般含有如下特性:
在過去的 10 年裏,Leslie Lamport 的 Paxos 算法幾乎已經成爲一致性的代名詞:Paxos 是在課程教學中最常用的算法,同時也是大多數一致性算法實現的起點。Paxos 首先定義了一個可以達成單一決策一致的協議,好比單條的複製日誌項。咱們把這一子集叫作單決策 Paxos。而後經過組合多個 Paxos 協議的實例來促進一系列決策的達成。Paxos 保證安全性和活性,同時也支持集羣成員關係的變動。Paxos 的正確性已經被證實,在一般狀況下也很高效。
不幸的是,Paxos 有兩個明顯的缺點。第一個缺點是 Paxos 算法特別的難以理解。完整的解釋是出了名的不透明;經過極大的努力以後,也只有少數人成功理解了這個算法。所以,有了幾回用更簡單的術語來解釋 Paxos 的嘗試。儘管這些解釋都只關注了單決策的子集問題,但依然很具備挑戰性。在 2012 年 NSDI 的會議中的一次調查顯示,不多有人對 Paxos 算法感到滿意,甚至在經驗老道的研究者中也是如此。咱們本身也嘗試去理解 Paxos;咱們一直沒能理解 Paxos 直到咱們讀了不少對 Paxos 的簡化解釋而且設計了咱們本身的算法以後,這一過程花了近一年時間。
咱們假設 Paxos 的不透明性來自它選擇單決策問題做爲它的基礎。單決策 Paxos 是晦澀微妙的,它被劃分紅了兩種沒有簡單直觀解釋和沒法獨立理解的情景。所以,這致使了很難創建起直觀的感覺爲何單決策 Paxos 算法可以工做。構成多決策 Paxos 增長了不少錯綜複雜的規則。咱們相信,在多決策上達成一致性的問題(一份日誌而不是單一的日誌記錄)可以被分解成其餘的方式而且更加直接和明顯。
Paxos算法的第二個問題就是它沒有提供一個足夠好的用來構建一個現實系統的基礎。一個緣由是尚未一種被普遍認同的多決策問題的算法。Lamport 的描述基本上都是關於單決策 Paxos 的;他簡要描述了實施多決策 Paxos 的方法,可是缺少不少細節。固然也有不少具體化 Paxos 的嘗試,可是他們都互相不同,和 Paxos 的概述也不一樣。例如 Chubby 這樣的系統實現了一個相似於 Paxos 的算法,可是大多數的細節並無被公開。
並且,Paxos 算法的結構也不是十分易於構建實踐的系統;單決策分解也會產生其餘的結果。例如,獨立的選擇一組日誌條目而後合併成一個序列化的日誌並無帶來太多的好處,僅僅增長了很多複雜性。圍繞着日誌來設計一個系統是更加簡單高效的;新日誌條目以嚴格限制的順序增添到日誌中去。另外一個問題是,Paxos 使用了一種對等的點對點的方式做爲它的核心(儘管它最終提議了一種弱領導人的方法來優化性能)。在只有一個決策會被制定的簡化世界中是頗有意義的,可是不多有現實的系統使用這種方式。若是有一系列的決策須要被制定,首先選擇一個領導人,而後讓他去協調全部的決議,會更加簡單快速。
所以,實際的系統中不多有和 Paxos 類似的實踐。每一種實現都是從 Paxos 開始研究,而後發現不少實現上的難題,再而後開發了一種和 Paxos 明顯不同的結構。這樣是很是費時和容易出錯的,而且理解 Paxos 的難度使得這個問題更加糟糕。Paxos 算法在理論上被證實是正確可行的,可是現實的系統和 Paxos 差異是如此的大,以致於這些證實沒有什麼太大的價值。下面來自 Chubby 實現很是典型:
在Paxos算法描述和實現現實系統中間有着巨大的鴻溝。最終的系統創建在一種沒有通過證實的算法之上。
因爲以上問題,咱們認爲 Paxos 算法既沒有提供一個良好的基礎給實踐的系統,也沒有給教學很好的幫助。基於一致性問題在大規模軟件系統中的重要性,咱們決定看看咱們是否能夠設計一個擁有更好特性的替代 Paxos 的一致性算法。Raft算法就是此次實驗的結果。
設計 Raft 算法咱們有幾個初衷:它必須提供一個完整的實際的系統實現基礎,這樣才能大大減小開發者的工做;它必須在任何狀況下都是安全的而且在大多數的狀況下都是可用的;而且它的大部分操做必須是高效的。可是咱們最重要也是最大的挑戰是可理解性。它必須保證對於廣泛的人羣均可以十分容易的去理解。另外,它必須可以讓人造成直觀的認識,這樣系統的構建者纔可以在現實中進行必然的擴展。
在設計 Raft 算法的時候,有不少的點須要咱們在各類備選方案中進行選擇。在這種狀況下,咱們評估備選方案基於可理解性原則:解釋各個備選方案有多大的難度(例如,Raft 的狀態空間有多複雜,是否有微妙的暗示)?對於一個讀者而言,徹底理解這個方案和暗示是否容易?
咱們意識到對這種可理解性分析上具備高度的主觀性;儘管如此,咱們使用了兩種一般適用的技術來解決這個問題。第一個技術就是衆所周知的問題分解:只要有可能,咱們就將問題分解成幾個相對獨立的,可被解決的、可解釋的和可理解的子問題。例如,Raft 算法被咱們分紅領導人選舉,日誌複製,安全性和角色改變幾個部分。
咱們使用的第二個方法是經過減小狀態的數量來簡化須要考慮的狀態空間,使得系統更加連貫而且在可能的時候消除不肯定性。特別的,全部的日誌是不容許有空洞的,而且 Raft 限制了日誌之間變成不一致狀態的可能。儘管在大多數狀況下咱們都試圖去消除不肯定性,可是也有一些狀況下不肯定性能夠提高可理解性。尤爲是,隨機化方法增長了不肯定性,可是他們有利於減小狀態空間數量,經過處理全部可能選擇時使用類似的方法。咱們使用隨機化去簡化 Raft 中領導人選舉算法。
Raft 是一種用來管理章節 2 中描述的複製日誌的算法。圖 2 爲了參考之用,總結這個算法的簡略版本,圖 3 列舉了這個算法的一些關鍵特性。圖中的這些元素會在剩下的章節逐一介紹。
Raft 經過選舉一個高貴的領導人,而後給予他所有的管理複製日誌的責任來實現一致性。領導人從客戶端接收日誌條目,把日誌條目複製到其餘服務器上,而且當保證安全性的時候告訴其餘的服務器應用日誌條目到他們的狀態機中。擁有一個領導人大大簡化了對複製日誌的管理。例如,領導人能夠決定新的日誌條目須要放在日誌中的什麼位置而不須要和其餘服務器商議,而且數據都從領導人流向其餘服務器。一個領導人能夠宕機,能夠和其餘服務器失去鏈接,這時一個新的領導人會被選舉出來。
經過領導人的方式,Raft 將一致性問題分解成了三個相對獨立的子問題,這些問題會在接下來的子章節中進行討論:
在展現一致性算法以後,這一章節會討論可用性的一些問題和計時在系統的做用。
狀態:
狀態 | 全部服務器上持久存在的 |
---|---|
currentTerm | 服務器最後一次知道的任期號(初始化爲 0,持續遞增) |
votedFor | 在當前得到選票的候選人的 Id |
log[] | 日誌條目集;每個條目包含一個用戶狀態機執行的指令,和收到時的任期號 |
狀態 | 全部服務器上常常變的 |
---|---|
commitIndex | 已知的最大的已經被提交的日誌條目的索引值 |
lastApplied | 最後被應用到狀態機的日誌條目索引值(初始化爲 0,持續遞增) |
狀態 | 在領導人裏常常改變的 (選舉後從新初始化) |
---|---|
nextIndex[] | 對於每個服務器,須要發送給他的下一個日誌條目的索引值(初始化爲領導人最後索引值加一) |
matchIndex[] | 對於每個服務器,已經複製給他的日誌的最高索引值 |
附加日誌 RPC:
由領導人負責調用來複制日誌指令;也會用做heartbeat
參數 | 解釋 |
---|---|
term | 領導人的任期號 |
leaderId | 領導人的 Id,以便於跟隨者重定向請求 |
prevLogIndex | 新的日誌條目緊隨以前的索引值 |
prevLogTerm | prevLogIndex 條目的任期號 |
entries[] | 準備存儲的日誌條目(表示心跳時爲空;一次性發送多個是爲了提升效率) |
leaderCommit | 領導人已經提交的日誌的索引值 |
返回值 | 解釋 |
---|---|
term | 當前的任期號,用於領導人去更新本身 |
success | 跟隨者包含了匹配上 prevLogIndex 和 prevLogTerm 的日誌時爲真 |
接收者實現:
term < currentTerm
就返回 false (5.1 節)leaderCommit > commitIndex
,令 commitIndex 等於 leaderCommit 和 新日誌條目索引值中較小的一個請求投票 RPC:
由候選人負責調用用來徵集選票(5.2 節)
參數 | 解釋 |
---|---|
term | 候選人的任期號 |
candidateId | 請求選票的候選人的 Id |
lastLogIndex | 候選人的最後日誌條目的索引值 |
lastLogTerm | 候選人最後日誌條目的任期號 |
返回值 | 解釋 |
---|---|
term | 當前任期號,以便於候選人去更新本身的任期號 |
voteGranted | 候選人贏得了此張選票時爲真 |
接收者實現:
term < currentTerm
返回 false (5.2 節)全部服務器需遵照的規則:
全部服務器:
commitIndex > lastApplied
,那麼就 lastApplied 加一,並把log[lastApplied]
應用到狀態機中(5.3 節)T > currentTerm
,那麼就令 currentTerm 等於 T,並切換狀態爲跟隨者(5.1 節)跟隨者(5.2 節):
候選人(5.2 節):
領導人:
N > commitIndex
的 N,而且大多數的matchIndex[i] ≥ N
成立,而且log[N].term == currentTerm
成立,那麼令 commitIndex 等於這個 N (5.3 和 5.4 節)圖 2:一個關於 Raft 一致性算法的濃縮總結(不包括成員變換和日誌壓縮)。
特性 | 解釋 |
---|---|
選舉安全特性 | 對於一個給定的任期號,最多隻會有一個領導人被選舉出來(5.2 節) |
領導人只附加原則 | 領導人絕對不會刪除或者覆蓋本身的日誌,只會增長(5.3 節) |
日誌匹配原則 | 若是兩個日誌在相同的索引位置的日誌條目的任期號相同,那麼咱們就認爲這個日誌從頭到這個索引位置之間所有徹底相同(5.3 節) |
領導人徹底特性 | 若是某個日誌條目在某個任期號中已經被提交,那麼這個條目必然出如今更大任期號的全部領導人中(5.4 節) |
狀態機安全特性 | 若是一個領導人已經在給定的索引值位置的日誌條目應用到狀態機中,那麼其餘任何的服務器在這個索引位置不會提交一個不一樣的日誌(5.4.3 節) |
圖 3:Raft 在任什麼時候候都保證以上的各個特性。
一個 Raft 集羣包含若干個服務器節點;一般是 5 個,這容許整個系統容忍 2 個節點的失效。在任什麼時候刻,每個服務器節點都處於這三個狀態之一:領導人、跟隨者或者候選人。在一般狀況下,系統中只有一個領導人而且其餘的節點所有都是跟隨者。跟隨者都是被動的:他們不會發送任何請求,只是簡單的響應來自領導者或者候選人的請求。領導人處理全部的客戶端請求(若是一個客戶端和跟隨者聯繫,那麼跟隨者會把請求重定向給領導人)。第三種狀態,候選人,是用來在 5.2 節描述的選舉新領導人時使用。圖 4 展現了這些狀態和他們之間的轉換關係;這些轉換關係會在接下來進行討論。
圖 4:服務器狀態。跟隨者只響應來自其餘服務器的請求。若是跟隨者接收不到消息,那麼他就會變成候選人併發起一次選舉。得到集羣中大多數選票的候選人將成爲領導者。在一個任期內,領導人一直都會是領導人直到本身宕機了。
圖 5:時間被劃分紅一個個的任期,每一個任期開始都是一次選舉。在選舉成功後,領導人會管理整個集羣直到任期結束。有時候選舉會失敗,那麼這個任期就會沒有領導人而結束。任期之間的切換能夠在不一樣的時間不一樣的服務器上觀察到。
Raft 把時間分割成任意長度的任期,如圖 5。任期用連續的整數標記。每一段任期從一次選舉開始,就像章節 5.2 描述的同樣,一個或者多個候選人嘗試成爲領導者。若是一個候選人贏得選舉,而後他就在接下來的任期內充當領導人的職責。在某些狀況下,一次選舉過程會形成選票的瓜分。在這種狀況下,這一任期會以沒有領導人結束;一個新的任期(和一次新的選舉)會很快從新開始。Raft 保證了在一個給定的任期內,最多隻有一個領導者。
不一樣的服務器節點可能屢次觀察到任期之間的轉換,但在某些狀況下,一個節點也可能觀察不到任何一次選舉或者整個任期全程。任期在 Raft 算法中充當邏輯時鐘的做用,這會容許服務器節點查明一些過時的信息好比陳舊的領導者。每個節點存儲一個當前任期號,這一編號在整個時期內單調的增加。當服務器之間通訊的時候會交換當前任期號;若是一個服務器的當前任期號比其餘人小,那麼他會更新本身的編號到較大的編號值。若是一個候選人或者領導者發現本身的任期號過時了,那麼他會當即恢復成跟隨者狀態。若是一個節點接收到一個包含過時的任期號的請求,那麼他會直接拒絕這個請求。
Raft 算法中服務器節點之間通訊使用遠程過程調用(RPCs),而且基本的一致性算法只須要兩種類型的 RPCs。請求投票(RequestVote) RPCs 由候選人在選舉期間發起(章節 5.2),而後附加條目(AppendEntries)RPCs 由領導人發起,用來複制日誌和提供一種心跳機制(章節 5.3)。第 7 節爲了在服務器之間傳輸快照增長了第三種 RPC。當服務器沒有及時的收到 RPC 的響應時,會進行重試, 而且他們可以並行的發起 RPCs 來得到最佳的性能。
Raft 使用一種心跳機制來觸發領導人選舉。當服務器程序啓動時,他們都是跟隨者身份。一個服務器節點繼續保持着跟隨者狀態只要他從領導人或者候選者處接收到有效的 RPCs。領導者週期性的向全部跟隨者發送心跳包(即不包含日誌項內容的附加日誌項 RPCs)來維持本身的權威。若是一個跟隨者在一段時間裏沒有接收到任何消息,也就是選舉超時,那麼他就會認爲系統中沒有可用的領導者,而且發起選舉以選出新的領導者。
要開始一次選舉過程,跟隨者先要增長本身的當前任期號而且轉換到候選人狀態。而後他會並行的向集羣中的其餘服務器節點發送請求投票的 RPCs 來給本身投票。候選人會繼續保持着當前狀態直到如下三件事情之一發生:(a) 他本身贏得了此次的選舉,(b) 其餘的服務器成爲領導者,(c) 一段時間以後沒有任何一個獲勝的人。這些結果會分別的在下面的段落裏進行討論。
當一個候選人從整個集羣的大多數服務器節點得到了針對同一個任期號的選票,那麼他就贏得了此次選舉併成爲領導人。每個服務器最多會對一個任期號投出一張選票,按照先來先服務的原則(注意:5.4 節在投票上增長了一點額外的限制)。要求大多數選票的規則確保了最多隻會有一個候選人贏得這次選舉(圖 3 中的選舉安全性)。一旦候選人贏得選舉,他就當即成爲領導人。而後他會向其餘的服務器發送心跳消息來創建本身的權威而且阻止新的領導人的產生。
在等待投票的時候,候選人可能會從其餘的服務器接收到聲明它是領導人的附加日誌項 RPC。若是這個領導人的任期號(包含在這次的 RPC中)不小於候選人當前的任期號,那麼候選人會認可領導人合法並回到跟隨者狀態。 若是這次 RPC 中的任期號比本身小,那麼候選人就會拒絕此次的 RPC 而且繼續保持候選人狀態。
第三種可能的結果是候選人既沒有贏得選舉也沒有輸:若是有多個跟隨者同時成爲候選人,那麼選票可能會被瓜分以致於沒有候選人能夠贏得大多數人的支持。當這種狀況發生的時候,每個候選人都會超時,而後經過增長當前任期號來開始一輪新的選舉。然而,沒有其餘機制的話,選票可能會被無限的重複瓜分。
Raft 算法使用隨機選舉超時時間的方法來確保不多會發生選票瓜分的狀況,就算髮生也能很快的解決。爲了阻止選票起初就被瓜分,選舉超時時間是從一個固定的區間(例如 150-300 毫秒)隨機選擇。這樣能夠把服務器都分散開以致於在大多數狀況下只有一個服務器會選舉超時;而後他贏得選舉並在其餘服務器超時以前發送心跳包。一樣的機制被用在選票瓜分的狀況下。每個候選人在開始一次選舉的時候會重置一個隨機的選舉超時時間,而後在超時時間內等待投票的結果;這樣減小了在新的選舉中另外的選票瓜分的可能性。9.3 節展現了這種方案可以快速的選出一個領導人。
領導人選舉這個例子,體現了可理解性原則是如何指導咱們進行方案設計的。起初咱們計劃使用一種排名系統:每個候選人都被賦予一個惟一的排名,供候選人之間競爭時進行選擇。若是一個候選人發現另外一個候選人擁有更高的排名,那麼他就會回到跟隨者狀態,這樣高排名的候選人可以更加容易的贏得下一次選舉。可是咱們發現這種方法在可用性方面會有一點問題(若是高排名的服務器宕機了,那麼低排名的服務器可能會超時並再次進入候選人狀態。並且若是這個行爲發生得足夠快,則可能會致使整個選舉過程都被重置掉)。咱們針對算法進行了屢次調整,可是每次調整以後都會有新的問題。最終咱們認爲隨機重試的方法是更加明顯和易於理解的。
一旦一個領導人被選舉出來,他就開始爲客戶端提供服務。客戶端的每個請求都包含一條被複制狀態機執行的指令。領導人把這條指令做爲一條新的日誌條目附加到日誌中去,而後並行的發起附加條目 RPCs 給其餘的服務器,讓他們複製這條日誌條目。當這條日誌條目被安全的複製(下面會介紹),領導人會應用這條日誌條目到它的狀態機中而後把執行的結果返回給客戶端。若是跟隨者崩潰或者運行緩慢,再或者網絡丟包,領導人會不斷的重複嘗試附加日誌條目 RPCs (儘管已經回覆了客戶端)直到全部的跟隨者都最終存儲了全部的日誌條目。
圖 6:日誌由有序序號標記的條目組成。每一個條目都包含建立時的任期號(圖中框中的數字),和一個狀態機須要執行的指令。一個條目當能夠安全的被應用到狀態機中去的時候,就認爲是能夠提交了。
日誌以圖 6 展現的方式組織。每個日誌條目存儲一條狀態機指令和從領導人收到這條指令時的任期號。日誌中的任期號用來檢查是否出現不一致的狀況,同時也用來保證圖 3 中的某些性質。每一條日誌條目同時也都有一個整數索引值來代表它在日誌中的位置。
領導人來決定何時把日誌條目應用到狀態機中是安全的;這種日誌條目被稱爲已提交。Raft 算法保證全部已提交的日誌條目都是持久化的而且最終會被全部可用的狀態機執行。在領導人將建立的日誌條目複製到大多數的服務器上的時候,日誌條目就會被提交(例如在圖 6 中的條目 7)。同時,領導人的日誌中以前的全部日誌條目也都會被提交,包括由其餘領導人建立的條目。5.4 節會討論某些當在領導人改變以後應用這條規則的隱晦內容,同時他也展現了這種提交的定義是安全的。領導人跟蹤了最大的將會被提交的日誌項的索引,而且索引值會被包含在將來的全部附加日誌 RPCs (包括心跳包),這樣其餘的服務器才能最終知道領導人的提交位置。一旦跟隨者知道一條日誌條目已經被提交,那麼他也會將這個日誌條目應用到本地的狀態機中(按照日誌的順序)。
咱們設計了 Raft 的日誌機制來維護一個不一樣服務器的日誌之間的高層次的一致性。這麼作不只簡化了系統的行爲也使得更加可預計,同時他也是安全性保證的一個重要組件。Raft 維護着如下的特性,這些同時也組成了圖 3 中的日誌匹配特性:
第一個特性來自這樣的一個事實,領導人最多在一個任期裏在指定的一個日誌索引位置建立一條日誌條目,同時日誌條目在日誌中的位置也歷來不會改變。第二個特性由附加日誌 RPC 的一個簡單的一致性檢查所保證。在發送附加日誌 RPC 的時候,領導人會把新的日誌條目緊接着以前的條目的索引位置和任期號包含在裏面。若是跟隨者在它的日誌中找不到包含相同索引位置和任期號的條目,那麼他就會拒絕接收新的日誌條目。一致性檢查就像一個概括步驟:一開始空的日誌狀態確定是知足日誌匹配特性的,而後一致性檢查保護了日誌匹配特性當日志擴展的時候。所以,每當附加日誌 RPC 返回成功時,領導人就知道跟隨者的日誌必定是和本身相同的了。
在正常的操做中,領導人和跟隨者的日誌保持一致性,因此附加日誌 RPC 的一致性檢查歷來不會失敗。然而,領導人崩潰的狀況會使得日誌處於不一致的狀態(老的領導人可能尚未徹底複製全部的日誌條目)。這種不一致問題會在領導人和跟隨者的一系列崩潰下加重。圖 7 展現了跟隨者的日誌可能和新的領導人不一樣的方式。跟隨者可能會丟失一些在新的領導人中有的日誌條目,他也可能擁有一些領導人沒有的日誌條目,或者二者都發生。丟失或者多出日誌條目可能會持續多個任期。
圖 7:當一個領導人成功當選時,跟隨者多是任何狀況(a-f)。每個盒子表示是一個日誌條目;裏面的數字表示任期號。跟隨者可能會缺乏一些日誌條目(a-b),可能會有一些未被提交的日誌條目(c-d),或者兩種狀況都存在(e-f)。例如,場景 f 可能會這樣發生,某服務器在任期 2 的時候是領導人,已附加了一些日誌條目到本身的日誌中,但在提交以前就崩潰了;很快這個機器就被重啓了,在任期 3 從新被選爲領導人,而且又增長了一些日誌條目到本身的日誌中;在任期 2 和任期 3 的日誌被提交以前,這個服務器又宕機了,而且在接下來的幾個任期裏一直處於宕機狀態。
在 Raft 算法中,領導人處理不一致是經過強制跟隨者直接複製本身的日誌來解決了。這意味着在跟隨者中的衝突的日誌條目會被領導人的日誌覆蓋。5.4 節會闡述如何經過增長一些限制來使得這樣的操做是安全的。
要使得跟隨者的日誌進入和本身一致的狀態,領導人必須找到最後二者達成一致的地方,而後刪除從那個點以後的全部日誌條目,發送本身的日誌給跟隨者。全部的這些操做都在進行附加日誌 RPCs 的一致性檢查時完成。領導人針對每個跟隨者維護了一個 nextIndex,這表示下一個須要發送給跟隨者的日誌條目的索引地址。當一個領導人剛得到權力的時候,他初始化全部的 nextIndex 值爲本身的最後一條日誌的index加1(圖 7 中的 11)。若是一個跟隨者的日誌和領導人不一致,那麼在下一次的附加日誌 RPC 時的一致性檢查就會失敗。在被跟隨者拒絕以後,領導人就會減少 nextIndex 值並進行重試。最終 nextIndex 會在某個位置使得領導人和跟隨者的日誌達成一致。當這種狀況發生,附加日誌 RPC 就會成功,這時就會把跟隨者衝突的日誌條目所有刪除而且加上領導人的日誌。一旦附加日誌 RPC 成功,那麼跟隨者的日誌就會和領導人保持一致,而且在接下來的任期裏一直繼續保持。
若是須要的話,算法能夠經過減小被拒絕的附加日誌 RPCs 的次數來優化。例如,當附加日誌 RPC 的請求被拒絕的時候,跟隨者能夠包含衝突的條目的任期號和本身存儲的那個任期的最先的索引地址。藉助這些信息,領導人能夠減少 nextIndex 越過全部那個任期衝突的全部日誌條目;這樣就變成每一個任期須要一次附加條目 RPC 而不是每一個條目一次。在實踐中,咱們十分懷疑這種優化是不是必要的,由於失敗是不多發生的而且也不大可能會有這麼多不一致的日誌。
經過這種機制,領導人在得到權力的時候就不須要任何特殊的操做來恢復一致性。他只須要進行正常的操做,而後日誌就能自動的在回覆附加日誌 RPC 的一致性檢查失敗的時候自動趨於一致。領導人歷來不會覆蓋或者刪除本身的日誌(圖 3 的領導人只附加特性)。
日誌複製機制展現出了第 2 節中形容的一致性特性:Raft 可以接受,複製並應用新的日誌條目只要大部分的機器是工做的;在一般的狀況下,新的日誌條目能夠在一次 RPC 中被複制給集羣中的大多數機器;而且單個的緩慢的跟隨者不會影響總體的性能。
前面的章節裏描述了 Raft 算法是如何選舉和複製日誌的。然而,到目前爲止描述的機制並不能充分的保證每個狀態機會按照相同的順序執行相同的指令。例如,一個跟隨者可能會進入不可用狀態同時領導人已經提交了若干的日誌條目,而後這個跟隨者可能會被選舉爲領導人而且覆蓋這些日誌條目;所以,不一樣的狀態機可能會執行不一樣的指令序列。
這一節經過在領導選舉的時候增長一些限制來完善 Raft 算法。這一限制保證了任何的領導人對於給定的任期號,都擁有了以前任期的全部被提交的日誌條目(圖 3 中的領導人完整特性)。增長這一選舉時的限制,咱們對於提交時的規則也更加清晰。最終,咱們將展現對於領導人完整特性的簡要證實,而且說明領導人是如何領導複製狀態機的作出正確行爲的。
在任何基於領導人的一致性算法中,領導人都必須存儲全部已經提交的日誌條目。在某些一致性算法中,例如 Viewstamped Replication,某個節點即便是一開始並無包含全部已經提交的日誌條目,它也能被選爲領導者。這些算法都包含一些額外的機制來識別丟失的日誌條目並把他們傳送給新的領導人,要麼是在選舉階段要麼在以後很快進行。不幸的是,這種方法會致使至關大的額外的機制和複雜性。Raft 使用了一種更加簡單的方法,它能夠保證全部以前的任期號中已經提交的日誌條目在選舉的時候都會出如今新的領導人中,不須要傳送這些日誌條目給領導人。這意味着日誌條目的傳送是單向的,只從領導人傳給跟隨者,而且領導人從不會覆蓋自身本地日誌中已經存在的條目。
Raft 使用投票的方式來阻止一個候選人贏得選舉除非這個候選人包含了全部已經提交的日誌條目。候選人爲了贏得選舉必須聯繫集羣中的大部分節點,這意味着每個已經提交的日誌條目在這些服務器節點中確定存在於至少一個節點上。若是候選人的日誌至少和大多數的服務器節點同樣新(這個新的定義會在下面討論),那麼他必定持有了全部已經提交的日誌條目。請求投票 RPC 實現了這樣的限制: RPC 中包含了候選人的日誌信息,而後投票人會拒絕掉那些日誌沒有本身新的投票請求。
Raft 經過比較兩份日誌中最後一條日誌條目的索引值和任期號定義誰的日誌比較新。若是兩份日誌最後的條目的任期號不一樣,那麼任期號大的日誌更加新。若是兩份日誌最後的條目任期號相同,那麼日誌比較長的那個就更加新。
如同 5.3 節介紹的那樣,領導人知道一條當前任期內的日誌記錄是能夠被提交的,只要它被存儲到了大多數的服務器上。若是一個領導人在提交日誌條目以前崩潰了,將來後續的領導人會繼續嘗試複製這條日誌記錄。然而,一個領導人不能判定一個以前任期裏的日誌條目被保存到大多數服務器上的時候就必定已經提交了。圖 8 展現了一種狀況,一條已經被存儲到大多數節點上的老日誌條目,也依然有可能會被將來的領導人覆蓋掉。
圖 8:如圖的時間序列展現了爲何領導人沒法決定對老任期號的日誌條目進行提交。在 (a) 中,S1 是領導者,部分的複製了索引位置 2 的日誌條目。在 (b) 中,S1 崩潰了,而後 S5 在任期 3 裏經過 S三、S4 和本身的選票贏得選舉,而後從客戶端接收了一條不同的日誌條目放在了索引 2 處。而後到 (c),S5 又崩潰了;S1 從新啓動,選舉成功,開始複製日誌。在這時,來自任期 2 的那條日誌已經被複制到了集羣中的大多數機器上,可是尚未被提交。若是 S1 在 (d) 中又崩潰了,S5 能夠從新被選舉成功(經過來自 S2,S3 和 S4 的選票),而後覆蓋了他們在索引 2 處的日誌。反之,若是在崩潰以前,S1 把本身主導的新任期裏產生的日誌條目複製到了大多數機器上,就如 (e) 中那樣,那麼在後面任期裏面這些新的日誌條目就會被提交(由於S5 就不可能選舉成功)。 這樣在同一時刻就同時保證了,以前的全部老的日誌條目就會被提交。
爲了消除圖 8 裏描述的狀況,Raft 永遠不會經過計算副本數目的方式去提交一個以前任期內的日誌條目。只有領導人當前任期裏的日誌條目經過計算副本數目能夠被提交;一旦當前任期的日誌條目以這種方式被提交,那麼因爲日誌匹配特性,以前的日誌條目也都會被間接的提交。在某些狀況下,領導人能夠安全的知道一個老的日誌條目是否已經被提交(例如,該條目是否存儲到全部服務器上),可是 Raft 爲了簡化問題使用一種更加保守的方法。
當領導人複製以前任期裏的日誌時,Raft 會爲全部日誌保留原始的任期號, 這在提交規則上產生了額外的複雜性。在其餘的一致性算法中,若是一個新的領導人要從新複製以前的任期裏的日誌時,它必須使用當前新的任期號。Raft 使用的方法更加容易辨別出日誌,由於它能夠隨着時間和日誌的變化對日誌維護着同一個任期編號。另外,和其餘的算法相比,Raft 中的新領導人只須要發送更少日誌條目(其餘算法中必須在他們被提交以前發送更多的冗餘日誌條目來爲他們從新編號)。
在給定了完整的 Raft 算法以後,咱們如今能夠更加精確的討論領導人完整性特性(這一討論基於 9.2 節的安全性證實)。咱們假設領導人徹底性特性是不存在的,而後咱們推出矛盾來。假設任期 T 的領導人(領導人 T)在任期內提交了一條日誌條目,可是這條日誌條目沒有被存儲到將來某個任期的領導人的日誌中。設大於 T 的最小任期 U 的領導人 U 沒有這條日誌條目。
圖 9:若是 S1 (任期 T 的領導者)提交了一條新的日誌在它的任期裏,而後 S5 在以後的任期 U 裏被選舉爲領導人,而後至少會有一個機器,如 S3,既擁有來自 S1 的日誌,也給 S5 投票了。
經過領導人徹底特性,咱們就能證實圖 3 中的狀態機安全特性,即若是服務器已經在某個給定的索引值應用了日誌條目到本身的狀態機裏,那麼其餘的服務器不會應用一個不同的日誌到同一個索引值上。在一個服務器應用一條日誌條目到他本身的狀態機中時,他的日誌必須和領導人的日誌,在該條目和以前的條目上相同,而且已經被提交。如今咱們來考慮在任何一個服務器應用一個指定索引位置的日誌的最小任期;日誌徹底特性保證擁有更高任期號的領導人會存儲相同的日誌條目,因此以後的任期裏應用某個索引位置的日誌條目也會是相同的值。所以,狀態機安全特性是成立的。
最後,Raft 要求服務器按照日誌中索引位置順序應用日誌條目。和狀態機安全特性結合起來看,這就意味着全部的服務器會應用相同的日誌序列集到本身的狀態機中,而且是按照相同的順序。
到目前爲止,咱們都只關注了領導人崩潰的狀況。跟隨者和候選人崩潰後的處理方式比領導人要簡單的多,而且他們的處理方式是相同的。若是跟隨者或者候選人崩潰了,那麼後續發送給他們的 RPCs 都會失敗。Raft 中處理這種失敗就是簡單的經過無限的重試;若是崩潰的機器重啓了,那麼這些 RPC 就會完整的成功。若是一個服務器在完成了一個 RPC,可是尚未響應的時候崩潰了,那麼在他從新啓動以後就會再次收到一樣的請求。Raft 的 RPCs 都是冪等的,因此這樣重試不會形成任何問題。例如一個跟隨者若是收到附加日誌請求可是他已經包含了這一日誌,那麼他就會直接忽略這個新的請求。
Raft 的要求之一就是安全性不能依賴時間:整個系統不能由於某些事件運行的比預期快一點或者慢一點就產生了錯誤的結果。可是,可用性(系統能夠及時的響應客戶端)不可避免的要依賴於時間。例如,若是消息交換比服務器故障間隔時間長,候選人將沒有足夠長的時間來贏得選舉;沒有一個穩定的領導人,Raft 將沒法工做。
領導人選舉是 Raft 中對時間要求最爲關鍵的方面。Raft 能夠選舉並維持一個穩定的領導人,只要系統知足下面的時間要求:
廣播時間(broadcastTime) << 選舉超時時間(electionTimeout) << 平均故障間隔時間(MTBF)
在這個不等式中,廣播時間指的是從一個服務器並行的發送 RPCs 給集羣中的其餘服務器並接收響應的平均時間;選舉超時時間就是在 5.2 節中介紹的選舉的超時時間限制;而後平均故障間隔時間就是對於一臺服務器而言,兩次故障之間的平均時間。廣播時間必須比選舉超時時間小一個量級,這樣領導人才可以發送穩定的心跳消息來阻止跟隨者開始進入選舉狀態;經過隨機化選舉超時時間的方法,這個不等式也使得選票瓜分的狀況變得不可能。選舉超時時間應該要比平均故障間隔時間小上幾個數量級,這樣整個系統才能穩定的運行。當領導人崩潰後,整個系統會大約至關於選舉超時的時間裏不可用;咱們但願這種狀況在整個系統的運行中不多出現。
廣播時間和平均故障間隔時間是由系統決定的,可是選舉超時時間是咱們本身選擇的。Raft 的 RPCs 須要接收方將信息持久化的保存到穩定存儲中去,因此廣播時間大約是 0.5 毫秒到 20 毫秒,取決於存儲的技術。所以,選舉超時時間可能須要在 10 毫秒到 500 毫秒之間。大多數的服務器的平均故障間隔時間都在幾個月甚至更長,很容易知足時間的需求。
本文經TopJohn受權轉自TopJohn's Blog
深刻淺出區塊鏈 - 系統學習區塊鏈,打造最好的區塊鏈技術博客。