摘要算法
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 節):
在轉變成候選人後就當即開始選舉過程
領導人:
若是對於一個跟隨者,最後日誌條目的索引值大於等於 nextIndex,那麼:發送從 nextIndex 開始的全部日誌條目:
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 毫秒之間。大多數的服務器的平均故障間隔時間都在幾個月甚至更長,很容易知足時間的需求。
到目前爲止,咱們都假設集羣的配置(加入到一致性算法的服務器集合)是固定不變的。可是在實踐中,偶爾是會改變集羣的配置的,例如替換那些宕機的機器或者改變複製級別。儘管能夠經過暫停整個集羣,更新全部配置,而後重啓整個集羣的方式來實現,可是在更改的時候集羣會不可用。另外,若是存在手工操做步驟,那麼就會有操做失誤的風險。爲了不這樣的問題,咱們決定自動化配置改變而且將其歸入到 Raft 一致性算法中來。
爲了讓配置修改機制可以安全,那麼在轉換的過程當中不可以存在任什麼時候間點使得兩個領導人同時被選舉成功在同一個任期裏。不幸的是,任何服務器直接從舊的配置直接轉換到新的配置的方案都是不安全的。一次性自動的轉換全部服務器是不可能的,因此在轉換期間整個集羣存在劃分紅兩個獨立的大多數羣體的可能性(見圖 10)。
圖 10:直接從一種配置轉到新的配置是十分不安全的,由於各個機器可能在任何的時候進行轉換。在這個例子中,集羣配額從 3 臺機器變成了 5 臺。不幸的是,存在這樣的一個時間點,兩個不一樣的領導人在同一個任期裏均可以被選舉成功。一個是經過舊的配置,一個經過新的配置。
爲了保證安全性,配置更改必須使用兩階段方法。目前有不少種兩階段的實現。例如,有些系統在第一階段停掉舊的配置因此集羣就不能處理客戶端請求;而後在第二階段在啓用新的配置。在 Raft 中,集羣先切換到一個過渡的配置,咱們稱之爲共同一致;一旦共同一致已經被提交了,那麼系統就切換到新的配置上。共同一致是老配置和新配置的結合:
共同一致容許獨立的服務器在不影響安全性的前提下,在不一樣的時間進行配置轉換過程。此外,共同一致可讓集羣在配置轉換的過程人依然響應客戶端的請求。
集羣配置在複製日誌中以特殊的日誌條目來存儲和通訊;圖 11 展現了配置轉換的過程。當一個領導人接收到一個改變配置從 C-old 到 C-new 的請求,他會爲了共同一致存儲配置(圖中的 C-old,new),之前面描述的日誌條目和副本的形式。一旦一個服務器將新的配置日誌條目增長到它的日誌中,他就會用這個配置來作出將來全部的決定(服務器老是使用最新的配置,不管他是否已經被提交)。這意味着領導人要使用 C-old,new 的規則來決定日誌條目 C-old,new 何時須要被提交。若是領導人崩潰了,被選出來的新領導人多是使用 C-old 配置也多是 C-old,new 配置,這取決於贏得選舉的候選人是否已經接收到了 C-old,new 配置。在任何狀況下, C-new 配置在這一時期都不會單方面的作出決定。
一旦 C-old,new 被提交,那麼不管是 C-old 仍是 C-new,在沒有通過他人批准的狀況下都不可能作出決定,而且領導人徹底特性保證了只有擁有 C-old,new 日誌條目的服務器纔有可能被選舉爲領導人。這個時候,領導人建立一條關於 C-new 配置的日誌條目並複製給集羣就是安全的了。再者,每一個服務器在見到新的配置的時候就會當即生效。當新的配置在 C-new 的規則下被提交,舊的配置就變得可有可無,同時不使用新的配置的服務器就能夠被關閉了。如圖 11,C-old 和 C-new 沒有任何機會同時作出單方面的決定;這保證了安全性。
圖 11:一個配置切換的時間線。虛線表示已經被建立可是尚未被提交的條目,實線表示最後被提交的日誌條目。領導人首先建立了 C-old,new 的配置條目在本身的日誌中,並提交到 C-old,new 中(C-old 的大多數和 C-new 的大多數)。而後他建立 C-new 條目並提交到 C-new 中的大多數。這樣就不存在 C-new 和 C-old 能夠同時作出決定的時間點。
在關於從新配置還有三個問題須要提出。第一個問題是,新的服務器可能初始化沒有存儲任何的日誌條目。當這些服務器以這種狀態加入到集羣中,那麼他們須要一段時間來更新追趕,這時還不能提交新的日誌條目。爲了不這種可用性的間隔時間,Raft 在配置更新的時候使用了一種額外的階段,在這個階段,新的服務器以沒有投票權身份加入到集羣中來(領導人複製日誌給他們,可是不考慮他們是大多數)。一旦新的服務器追遇上了集羣中的其餘機器,從新配置能夠像上面描述的同樣處理。
第二個問題是,集羣的領導人可能不是新配置的一員。在這種狀況下,領導人就會在提交了 C-new 日誌以後退位(回到跟隨者狀態)。這意味着有這樣的一段時間,領導人管理着集羣,可是不包括他本身;他複製日誌可是不把他本身算做是大多數之一。當 C-new 被提交時,會發生領導人過渡,由於這時是最先新的配置能夠獨立工做的時間點(將老是可以在 C-new 配置下選出新的領導人)。在此以前,可能只能從 C-old 中選出領導人。
第三個問題是,移除不在 C-new 中的服務器可能會擾亂集羣。這些服務器將不會再接收到心跳,因此當選舉超時,他們就會進行新的選舉過程。他們會發送擁有新的任期號的請求投票 RPCs,這樣會致使當前的領導人回退成跟隨者狀態。新的領導人最終會被選出來,可是被移除的服務器將會再次超時,而後這個過程會再次重複,致使總體可用性大幅下降。
爲了不這個問題,當服務器確認當前領導人存在時,服務器會忽略請求投票 RPCs。特別的,當服務器在當前最小選舉超時時間內收到一個請求投票 RPC,他不會更新當前的任期號或者投出選票。這不會影響正常的選舉,每一個服務器在開始一次選舉以前,至少等待一個最小選舉超時時間。然而,這有利於避免被移除的服務器擾亂:若是領導人可以發送心跳給集羣,那麼他就不會被更大的任期號廢黜。
Raft 的日誌在正常操做中不斷的增加,可是在實際的系統中,日誌不能無限制的增加。隨着日誌不斷增加,他會佔用愈來愈多的空間,花費愈來愈多的時間來重置。若是沒有必定的機制去清除日誌裏積累的陳舊的信息,那麼會帶來可用性問題。
快照是最簡單的壓縮方法。在快照系統中,整個系統的狀態都以快照的形式寫入到穩定的持久化存儲中,而後到那個時間點以前的日誌所有丟棄。快照技術被使用在 Chubby 和 ZooKeeper 中,接下來的章節會介紹 Raft 中的快照技術。
增量壓縮的方法,例如日誌清理或者日誌結構合併樹,都是可行的。這些方法每次只對一小部分數據進行操做,這樣就分散了壓縮的負載壓力。首先,他們先選擇一個已經積累的大量已經被刪除或者被覆蓋對象的區域,而後重寫那個區域還活躍的對象,以後釋放那個區域。和簡單操做整個數據集合的快照相比,須要增長複雜的機制來實現。狀態機能夠實現 LSM tree 使用和快照相同的接口,可是日誌清除方法就須要修改 Raft 了。
圖 12:一個服務器用新的快照替換了從 1 到 5 的條目,快照值存儲了當前的狀態。快照中包含了最後的索引位置和任期號。
圖 12 展現了 Raft 中快照的基礎思想。每一個服務器獨立的建立快照,只包括已經被提交的日誌。主要的工做包括將狀態機的狀態寫入到快照中。Raft 也包含一些少許的元數據到快照中:最後被包含索引指的是被快照取代的最後的條目在日誌中的索引值(狀態機最後應用的日誌),最後被包含的任期指的是該條目的任期號。保留這些數據是爲了支持快照後緊接着的第一個條目的附加日誌請求時的一致性檢查,由於這個條目須要前一日誌條目的索引值和任期號。爲了支持集羣成員更新(第 6 節),快照中也將最後的一次配置做爲最後一個條目存下來。一旦服務器完成一次快照,他就能夠刪除最後索引位置以前的全部日誌和快照了。
儘管一般服務器都是獨立的建立快照,可是領導人必須偶爾的發送快照給一些落後的跟隨者。這一般發生在當領導人已經丟棄了下一條須要發送給跟隨者的日誌條目的時候。幸運的是這種狀況不是常規操做:一個與領導人保持同步的跟隨者一般都會有這個條目。然而一個運行很是緩慢的跟隨者或者新加入集羣的服務器(第 6 節)將不會有這個條目。這時讓這個跟隨者更新到最新的狀態的方式就是經過網絡把快照發送給他們。
安裝快照 RPC:
由領導人調用以將快照的分塊發送給跟隨者。領導者老是按順序發送分塊。
參數 | 解釋 |
---|---|
term | 領導人的任期號 |
leaderId | 領導人的 Id,以便於跟隨者重定向請求 |
lastIncludedIndex | 快照中包含的最後日誌條目的索引值 |
lastIncludedTerm | 快照中包含的最後日誌條目的任期號 |
offset | 分塊在快照中的字節偏移量 |
data[] | 原始數據 |
done | 若是這是最後一個分塊則爲 true |
結果 | 解釋 |
---|---|
term | 當前任期號(currentTerm),便於領導人更新本身 |
接收者實現:
term < currentTerm
就當即回覆圖 13:一個關於安裝快照的簡要概述。爲了便於傳輸,快照都是被分紅分塊的;每一個分塊都給了跟隨者生命的跡象,因此跟隨者能夠重置選舉超時計時器。
在這種狀況下領導人使用一種叫作安裝快照的新的 RPC 來發送快照給太落後的跟隨者;見圖 13。當跟隨者經過這種 RPC 接收到快照時,他必須本身決定對於已經存在的日誌該如何處理。一般快照會包含沒有在接收者日誌中存在的信息。在這種狀況下,跟隨者丟棄其整個日誌;它所有被快照取代,而且可能包含與快照衝突的未提交條目。若是接收到的快照是本身日誌的前面部分(因爲網絡重傳或者錯誤),那麼被快照包含的條目將會被所有刪除,可是快照後面的條目仍然有效,必須保留。
這種快照的方式背離了 Raft 的強領導人原則,由於跟隨者能夠在不知道領導人狀況下建立快照。可是咱們認爲這種背離是值得的。領導人的存在,是爲了解決在達成一致性的時候的衝突,可是在建立快照的時候,一致性已經達成,這時不存在衝突了,因此沒有領導人也是能夠的。數據依然是從領導人傳給跟隨者,只是跟隨者能夠從新組織他們的數據了。
咱們考慮過一種替代的基於領導人的快照方案,即只有領導人建立快照,而後發送給全部的跟隨者。可是這樣作有兩個缺點。第一,發送快照會浪費網絡帶寬而且延緩了快照處理的時間。每一個跟隨者都已經擁有了全部產生快照須要的信息,並且很顯然,本身從本地的狀態中建立快照比經過網絡接收別人發來的要經濟。第二,領導人的實現會更加複雜。例如,領導人須要發送快照的同時並行的將新的日誌條目發送給跟隨者,這樣纔不會阻塞新的客戶端請求。
還有兩個問題影響了快照的性能。首先,服務器必須決定何時應該建立快照。若是快照建立的過於頻繁,那麼就會浪費大量的磁盤帶寬和其餘資源;若是建立快照頻率過低,他就要承受耗盡存儲容量的風險,同時也增長了從日誌重建的時間。一個簡單的策略就是當日志大小達到一個固定大小的時候就建立一次快照。若是這個閾值設置的顯著大於指望的快照的大小,那麼快照對磁盤壓力的影響就會很小了。
第二個影響性能的問題就是寫入快照須要花費顯著的一段時間,而且咱們還不但願影響到正常操做。解決方案是經過寫時複製的技術,這樣新的更新就能夠被接收而不影響到快照。例如,具備函數式數據結構的狀態機自然支持這樣的功能。另外,操做系統的寫時複製技術的支持(如 Linux 上的 fork)能夠被用來建立完整的狀態機的內存快照(咱們的實現就是這樣的)。
這一節將介紹客戶端是如何和 Raft 進行交互的,包括客戶端如何發現領導人和 Raft 是如何支持線性化語義的。這些問題對於全部基於一致性的系統都存在,而且 Raft 的解決方案和其餘的也差很少。
Raft 中的客戶端發送全部請求給領導人。當客戶端啓動的時候,他會隨機挑選一個服務器進行通訊。若是客戶端第一次挑選的服務器不是領導人,那麼那個服務器會拒絕客戶端的請求而且提供他最近接收到的領導人的信息(附加條目請求包含了領導人的網絡地址)。若是領導人已經崩潰了,那麼客戶端的請求就會超時;客戶端以後會再次重試隨機挑選服務器的過程。
咱們 Raft 的目標是要實現線性化語義(每一次操做當即執行,只執行一次,在他調用和收到回覆之間)。可是,如上述,Raft 是能夠執行同一條命令屢次的:例如,若是領導人在提交了這條日誌以後,可是在響應客戶端以前崩潰了,那麼客戶端會和新的領導人重試這條指令,致使這條命令就被再次執行了。解決方案就是客戶端對於每一條指令都賦予一個惟一的序列號。而後,狀態機跟蹤每條指令最新的序列號和相應的響應。若是接收到一條指令,它的序列號已經被執行了,那麼就當即返回結果,而不從新執行指令。
只讀的操做能夠直接處理而不須要記錄日誌。可是,在不增長任何限制的狀況下,這麼作可能會冒着返回髒數據的風險,由於領導人響應客戶端請求時可能已經被新的領導人做廢了,可是他還不知道。線性化的讀操做必須不能返回髒數據,Raft 須要使用兩個額外的措施在不使用日誌的狀況下保證這一點。首先,領導人必須有關於被提交日誌的最新信息。領導人徹底特性保證了領導人必定擁有全部已經被提交的日誌條目,可是在他任期開始的時候,他可能不知道那些是已經被提交的。爲了知道這些信息,他須要在他的任期裏提交一條日誌條目。Raft 中經過領導人在任期開始的時候提交一個空白的沒有任何操做的日誌條目到日誌中去來實現。第二,領導人在處理只讀的請求以前必須檢查本身是否已經被廢黜了(他本身的信息已經變髒了若是一個更新的領導人被選舉出來)。Raft 中經過讓領導人在響應只讀請求以前,先和集羣中的大多數節點交換一次心跳信息來處理這個問題。可選的,領導人能夠依賴心跳機制來實現一種租約的機制,可是這種方法依賴時間來保證安全性(假設時間偏差是有界的)。
咱們已經爲 RAMCloud 實現了 Raft 算法做爲存儲配置信息的複製狀態機的一部分,而且幫助 RAMCloud 協調故障轉移。這個 Raft 實現包含大約 2000 行 C++ 代碼,其中不包括測試、註釋和空行。這些代碼是開源的。同時也有大約 25 個其餘獨立的第三方的基於這篇論文草稿的開源實現,針對不一樣的開發場景。同時,不少公司已經部署了基於 Raft 的系統。
這一節會從三個方面來評估 Raft 算法:可理解性、正確性和性能。
爲了和 Paxos 比較 Raft 算法的可理解能力,咱們針對高層次的本科生和研究生,在斯坦福大學的高級操做系統課程和加州大學伯克利分校的分佈式計算課程上,進行了一次學習的實驗。咱們分別拍了針對 Raft 和 Paxos 的視頻課程,並準備了相應的小測驗。Raft 的視頻講課覆蓋了這篇論文的全部內容除了日誌壓縮;Paxos 講課包含了足夠的資料來建立一個等價的複製狀態機,包括單決策 Paxos,多決策 Paxos,從新配置和一些實際系統須要的性能優化(例如領導人選舉)。小測驗測試一些對算法的基本理解和解釋一些邊角的示例。每一個學生都是看完第一個視頻,回答相應的測試,再看第二個視頻,回答相應的測試。大約有一半的學生先進行 Paxos 部分,而後另外一半先進行 Raft 部分,這是爲了說明二者從第一部分的算法學習中得到的表現和經驗的差別。咱們計算參加人員的每個小測驗的得分來看參與者是否在 Raft 算法上更加容易理解。
咱們儘量的使得 Paxos 和 Raft 的比較更加公平。這個實驗偏心 Paxos 表如今兩個方面:43 個參加者中有 15 我的在以前有一些 Paxos 的經驗,而且 Paxos 的視頻要長 14%。如表格 1 總結的那樣,咱們採起了一些措施來減輕這種潛在的偏見。咱們全部的材料均可供審查。
關心 | 緩和偏見採起的手段 | 可供查看的材料 |
---|---|---|
相同的講課質量 | 二者使用同一個講師。Paxos 使用的是如今不少大學裏常用的。Paxos 會長 14%。 | 視頻 |
相同的測驗難度 | 問題以難度分組,在兩個測驗裏成對出現。 | 小測驗 |
公平評分 | 使用評價量規。隨機順序打分,兩個測驗交替進行。 | 評價量規(rubric) |
表 1:考慮到可能會存在的偏見,對於每種狀況的解決方法,和相應的材料。
參加者平均在 Raft 的測驗中比 Paxos 高 4.9 分(總分 60,那麼 Raft 的平均得分是 25.7,而 Paxos 是 20.8);圖 14 展現了每一個參與者的得分。配置t-檢驗(又稱student‘s t-test)代表,在 95% 的可信度下,真實的 Raft 分數分佈至少比 Paxos 高 2.5 分。
圖 14:一個散點圖表示了 43 個學生在 Paxos 和 Raft 的小測驗中的成績。在對角線之上的點表示在 Raft 得到了更高分數的學生。
咱們也創建了一個線性迴歸模型來預測一個新的學生的測驗成績,基於如下三個因素:他們使用的是哪一個小測驗,以前對 Paxos 的經驗,和學習算法的順序。模型預測,對小測驗的選擇會產生 12.5 分的差異。這顯著的高於以前的 4.9 分,由於不少學生在以前都已經有了對於 Paxos 的經驗,這至關明顯的幫助 Paxos,對 Raft 就沒什麼太大影響了。可是奇怪的是,模型預測對於先進行 Paxos 小測驗的人而言,Raft的得分低了6.3分; 雖然咱們不知道爲何,這彷佛在統計上是有意義的。
咱們同時也在測驗以後調查了參與者,他們認爲哪一個算法更加容易實現和解釋;這個的結果在圖 15 上。壓倒性的結果代表 Raft 算法更加容易實現和解釋(41 人中的 33個)。可是,這種本身報告的結果不如參與者的成績更加可信,而且參與者可能由於咱們的 Raft 更加易於理解的假說而產生偏見。
圖 15:經過一個 5 分制的問題,參與者(左邊)被問哪一個算法他們以爲在一個高效正確的系統裏更容易實現,右邊被問哪一個更容易向學生解釋。
關於 Raft 用戶學習有一個更加詳細的討論。
在第 5 節,咱們已經制定了正式的規範,和對一致性機制的安全性證實。這個正式規範使用 TLA+ 規範語言使圖 2 中總結的信息很是清晰。它長約400行,並做爲證實的主題。同時對於任何想實現 Raft 的人也是十分有用的。咱們經過 TLA 證實系統很是機械的證實了日誌徹底特性。然而,這個證實依賴的約束前提尚未被機械證實(例如,咱們尚未證實規範的類型安全)。並且,咱們已經寫了一個非正式的證實關於狀態機安全性是完備的,而且是至關清晰的(大約 3500 個詞)。
Raft 和其餘一致性算法例如 Paxos 有着差很少的性能。在性能方面,最重要的關注點是,當領導人被選舉成功時,何時複製新的日誌條目。Raft 經過不多數量的消息包(一輪從領導人到集羣大多數機器的消息)就達成了這個目的。同時,進一步提高 Raft 的性能也是可行的。例如,很容易經過支持批量操做和管道操做來提升吞吐量和下降延遲。對於其餘一致性算法已經提出過不少性能優化方案;其中有不少也能夠應用到 Raft 中來,可是咱們暫時把這個問題放到將來的工做中去。
咱們使用咱們本身的 Raft 實現來衡量 Raft 領導人選舉的性能而且回答兩個問題。首先,領導人選舉的過程收斂是否快速?第二,在領導人宕機以後,最小的系統宕機時間是多久?
圖 16:發現並替換一個已經崩潰的領導人的時間。上面的圖考察了在選舉超時時間上的隨機化程度,下面的圖考察了最小選舉超時時間。每條線表明了 1000 次實驗(除了 150-150 毫秒只試了 100 次),和相應的肯定的選舉超時時間。例如,150-155 毫秒意思是,選舉超時時間從這個區間範圍內隨機選擇並肯定下來。這個實驗在一個擁有 5 個節點的集羣上進行,其廣播時延大約是 15 毫秒。對於 9 個節點的集羣,結果也差很少。
爲了衡量領導人選舉,咱們反覆的使一個擁有五個節點的服務器集羣的領導人宕機,並計算須要多久才能發現領導人已經宕機並選出一個新的領導人(見圖 16)。爲了構建一個最壞的場景,在每一的嘗試裏,服務器都有不一樣長度的日誌,意味着有些候選人是沒有成爲領導人的資格的。另外,爲了促成選票瓜分的狀況,咱們的測試腳本在終止領導人以前同步的發送了一次心跳廣播(這大約和領導人在崩潰前複製一個新的日誌給其餘機器很像)。領導人均勻的隨機的在心跳間隔裏宕機,也就是最小選舉超時時間的一半。所以,最小宕機時間大約就是最小選舉超時時間的一半。
圖 16 中上面的圖代表,只須要在選舉超時時間上使用不多的隨機化就能夠大大避免選票被瓜分的狀況。在沒有隨機化的狀況下,在咱們的測試裏,選舉過程每每都須要花費超過 10 秒鐘因爲太多的選票瓜分的狀況。僅僅增長 5 毫秒的隨機化時間,就大大的改善了選舉過程,如今平均的宕機時間只有 287 毫秒。增長更多的隨機化時間能夠大大改善最壞狀況:經過增長 50 毫秒的隨機化時間,最壞的完成狀況(1000 次嘗試)只要 513 毫秒。
圖 16 中下面的圖顯示,經過減小選舉超時時間能夠減小系統的宕機時間。在選舉超時時間爲 12-24 毫秒的狀況下,只須要平均 35 毫秒就能夠選舉出新的領導人(最長的一次花費了 152 毫秒)。然而,進一步下降選舉超時時間的話就會違反 Raft 的時間不等式需求:在選舉新領導人以前,領導人就很難發送完心跳包。這會致使沒有意義的領導人改變並下降了系統總體的可用性。咱們建議使用更爲保守的選舉超時時間,好比 150-300 毫秒;這樣的時間不大可能致使沒有意義的領導人改變,並且依然提供不錯的可用性。
已經有不少關於一致性算法的工做被髮表出來,其中不少均可以歸到下面的類別中:
Raft 和 Paxos 最大的不一樣之處就在於 Raft 的強領導特性:Raft 使用領導人選舉做爲一致性協議裏必不可少的部分,而且將盡量多的功能集中到了領導人身上。這樣就能夠使得算法更加容易理解。例如,在 Paxos 中,領導人選舉和基本的一致性協議是正交的:領導人選舉僅僅是性能優化的手段,並且不是一致性所必需要求的。可是,這樣就增長了多餘的機制:Paxos 同時包含了針對基本一致性要求的兩階段提交協議和針對領導人選舉的獨立的機制。相比較而言,Raft 就直接將領導人選舉歸入到一致性算法中,並做爲兩階段一致性的第一步。這樣就減小了不少機制。
像 Raft 同樣,VR 和 ZooKeeper 也是基於領導人的,所以他們也擁有一些 Raft 的優勢。可是,Raft 比 VR 和 ZooKeeper 擁有更少的機制由於 Raft 儘量的減小了非領導人的功能。例如,Raft 中日誌條目都遵循着從領導人發送給其餘人這一個方向:附加條目 RPC 是向外發送的。在 VR 中,日誌條目的流動是雙向的(領導人能夠在選舉過程當中接收日誌);這就致使了額外的機制和複雜性。根據 ZooKeeper 公開的資料看,它的日誌條目也是雙向傳輸的,可是它的實現更像 Raft。
和上述咱們說起的其餘基於一致性的日誌複製算法中,Raft 的消息類型更少。例如,咱們數了一下 VR 和 ZooKeeper 使用的用來基本一致性須要和成員改變的消息數(排除了日誌壓縮和客戶端交互,由於這些都比較獨立且和算法關係不大)。VR 和 ZooKeeper 都分別定義了 10 中不一樣的消息類型,相對的,Raft 只有 4 中消息類型(兩種 RPC 請求和對應的響應)。Raft 的消息都稍微比其餘算法的要信息量大,可是都很簡單。另外,VR 和 ZooKeeper 都在領導人改變時傳輸了整個日誌;因此爲了可以實踐中使用,額外的消息類型就很必要了。
Raft 的強領導人模型簡化了整個算法,可是同時也排斥了一些性能優化的方法。例如,平等主義 Paxos (EPaxos)在某些沒有領導人的狀況下能夠達到很高的性能。平等主義 Paxos 充分發揮了在狀態機指令中的交換性。任何服務器均可以在一輪通訊下就提交指令,除非其餘指令同時被提出了。然而,若是指令都是併發的被提出,而且互相之間不通訊溝通,那麼 EPaxos 就須要額外的一輪通訊。由於任何服務器均可以提交指令,因此 EPaxos 在服務器之間的負載均衡作的很好,而且很容易在 WAN 網絡環境下得到很低的延遲。可是,他在 Paxos 上增長了很是明顯的複雜性。
一些集羣成員變換的方法已經被提出或者在其餘的工做中被實現,包括 Lamport 的原始的討論,VR 和 SMART。咱們選擇使用共同一致的方法由於他對一致性協議的其餘部分影響很小,這樣咱們只須要不多的一些機制就能夠實現成員變換。Lamport 的基於 α 的方法之因此沒有被 Raft 選擇是由於它假設在沒有領導人的狀況下也能夠達到一致性。和 VR 和 SMART 相比較,Raft 的從新配置算法能夠在不限制正常請求處理的狀況下進行;相比較的,VR 須要中止全部的處理過程,SMART 引入了一個和 α 相似的方法,限制了請求處理的數量。Raft 的方法同時也須要更少的額外機制來實現,和 VR、SMART 比較而言。
算法的設計一般會把正確性,效率或者簡潔做爲主要的目標。儘管這些都是頗有意義的目標,可是咱們相信,可理解性也是同樣的重要。在開發者把算法應用到實際的系統中以前,這些目標沒有一個會被實現,這些都會必然的偏離發表時的形式。除非開發人員對這個算法有着很深的理解而且有着直觀的感受,不然將會對他們而言很難在實現的時候保持原有指望的特性。
在這篇論文中,咱們嘗試解決分佈式一致性問題,可是一個廣爲接受可是十分使人費解的算法 Paxos 已經困擾了無數學生和開發者不少年了。咱們創造了一種新的算法 Raft,顯而易見的比 Paxos 要容易理解。咱們同時也相信,Raft 也能夠爲實際的實現提供堅實的基礎。把可理解性做爲設計的目標改變了咱們設計 Raft 的方式;隨着設計的進展,咱們發現本身重複使用了一些技術,好比分解問題和簡化狀態空間。這些技術不只提高了 Raft 的可理解性,同時也使咱們堅信其正確性。
這項研究必須感謝如下人員的支持:Ali Ghodsi,David Mazie`res,和伯克利 CS 294-91 課程、斯坦福 CS 240 課程的學生。Scott Klemmer 幫咱們設計了用戶調查,Nelson Ray 建議咱們進行統計學的分析。在用戶調查時使用的關於 Paxos 的幻燈片很大一部分是從 Lorenzo Alvisi 的幻燈片上借鑑過來的。特別的,很是感謝 DavidMazieres 和 Ezra Hoch,他們找到了 Raft 中一些難以發現的漏洞。許多人提供了關於這篇論文十分有用的反饋和用戶調查材料,包括 Ed Bugnion,Michael Chan,Hugues Evrard,Daniel Giffin,Arjun Gopalan,Jon Howell,Vimalkumar Jeyakumar,Ankita Kejriwal,Aleksandar Kracun,Amit Levy,Joel Martin,Satoshi Matsushita,Oleg Pesok,David Ramos,Robbert van Renesse,Mendel Rosenblum,Nicolas Schiper,Deian Stefan,Andrew Stone,Ryan Stutsman,David Terei,Stephen Yang,Matei Zaharia 以及 24 位匿名的會議審查人員(可能有重複),而且特別感謝咱們的領導人 Eddie Kohler。Werner Vogels 發了一條早期草稿連接的推特,給 Raft 帶來了極大的關注。咱們的工做由 Gigascale 系統研究中心和 Multiscale 系統研究中心給予支持,這兩個研究中心由關注中心研究程序資金支持,一個是半導體研究公司的程序,由 STARnet 支持,一個半導體研究公司的程序由 MARCO 和 DARPA 支持,在國家科學基金會的 0963859 號批准,而且得到了來自 Facebook,Google,Mellanox,NEC,NetApp,SAP 和 Samsung 的支持。Diego Ongaro 由 Junglee 公司,斯坦福的畢業團體支持。
本文經TopJohn受權轉自TopJohn's Blog
深刻淺出區塊鏈 - 系統學習區塊鏈,打造最好的區塊鏈技術博客。