Paxos Made Simple

1. 簡介

用於實現高容錯性分佈式系統的Paxos算法,一直以來老是被認爲是難以理解的,或許是由於對不少人來講,初始版本就像是」希臘語"同樣(最初的論文是以希臘故事展開的形式)[5]。實際上,它也算是最淺顯易見的分佈式算法之一了。它的核心就是一個一致性算法——論文[5]中的「synod」算法。在下一個章節能夠看到,它基本上是根據一個一致性算法所必需知足的條件天然而然地推斷出來的。最後一個章節,咱們經過將Paxos算法做爲構建一個實現了狀態機的分佈式系統的一致性實現,來完整地描述它。這種使用狀態機方法的論文[4]應該早已廣爲人知,由於它可能已是分佈式系統理論研究領域被引用最普遍的了。算法

2. 一致性算法

2.1 問題描述

假設有一組能夠提出提案的進程集合。一個一致性算法須要保證:
在這些被提出的提案中,只有一個會被選定。
若是沒有提案被提出,則不會有被選定的提案。
當一個提案被選定後,進程應該能獲取被選定提案的信息。安全

對於一致來講,安全性(Safety)需求是這樣的:
只有被提出的提案才能被選定。
只能有一個值被選中(chosen),同時
進程不能認爲某個提案被選定,除非它真的是被選定的那個。性能優化

咱們不會嘗試去精確地描述活性(Liveness)需求。可是從整體上看,最終的目標是保證有一個提案被選定,而且當提案被選定後,進程最終也能獲取到被選定提案的信息。
一個分佈式算法,有兩個重要的屬性:Safety和Liveness,簡單來講:
Safety是指那些須要保證永遠都不會發生的事情
Liveness是指那些最終必定會發生的事情服務器

在這個一致性算法中,有三個參與角色,咱們分別用Proposer,Acceptor和Learner來表示。在具體實現中,一個進程可能充當不止一種角色,可是在這裏咱們並不關心它們之間的映射關係。網絡

假設不一樣的參與者之間能夠經過發消息來進行通訊,咱們使用普通的非拜占庭模式的異步模型:
每一個參與者以任意的速度運行,可能會因中止而執行失敗,也可能會重啓。當一個提案被選定後,全部的參與者都有可能失敗而後重啓,除非這些參與者能夠記錄某些信息,不然是不可能存在一個解法的。
消息在傳輸中可能花費任意時間,可能會重複,也可能丟失,但不會被損壞(不會被篡改,即不會發生拜占庭問題)。異步

2.2 提案的選定

選定提案最簡單的方式就是隻有一個Acceptor存在。Proposer發送提案給Acceptor,Acceptor會選擇它接收到的第一個提案做爲被選提案。雖然簡單,這個解決方案卻很難讓人滿意,由於當Acceptor出錯時,整個系統就沒法工做了。分佈式

所以,咱們應該選擇其餘方式來選定提案,好比能夠用多個Acceptor來避免一個Acceptor的單點問題。這樣的話,Proposer向一個Acceptor集合發送提案,某個Acceptor可能會經過(accept)這個提案。當有足夠多的Acceptor經過它時,咱們就認爲這個提案被選定了。那麼怎樣纔算是足夠多呢?爲了確保只一個提案被選定,咱們可讓這個集合大到包含了Acceptor集合中的多數成員。由於任意兩個多數集(majority)至少包含一個公共成員,若是咱們再規定一個Acceptor只能經過一個提案,那麼就能保證只有一個提案被選定(這是不少論文都研究過的多數集的一個普通應用[3])。oop

假設沒有失敗和消息丟失的狀況,若是咱們但願在每一個Proposer只能提出一個提案的前提下仍然能夠選出一個提案來,這就意味着以下需求:性能

P1. 一個Acceptor必須經過它收到的第一個提案。

可是這個需求會引起另外的問題。若是有多個提案被不一樣的Proposer同時提出,這會致使雖然每一個Acceptor都經過了一個提案,可是沒有一個提案是由多數人經過的。甚至即便只有兩個提案被提出,若是每一個都被差很少一半的Acceptor經過了,哪怕只有一個Acceptor出錯均可能致使沒法肯定該選定哪一個提案。
好比有5個Acceptor,其中2個經過了提案a,另外3個經過了提案b,此時若是經過提案b的3個當中有一個出錯了,那麼a和b的經過數都爲2, 這樣就沒法肯定了。優化

P1再加一個提案被選定須要由半數以上Acceptor經過的這個需求,暗示着一個Acceptor必需要能經過不止一個提案。咱們爲每一個提案分配一個編號來記錄一個Acceptor經過的那些提案,因而一個提案就包含一個提案編號以及它的value值。爲了不形成混淆,須要保證不一樣的提案具備不一樣編號。如何實現這個功能依賴於具體的實現細節,在這裏咱們假設已經實現了這種保證。當一個具備value值的提案被多數Acceptor經過後,咱們就認爲該value被選定了。同時咱們也認爲該提案被選定了。

咱們容許多個提案被選定,可是咱們必須保證全部被選定的提案具備相同的值value。經過對提案編號的約定,它須要知足如下保證:
P2. 若是具備value值v的提案被選定了,那麼全部比它編號高的提案的value值也必須是v。

由於編號是徹底有序的,因此條件P2就保證了只有一個value值被選定這一關鍵安全性屬性。

一個提案能被選定,必需要被至少一個Acceptor經過,因此咱們能夠經過知足以下條件來知足P2:

P2a. 若是一個具備value值v的提案被選定了,那麼被Acceptor經過的全部編號比它高的提案的value值也必須是v。

咱們仍然須要P1來保證有提案會被選定。由於通訊是異步的,一個提案可能會在某個Acceptor c還沒收到任何提案時就被選定了。假設有個新的Proposer甦醒了,而後提出了一個具備不一樣value值的更高編號的提案,根據P1, 須要c經過這個提案,但這是與P2a相矛盾的。所以爲了同時知足P1和P2a,須要對P2a進行強化:

P2b. 若是具備value值v的提案被選定了,那麼全部比它編號更高的被Proposer提出的提案的value值也必須是v。

一個提案被Acceptor經過以前確定是由某個Proposer提出,所以P2b就隱含P2a,進而隱含了P2.

爲了發現如何保證P2b,咱們來看看如何證實它成立。咱們假設某個具備編號m和value值v的提案被選定了,須要證實任意具備編號n(n > m)的提案都具備value值v。咱們能夠經過對n使用數學概括法來簡化證實,這樣咱們能夠在額外的假設下——即編號在m..(n-1)之間的提案具備value值v,來證實編號爲n的提案具備value值v,其中i..j表示從i到j的集合。由於編號爲m的提案已經被選定了,這就意味着存在一個多數Acceptor組成的集合C,C中的每一個成員都經過了這個提案。結合概括的假設,m被選定意味着:

C中的每個Acceptor都經過了一個編號在m..(n-1)之間的提案,而且每一個編號在m..(n-1)之間的被Acceptor經過的提案都具備value值v。

因爲任何包含多數Acceptor的集合S都至少包含一個C中的成員,咱們能夠經過保持以下不變性來確保編號爲n的提案具備value值v:

P2c. 對於任意v和n,若是一個編號爲n,value值爲v的提案被提出,那麼確定存在一個由多數Acceptor組成的集合S知足如下條件中的一個:
    a. S中不存在任何Acceptor經過了編號小於n的提案
    b. v是S中全部Acceptor已經經過的編號小於n的具備最大編號的提案的value值。

經過維護P2c的不變性咱們就能夠知足P2b的條件了。

爲了維護P2c的不變性,一個Proposer在提出編號爲n的提案時,若是存在一個將要或者已經被多數Acceptor經過的編號小於n的最大編號提案,Proposer須要知道它的信息。獲取那些已經被經過的提案很簡單,可是預測將來會被經過的卻很困難。爲了不去預測將來,Proposer經過提出承諾不會有那樣的經過狀況來控制它。換句話說,Proposer會請求那些Acceptor不要再經過任何編號小於n的提案了。這就致使了以下的提案生成算法:
Proposer選擇一個新的提案編號n,而後向某個Acceptor集合中的成員發送請示,要求它做出以下回應:

(a)保證再也不經過任何編號小於n的提案。
    (b)當前它已經經過的編號小於n的最大編號提案,如何存在的話。
 咱們把這樣的請求稱爲編號爲n的prepare請求。

若是Proposer收到來自集合中多數成員的響應結果,那麼它能夠提出編號爲n,value值爲v的提案,這裏v是全部響應中最大編號提案的value值,若是響應中不包含任何提案,那麼這個值就由Proposer自由決定。

Proposer經過向某個Acceptor集合發送須要被經過的提案請求來產生一個提案(這裏的Acceptor集合不必定是響應前一個請求的集合)。這們把這個叫作accept請求。

目前咱們描述了Proposer端的算法。那麼Acceptor端是怎樣的呢?它可能會收到來自Proposer端的兩種請求:prepare請求和accept請求。Acceptor能夠忽略任意請求而不用擔憂破壞算法的安全性。所以咱們只須要說明它在什麼狀況下能夠對一個請求做出響應。它能夠在任什麼時候候響應prepare請求也能夠在不違反現有承諾的狀況下響應accept請求。換句話說:

P1a. 一個Acceptor能夠經過一個編號爲n的提案,只要它還未響應任何編號大於n的prepare請求。

能夠看出P1a包含了P1。

如今咱們就得到了一個知足安全性需求的提案選定算法——假設在提案編號惟一的前提下。只要再作點小優化,就能獲得最終的算法了。
假設一個Acceptor收到了一個編號爲n的prepare請求,可是它已經對編號大於n的prepare請求做出了響應,所以它確定不會再經過任何新的編號爲n的提案。那麼它就沒有必要對這個請求做出響應,由於它確定不會經過編號爲n的提案,因而咱們會讓Acceptor忽略這樣的prepare請求,咱們也會讓它忽略那些它已經經過的提案的prepare請求。
經過這個優化,Acceptor只須要記住它已經經過的提案的最大編號以及它已經響應過prepare請求的提案的最大編號。由於必需要在出錯的狀況下也保證P2c的不變性,因此Acceptor要在故障和重啓的狀況下也能記住這些信息。Proposer能夠隨時丟棄提案以及它的全部信息——只要它能夠保證不會提出具備相同編號的提案便可。

把Proposer和Acceptor的行爲結合起來,咱們就能獲得算法的以下兩階段執行過程:
Phase 1:
Proposer選擇一個提案編號n,而後向Acceptor的多數集發送編號爲n的prepare請求。
若是一個Acceptor收到一個編號爲n的prepare請示,且n大於它全部已響應請求的編號,那麼它就會保證不會再經過任意編號小於n的提案,同時將它已經經過的最大編號提案(若是存在的話)一併做爲響應。
Phase 2:
若是Proposer收到多數Acceptor對它prepare請求(編號爲n)的響應,那麼它就會發送一個編號爲n,value值爲v的提案的accept請求給每一個Acceptor,這裏v是收到的響應中最大編號提案的值,若是響應中不包含任何提案,那麼它就能夠是任意值。
若是Acceptor收到一個編號爲n的提案的accept請求,只要它還未對編號大於n的prepare做出響應,它就能夠經過這個提案。

一個Proposer能夠提出多個提案,只要它能遵循以上算法約定。它能夠在任意時刻丟棄某個提案(即便針對該提案的請求或響應在提案丟棄後好久纔到達,正確性依然能夠保證)。若是Proposer已經在嘗試提交更大編號的提案,那麼丟棄也何嘗不是一件好事。所以,若是一個Acceptor由於已經收到更高編號的prepare請求而忽略某個prepare或者accept請求,它應該通知對應的Proposer,而後該Proposer能夠丟棄這個提案。這是一個不影響正確性的性能優化。

2.3 獲取被選定的提案值

爲了獲取被選定的值,一個Learner必需要能知道一個提案已經被多數Acceptor經過了。最直觀的算法是,讓每一個Acceptor在經過一個提案時就通知全部Learner,把經過的提案告知它們。這可讓Learner儘快找到被選定的值,但這須要每一個Acceptor和Learner之間互相通訊——通訊次數等於兩者數量的乘積。

在假設非拜占庭錯誤的前提下,一個Learner能夠很容易地經過另外一個Learner瞭解一個值已經被選定了。咱們可讓全部Acceptor將它們的經過信息發送給一個特定的Learner,當一個value被選定時,由它來通知其餘Learner。這種方法須要額外一個步驟才能通知到全部Learner,並且它也不是可靠的,由於那個特定的Learner可能會發生一些故障。可是這種狀況下的通訊次數,只須要兩者數量之和。

更通常地,Acceptor能夠將它們的經過信息發送給一個特寫的Learner集合,它們中的任何一個均可以在某個value被選定後通知全部Learner。這個集合中的Learner越多,可靠性就越好,通訊複雜度也相應更高。

由於消息可能會丟失,一個value被選定後,可能沒有Learner會發現。Learner能夠向Acceptor詢問它們經過了哪些提案,可是任一Acceptor出錯,都有可能致使沒法分辨是否有多數Acceptor經過了某個提案。在這種狀況下,只有當一個新的提案被選定時,Learner才能發現被選定的value。若是一個Learner想知道是否已經選定一個value,它可讓Proposer利用上面的算法提出一個提案。

2.4 進展性

很容易能夠構造出這樣一種狀況,兩個Proposer持續地提出序號遞增的提案,可是沒有提案會被選定。Proposer p爲編號爲n1的提案完成Phase 1, 而後另外一個Proposer q爲編號爲n2(n2>n1)的提案完成Phase 1。Proposer p對於編號n1的Phase 2的accept請求會被忽略,由於Acceptor承諾再也不經過任何編號小於n2的提案。這樣Proposer p就會用一個新的編號n3(n3>n2)從新開始並完成Phase 1,這又致使了Proposer q對於Phase 2的accept請求被忽略,如此往復。

爲了保證進度,必須選擇一個特定的Proposer做爲惟一的提案提出者。若是這個Proposer能夠和多數Acceptor進行通訊,而且可使用比已用編號更大的編號來進行提案的話,那麼它提出的提案就能夠成功被經過。若是知道有某些編號更高的請求,它能夠經過捨棄當前的提案並從新開始,這個Proposer最終必定會選到一個足夠大的提案編號。

若是系統中有足夠的組件(Proposer, Acceptor以及網絡通訊)工做良好,經過選舉一個特定的Proposer,活性就可以達到。著名的FLP理論[1]指出,一個可靠的Proposer選舉算法要麼利用隨時性要麼利用實時性來實現——好比使用超時機制。然而不管選舉是否成功,安全性均可以保證。

2.5 實現

Paxos算法[5]假設了一組進程網絡。在它的一致性算法中,每一個進程都扮演着Proposer, Acceptor以及Learner的角色。該算法選擇了一個Leader來扮演那個特定的Proposer和Learner。Paxos一致性算法就是上面描述的那個,請求和響應都以普通消息的方式發送(響應消息經過對應的提案編號來標識以免混淆)。使用可靠的存儲設備存儲Acceptor須要記住的信息來防止出錯。Acceptor在真正發送響應以前,會將它記錄到可靠的存儲設備中。

剩下的就是描述若是保證不會用到重複編號的機制了。不一樣的Proposer從不相交的編號集合中選擇本身的編號,這樣任何兩個Proposer就不會用到相同的編號了。每一個Proposer都記錄(在可靠存儲設備中)它使用過的最大編號,而後用比這更大編號的提案開始Phase 1。

3. 狀態機實現

有一種實現分佈式系統的簡單方式,就是使用一組客戶端集合向中央服務器發送命令。服務器能夠當作一個以某種順序執行客戶端命令的肯定性狀態機。這個狀態機有個當前狀態,經過接收一個命令看成輸入來產生一個輸出和新狀態。好比,分佈式銀行系統的客戶端多是一些出納員,狀態機的狀態則由全部用戶的帳戶餘額組成。一個取款操做,經過執行一個減小帳戶餘額的狀態機命令(當且僅當餘額大於取款數目時)實現,而後將新舊餘額做爲輸出。

使用單點中央服務器的系統在該服務器故障的狀況下,整個系統都將運行失敗。所以咱們用一組服務器來代替它,每一個服務器都獨立實現了該狀態機。由於這個狀態機是肯定性的,若是全部服務器都以一樣的順序執行命令,那麼它們將產生相同的狀態機狀態和輸出。一個提出命令的客戶端,可使用任意服務器爲它產生的輸出。

爲了保證全部服務器都能執行相同的狀態機命令序列,咱們須要實現一系列獨立的Paxos一致性算法實例,第i個實例選定的值做爲序列中的第i個狀態機命令。在算法的每一個實例中,每一個服務器擔任全部角色(Proposer,Acceptor和Learner)。如今,咱們假設服務器的集合是固定的,這樣全部的一致性算法實例都具備相同的參與者集合。

在正常執行中,一個服務器被選舉成爲Leader,它會在全部一致性算法實例當中扮演特定的Proposer(惟一的提案提出者)。客戶端給Leader發送命令,它來決定每條命令出如今序列當中的位置。若是Leader決定某個客戶端命令應該是第135個,它會嘗試讓該命令成爲第135個一致性算法實例選定的value值。這一般都會成功,可是在一些故障或者有另外的服務器也認爲本身是Leader而且對第135個命令持有異議時,它可能會失敗。可是一致性算法能夠保證,最多隻有一條命令會被選定爲第135條。

這個方法的關鍵在於,在Paxos一致性算法中,被提出的value值只在Phase 2纔會被選定。回憶一下,在Proposer完成Phase 1時,要麼提案的value值被肯定了,要麼Proposer能夠自由提出任意值。

咱們如今描述了Paxos狀態機實現是怎樣在正常狀況下運行的,接下來咱們看看會有哪些出錯的狀況,看下以前的Leader故障以及新的Leader被選舉出來後會發生什麼(系統啓動是一種特殊狀況,此時尚未命令被提出)。

新的Leader被選舉出來後,首先要成爲全部一致性算法實例的Learner,須要知道目前已經選定的大部分命令。假設它知道命令1-134,138以及139——也就是一致性算法實例1-134,138以及139選定的值(後面咱們會看到這樣的命令缺口是如何產生的)。接下來它會執行135-137以及139之後的算法實例的Phase 1(下面會描述如何來作)。假設執行結果代表,實例135和140的提案值已被肯定,可是其餘執行實例的提案值是沒有限制的。那麼Leader能夠執行實例135和140的Phase 2,進而選定第135和140條命令。

Leader以及其餘已經獲取Leader全部已知命令的服務器,如今能夠執行命令1-135。然而它還不能執行命令138-140,由於命令136和137還未被選定。Leader能夠將接下來兩條客戶端請求的命令看成命令136和137。同時咱們也能夠提出一個特殊的「noop」指令來當即填補這個空缺但保持狀態不變(經過執行一致性算法實例136和137的Phase 2來完成)。一旦該no-op指令被選定,命令138-140就能夠被執行了。

命令1-140目前已經被選定了。Leader也已經完成了全部大於140的一致性算法實例的Phase 1,並且它能夠在Phase 2中自由地爲這些實例指定任意值。它爲下一個從客戶端接收的命令分配序號141, 並在Phase 2中將它做爲第141個一致性算法實例的value值。它將接收到的下一個客戶端命令做爲命令142, 並以此類推。

Leader能夠在它提出的命令141被選定前提出命令142。它發送的關於命令141的提案信息可能所有丟失,所以在全部其餘服務器獲知Leader選定的命令141以前,命令142就可能已被選定。當Leader沒法收到實例141的Phase 2的指望迴應時,它會重傳這些信息。若是一切順利的話,它的提案命令將被選定。可是仍然可能會失敗,形成在選定的命令序列中出現缺口。通常來講,假設Leader能夠提早肯定a個命令,這意味着命令i被選定以後,它就能夠提出i+1到i+a的命令了。這樣就可能造成長達a-1的命令缺口。

一個新選定的Leader須要爲無數個一致性算法實例執行Phase 1——在上面的場景中,就是135-137以及全部大於139的執行實例。經過向其餘服務器發送一條合適的消息,就可讓全部執行實例使用同一個提案編號(計數器)。在Phase 1中,只要一個Acceptor已經收到來自某Proposer的Phase 2消息,那麼它就能夠爲不止一個實例做出經過迴應(在上面的場景中,就是針對135和140的狀況)。所以一個服務器(做爲Acceptor時)能夠用一條適當的短消息對全部實例做出迴應。執行這樣無限多的實例的Phase 1也不會有問題。
這裏應該是指穩定的Paxos模型,Phase 1能夠被省略,只要編號計數器是惟一的。

因爲Leader的故障和新Leader的選舉是不多見的狀況,那麼執行一條狀態機命令的主要開銷,即在命令值上達成一致性的開銷,就是執行一致性算法中Phase 2的開銷。能夠證實,在容許失效的狀況下,Paxos一致性算法的Phase 2在全部一致性算法中具備最小可能的時間複雜度[2]。所以Paxos算法基本上是最優的。

在系統正常運行的狀況下,咱們假設老是隻有一個Leader,只有在當前Leader故障及選舉出新Leader之間的短期內纔會違背這個假設。在特殊狀況下,Leader選舉可能失敗。若是沒有服務器扮演Leader,那麼就沒有新命令被提出。若是同時有多個服務器認爲本身是Leader,它們在一個一致性算法執行實例中可能提出不一樣value值,這可能致使沒有任何值能被選定。可是安全性是能夠保證的——不可能有兩個不一樣的值被選定爲第i條狀態機命令。單個Leader的選舉只是爲了保證流程能往下進行。

若是服務器的集合是變化的,那麼必須有某種方法能夠決定哪些服務器來實現哪一些一致性算法實例。最簡單的方式就是經過狀態機自己來完成。當前的服務器集合能夠是狀態的一部分,同時也能夠經過狀態機命令來改變。經過用執行完第i條狀態機命令後的狀態來描述執行一致性算法i+a的服務器集合,咱們就能讓Leader提早獲取a個狀態機命令。這就容許任意複雜的重配置算法有一個簡單實現。

參與文獻

[1] Michael J. Fischer, Nancy Lynch, and Michael S. Paterson. Impossibility of distributed consensus with one faulty process. Journal of the ACM, 32(2):374–382, April 1985. [2] Idit Keidar and Sergio Rajsbaum. On the cost of fault-tolerant consensus when there are no faults—a tutorial. TechnicalReport MIT-LCS-TR-821, Laboratory for Computer Science, Massachusetts Institute Technology, Cambridge, MA, 02139, May 2001. also published in SIGACT News 32(2) (June 2001). [3] Leslie Lamport. The implementation of reliable distributed multiprocess systems. Computer Networks, 2:95–114, 1978. [4] Leslie Lamport. Time, clocks, and the ordering of events in a distributed system. Communications of the ACM, 21(7):558–565, July 1978. [5] (1, 2, 3, 4) Leslie Lamport. The part-time parliament. ACM Transactions on Computer Systems, 16(2):133–169, May 1998.
相關文章
相關標籤/搜索