Paxos一致性算法——分佈式系統中的經典算法,論文自己也有一段有趣的故事。一致性問題是分佈式系統的根本問題之一,在論文中,做者一步步的增強最初一致性問題(2.1節提出的問題)的約束條件,最終導出了一個可實現的一致性模型。當前Paxos算法的研究愈來愈多,相關實現也很多,而原論文依然是最不可少的資料。論文通篇沒有一個數學公式,這是大牛的堅持!
【】中的是我我的的註釋。
先解釋文中幾個關鍵詞的翻譯:
Proposal譯爲「議案」,由proposer提出,被aceeptor批准或否決
Value譯爲「決議」,它是議案的內容,一個議案就是一個{編號,決議}對
Accept譯爲「批准」,表示議案被acceptor批准
Choose譯爲「選擇」,表示議案「被選擇」,也就是被多數acceptor批准算法
Leslie Lamport
2001.11.1安全
Paxos算法,純文本方式描述,很是簡單。【論文初版沒多少人理會,鬱悶了:)】性能優化
爲實現具備容錯能力的分佈式系統而提出的Paxos算法,曾被認爲難以理解,可能由於對於大部分讀者而言,原來的描述是基於希臘故事的[5]。【爲了描述Paxos算法,Lamport設計了一個虛擬的希臘城邦Paxos】實際上,它是最簡單和直觀的分佈式算法之一【這個…,其實沒那麼簡單吧】。它的核心是一個一致性算法——[5]中提出的「synod」算法。下一節描述這個一致性算法,並聽從咱們要求的性質。最後一節解釋了完整的Paxos算法,從一致性的直觀應用到構建分佈式系統的有限狀態機模型——應該是總所周知的模型,由於它是論文[5]的主題——它多是分佈式系統理論被引用最多的論文。服務器
設想一組能夠提出決議(value)的process。一致性算法保證全部提出的決議中,有一個決議會被選擇(chosen)。若是沒有提出決議,那麼將不會有選擇。若是一個決議被選擇,那麼process最終都能知道這個被選擇的決議。一致性的安全性包括:
——決議只有被提出後纔可能被選擇,
——只有一個決議被選擇,而且
——process永遠不會獲知一個決議被選擇了,除非這個決議確實已經被選擇。
咱們將不會特別明確精確的時間性要求。然而,其目標是最終有一個提出的決議被選擇,而且process最終會獲知被選擇的決議,若是有的話。
咱們爲一致性算法劃分3個角色,並分別以代理:proposer(提出者),acceptor(批准者)和listener(接收者)表示。實現中,容許一個process扮演多個代理,這裏咱們不關心從代理到process的映射。
假設代理之間用消息通訊。咱們採用異步、非拜占庭模型【拜占庭模型(Byzantine model),消息可能丟失、重複或者內容損壞。換而言之,非拜占庭模型就是容許消息的丟失或者重複,可是不會出現內容損壞的狀況】:
——代理以任意的速度操做,可能會由於停機而失效,可能會重啓。由於任一個代理均可能會在決議被選擇後停機再重啓,所以解決方案要求代理必須可以記憶某些信息,從而能在重啓後從新載入。
——消息傳送速度不可預測,可能會重複或丟失,可是內容不會損壞。網絡
最簡單的方法就是用單個acceptor代理。Proposer發送議案(proposal)給這個acceptor,它選擇最早收到的議案。儘管簡單,可是若是acceptor停機了,那麼系統就不能繼續運行了,這個方案並不能知足要求。【明顯的單點問題】
看來咱們須要選擇另外的方法,咱們用多個acceptor代理,而非一個,proposer向一組acceptor提出議案。一個acceptor可能批准該議案,當有足夠大的acceptor集合批准了這個議案時,決議【議案是一個{編號,決議}對】就被選擇了。那麼這個集合多大才足夠呢?爲了保證只有一個決議被選擇,咱們可讓這個集合包含多數的代理【後面也會稱之爲多數派】。由於任意兩個多數派至少有一個相同的代理,若是一個acceptor最多隻能批准一個決議,這就是可行的。
假設沒有失敗或者消息丟失,即便僅有一個proposer提出了一個決議,咱們也但願能選擇一個決議。這就導出了下面的需求:
P1. Acceptor必須批准它接收到的第一個決議。
可是該需求會致使一個問題。同時可能有幾個proposer提出了幾個不一樣的決議,從而致使每一個acceptor都批准了一個決議,可是沒有一個決議被多數派批准。即便只有兩個決議,若是每一個都被半數的acceptor批准,單個的acceptor失效也會致使不可能知道到底哪一個決議被選擇了。
一個決議要通過多數派的批准才能被選擇,這個需求和P1暗示了acceptor必須可以批准多個議案。咱們爲每一個議案分配一個編號來記錄不一樣的議案,所以一個議案由編號和決議構成【也就是議案={編號,決議}】。爲避免混淆,咱們要求議案的編號是惟一的。這個取決於實現,咱們假設能夠作到這一點。若是一個議案{n, v}經過多數派的批准,那麼決議v就被選擇了。這種狀況下,咱們稱議案(包括其決議v)被選擇了。
咱們容許選擇多個議案,可是必須保證全部選擇的議案包括相同的決議。對議案編號概括,能夠保證:
P2. 若是一個議案{n, v}被選擇,那麼全部被選擇的議案(編號更高)包含的決議都是v。
由於編號是全序的,P2保證了「只有一個決議被選擇」這一關鍵安全屬性。議案必須至少被一個acceptor批准纔可能被選擇。所以只要知足下面的條件,就能夠知足P2:
P2A. 若是一個議案{n, v}被選擇,那麼任何acceptor批准的議案(編號更高)包含的決議都是v。
咱們依然保證P1來確認選擇了某些議案。由於通訊是異步的,在特殊狀況下,某些acceptor c沒有接收到過任何議案,它們可能會【錯誤的】批准一個議案。設想一個新的proposer「醒來」並提出了一個更高編號的議案(包含不一樣的決議)。根據P1的要求,c應該批准這個議案,可是這違反了P2A。爲了同時保證P1和P2A,咱們須要加強P2A:
P2B. 若是一個議案{n, v}被選擇,那麼此後,任何proposer提出的議案(編號更高)包含的決議都是v。
由於一個議案必須在被proposer提出後才能被acceptor批准,所以P2B包含了P2A,進而包含了P2。
如何才能知足P2B呢,讓咱們來考慮如何證實它是成立的。咱們假設某個議案{m, v}被選擇,而後證實任何編號n>m的議案的決議都是v。對n概括能夠簡化證實,根據條件:每一個提出的議案(編號從m到n-1)的決議都是v,咱們能夠證實編號爲n的議案的決議是v。對於選擇的議案(編號爲m),一定存在一個集合C(acceptor的多數派),C中的每一個acceptor都批准了該議案。結合概括假設,m被選擇這一前提意味着:
C中的每一個acceptor都批准了一個編號在m到n-1範圍內的議案,而且議案的決議爲v。
由於任何由多數派組成的集合S都至少包含C中的一個成員,咱們能夠得出結論:若是下面的不變性成立,那麼編號爲n的議案的決議就是v:
P2C. 對於任意的v和n,若是議案{n, v}被提出,那麼存在一個由acceptor的多數派組成的集合S,或者a) S中沒有acceptor批准過編號小於n的議案,或者b) 在S的任何acceptor批准的全部議案(編號小於n)中,v是編號最大的議案的決議。
經過保持P2C,咱們就能知足P2B。
爲了保持不變性P2C,準備提出議案(編號爲n)的proposer必須知道全部編號小於n的議案中編號最大的那個,若是存在的話,它已經或將要被acceptor的某個多數派批准。獲取已經批准的議案是簡單的,可是預知未來可能批准的議案是困難的。Proposer並不作預測,而是假定不會有這樣的狀況。也就是說,proposer要求acceptor不能批准任何編號小於n的議案。這引出了下面提出議案的算法【這就是兩階段提交了】。
1 proposer選擇一個新編號n,向某個acceptor集合中的全部成員發送請求,【prepare請求階段,n是prepare請求的編號,也是下面accept請求的議案編號】並要求迴應:
a) 一個永不批准編號小於n的議案的承諾,以及
b) 在它已經批准的全部編號小於n的議案中,編號最大的議案,若是存在的話。
我把這樣的請求稱爲prepare請求n。
2 若是proposer收到了多數acceptor的迴應,那麼它就能夠提出議案{n, v},其中v是全部迴應中編號最高的議案的決議,或者是proposer選擇的任意值,若是acceptor們迴應說尚未批准過議案。
一個proposer向一個acceptor集合發送已經被批准的議案(不必定是迴應proposer初始請求的acceptor集合),咱們稱之爲accept請求。
咱們已經描述了proposer的算法。那麼acceptor呢?它能夠接收兩種來自proposer的請求: prepare請求和accept請求。Acceptor能夠忽略任何請求,而不用擔憂安全性。所以,咱們只須要描述它須要迴應請求的狀況。任什麼時候候它均可以迴應prepare請求【本文中,迴應就意味着接受了這個prepare請求和編號n】,它能夠迴應accept請求,並批准議案,當且僅當它沒有承諾過【承諾批准或批准一個更高編號的議案】。換句話講:
P1A. acceptor能夠批准一個編號爲n的議案,當且僅當它沒有迴應過一個編號大於n的prepare請求。
P1A蘊含了P1。
如今咱們獲得了一個完整的決議選擇算法,並知足咱們要求的安全屬性——假設議案編號惟一。經過一些簡單優化就能獲得最終算法。
假設一個acceptor接收到一個編號爲n的prepare請求,可是它已經迴應了一個編號大於n的prepare請求。因而acceptor就沒有必要回應這個prepare請求了,由於它不會批准這個編號爲n的議案。它還能夠忽略已經批准過的議案的prepare請求。
有了這些優化,acceptor只須要保存它已經批准的最高編號的議案(包括編號和決議),以及它已經迴應的全部prepare請求的最高編號。由於任何狀況下,都須要保證P2C,acceptor必須記住這些信息,包括失效並重啓以後。注意,proposer能夠隨意的拋棄一個議案——只要它永遠不會使用相同的編號來提出另外一個議案。
結合proposer和acceptor的行爲,咱們將把算法能夠分爲兩個階段來執行。
階段1.
a) Proposer選擇一個議案編號n,向acceptor的多數派發送編號也爲n的prepare請求。
b) Acceptor:若是接收到的prepare請求的編號n大於它已經迴應的任何prepare請求,它就回應已經批准的編號最高的議案(若是有的話),並承諾再也不迴應任何編號小於n的議案;
階段2.
a) Proposer:若是收到了多數acceptor對prepare請求(編號爲n)的迴應,它就向這些acceptor發送議案{n, v}的accept請求,其中v是全部迴應中編號最高的議案的決議,或者是proposer選擇的值,若是迴應說尚未議案。
b) Acceptor:若是收到了議案{n, v}的accept請求,它就批准該議案,除非它已經迴應了一個編號大於n的議案。
Proposer能夠提出多個議案,只要它遵循上面的算法。它能夠在任什麼時候刻放棄一個議案。(這不會破壞正確性,即便在議案被放棄後,議案的請求或者回應消息纔到達目標)若是其它的proposer已經開始提出更高編號的議案,那麼最好能放棄當前的議案。所以,若是acceptor忽略一個prepare或者accept請求(由於已經收到了更高編號的prepare請求),它應該告知proposer放棄議案。這是一個性能優化,而不影響正確性。
2.3 獲知選擇的決議
Learner必須找到一個被多數acceptor批准的議案,才能知道一個決議被選擇了。一個顯而易見的算法就是,讓每一個acceptor在批准議案時通知全部的learner。因而learner能夠儘快知道選擇的決議,可是要求每一個acceptor通知每一個learner——須要的消息個數等於learner數和acceptor數的乘積。
基於非拜占庭假設,一個learner能夠從另外一個learner得知被選擇的決議。咱們可讓acceptor將批准狀況迴應給一個主Learner,它再把被選擇的決議通知給其它的learner。這增長了一次額外的消息傳遞,也不可靠,由於主learner可能會失效,可是要求的消息個數僅是learner數和acceptor數的總和。
更通常的,能夠有多個主Learner,每一個都能通知其它全部的acceptor。主learner越多越可靠,可是通訊代價會增長【消息個數越多】。
因爲消息丟失,可能沒有learner知道選擇了一個決議。Learner能夠向acceptor詢問批准的議案,可是因爲acceptor的失效,可能難以得知多數派是否批准了一個議案。這樣,learner只能在新的議案被選擇時才能知道acceptor選擇的決議。若是learner須要知道是否已經選擇了一個決議,它可讓proposer根據上面的算法提出一個議案【提出請求就有迴應,而且新的提案的決議就是當前選擇的決議】。數據結構
很容易構造這樣一個場景,兩個proposer輪流提出一系列編號遞增的議案,可是都沒有被選擇。Propoer p選擇議案的編號爲n1,並結束階段1。接着,另一個proposer q選擇了議案編號n2>n1,並結束階段1。因而p在階段2的accept請求將被忽略,由於acceptor已經承諾再也不批准編號小於n2的議案。因而p再回到階段1並選擇了編號n3 > n2,這又致使q第二階段的accept請求被忽略,…
爲了保證流程,必須選擇一個主proposer,只有主proposer才能提出議案。若是主proposer和多數acceptor成功通訊,並提出一個編號更高的議案,議案將被批准。若是它得知已經有編號更高的議案,它將放棄當前的議案,並最終能選擇一個足夠大的編號。
若是系統中有足夠的組件(proposer,acceptor和網絡)能正常工做,經過選擇一個主proposer,系統就能保持響應。Fischer、Lynch和Patterson的著名結論[1]代表:選擇proposer的可靠算法必須是隨機的或者實時的——例如,使用超時機制。然而無論選擇成功與否,安全性都能獲得保證。異步
Paxos算法[5]假設了一組網絡進程。在其一致性算法中,每一個process都同時扮演proposer、acceptor和learner的角色。算法選擇一個leader,它就是主proposer和主learner。Paxos一致性算法就是上面描述的那個,請求和響應都用消息發送(響應會被打上對應議案的編號,以防止混淆)。使用持久化存儲來保證acceptor失效後也能記起必要的信息。Acceptor在發送響應前必須持久化存儲該響應。
接下來就是描述保證任何兩個議案的編號都不相同的機制。proposer從互不相交的集合中選擇議案編號,所以兩個不一樣的proposer永遠不會提出相同編號的議案。【假設有5個proposer,編號爲0~4,可使proposer i的議案編號選擇序列爲:5*j + i(j >= 0),就能保證永不重複,且遞增】每一個proposer都持久化保存它已經提出的編號最高的議案,並使用一個更高的議案編號來開始階段1。分佈式
實現分佈式系統的簡單方式就是使用一些客戶端向中心服務器發送命令【就是C/S模式了】。服務器能夠看做是根據必定順序執行客戶端命令的肯定狀態自動機。狀態機包含當前狀態,每讀入一個命令併產生相應的結果,它就執行一步。好比,分佈式銀行系統的客戶端多是出納員,狀態機的狀態由全部用戶的帳戶餘額構成。取款操做將會執行一條狀態機命令:減小帳戶餘額,輸出新舊餘額數(當且僅當餘額大於取款值)。
使用單箇中心服務器的實現是不可靠的,所以咱們使用一組服務器,每個都獨立的執行狀態機。由於狀態機是肯定的,若是執行相同的命令序列,全部的服務器將會產生一樣的狀態序列和輸出【非拜占庭模型假設再一次起做用了】。客戶端發起命令後,可使用任何服務器的輸出。
爲了保證全部的服務器執行的是相同的命令序列,咱們執行一個paxos一致性算法的實例(instance)序列【註解#】,每一個實例是一個獨立運行的paxos一致性算法,第i個實例選擇的決議就是序列的第i個狀態機命令。在算法的每一個實例中,每一個server都扮演全部的角色(proposer、acceptor和learner)。如今,假設服務器組固定,全部的實例都使用相同的代理。
【註解#】
某些資料會把「實例」稱爲「輪」(round),每輪選擇一個決議,但每輪可能會執行屢次一致性算法,好比若是主proposer在階段1提出的prepare請求被否決了,那麼它將會選擇新的議案編號,從新提出議案請求,直到議案被多數acceptor批准(消息發送失敗也會致使重傳請求)。引入輪(就是實例啦)這一律念後,能夠作到各輪並行運行,同時批准多個決議,互不干涉,更有效率。
【end #】
正常操做下,一個服務器被選擇爲leader,它就是全部的實例的主proposer(惟一可以提出議案的)。客戶端向leader發送命令,leader決定命令的順序。若是leader決定某個客戶端命令應該是第15個命令,它將試圖把該命令選擇爲第135個實例的決議,一般都能成功。有可能由於失效而失敗,或者另外的一個服務器相信它是leader,有另外的選擇。可是一致性算法保證最多隻能有一個命令被選成第135個。
Paxos一致性算法有效性的關鍵在於,決議直到階段2纔會真正提出。回憶proposer算法的階段1結束後,或者要提的決議已經決定了,或者proposer能夠提出任何決議。我將描述正常操做下Paxos狀態機的執行狀況。接下來,再討論可能的錯誤狀況。我會考慮前一個leader失效、新的leader被選擇後會發生什麼狀況(系統啓動是個特例,這時尚未提出命令)。
【在下面的部分,由於客戶端的命令就是議案的決議,所以決議就是命令,會混用,並不影響理解】
在一致性算法的全部實例中,新的leader也是learner,應該知道已經選擇的命令。假設它知道命令1–13四、138和139——也就是算法實例1–13四、138和139選擇的決議(後面將看到命令序列中的間隔是如何引發的)。而後leader爲實例135-137和全部大於139的實例執行階段1(後面會說明是怎麼作到的)。假設這些執行的輸出將決定實例135和140提出的決議,可是不會影響其它實例的決議。而後leader爲實例135和140執行階段2,並選擇了第135和140個命令。
Leader和其它全部知道leader命令的服務器,如今能夠執行命令1-135。可是不能執行命令138-140(雖然它們也知道),由於必須選擇命令136和137。Leader也能夠把客戶端提出的後面兩個命令選爲命令136和137。咱們但願儘快的消除間隔,因而爲命令136和137提出一個特殊的「noop」命令(不改變狀態)(爲命令136和137執行階段2)。一旦noop命令被選擇,命令138-140就能執行了。
如今命令1-140已經被選擇了。Leader還已經爲全部大於140的實例完成了一致性算法的階段1的操做【註解*】,並能夠在階段2中提出任何決議。它把客戶端的下一條命令編號爲141,並看成實例141的階段2的決議。再把下一條編號爲142,以此執行。
【註解*】
對於同一個leader而言,若是它在執行實例i中執行了階段1,那麼後續的執行實例就不須要再次執行階段1了,而直接執行階段2,緣由以下:
1. 由於它提出的議案編號老是遞增的,acceptor一定接受階段1的prepare請求;
2. 每一個實例都是獨立運行的paxos算法,互不干擾,決議互相獨立;
減小了一個階段,效率也一定有所提升。
【end*】
在知道提出的命令141被選擇以前,Leader能夠提出命令142。它提出命令141的消息可能都丟失了,還可能在其餘服務器知道leader提出命令141以前,命令142已經被選擇了。 當leader在實例141的階段2中沒有收到指望的迴應後,它將重傳這些消息。若是一切順利,它提出的命令將被選擇。然而,也可能會失敗,在命令序列中留下了一個間隔。通常來講,假設leader能夠預取r個命令——也就是說,在命令1到i被選擇以後,它就能夠同步提出命令i+1, …, i+r。那麼最多能夠產生r-1個間隔【容易理解,前面的r-1個命令都失敗了】。
新選出的leader要爲算法的無限個實例執行階段1——在上面的場景中,是實例135-137,和139以後的全部實例【個人理解是,爲了這些實例,它總共只須要成功的執行一次階段1,理由見上面的註解*,固然屢次執行也不會出問題,就像下面所說的那樣】。它能夠向其餘的服務器發送一條簡單的合理短消息,併爲全部實例使用相同的議案編號。在階段1,僅當acceptor已經收到了一個來自其它proposer的階段2的消息時,除了簡單的OK,它的迴應會包含額外的消息,(在上述場景中,僅實例135和140是這樣的【像前面所描述的,實例135-137和139執行的輸出將決定實例135和140提出的決議】)。所以,服務器(做爲acceptor)能夠爲全部實例迴應一條簡單的短消息。屢次執行階段1並不會出問題。
由於leader的失效和由此引起的選舉應該是小几率事件,有效執行一條狀態機命令的開銷——達到對命令/決議的一致性——僅僅是一致性算法的階段2的開銷【又一次驗證了階段1並不須要屢次執行】。能夠證實,在容許失效的狀況下,在全部的同類(一致性)算法中, paxos一致性算法的階段2具備最小可能的【時間】複雜度[2]。所以paxos算法在本質上就是優化的。
上面對系統正常操做的討論假設一直存在單個leader,除去在當前leader失效和選舉新leader之間的一小段時間。在異常環境中,leader選擇可能失敗。若是沒有服務器被選爲leader,那麼將不能接受命令。若是多個服務器都認爲本身是leader,在同一個算法實例中,它們將都能提出議案,這可能會致使全部的議案都不能被選擇。然而,安全屬性是知足的——兩個不一樣的服務器將永遠不會在第i個狀態機命令的選擇上達成一致。選擇單一的leader只是爲了保證流程【避免衝突】。
若是服務器組能夠變更,必須有方法能檢測哪些服務器執行的是算法的哪些實例。最簡單的作法就是讓狀態機本身檢測。當前的服務器組能夠做爲狀態的一部分,並能被原生的狀態機命令修改。咱們可讓leader預取r個命令,並將執行算法實例i+r的服務器組的狀態設置成執行第i個狀態機命令後的狀態。因而你可使用簡單的重配置算法來進一步加強算法的可靠性【若是半數服務器同時失效,重配置機制也一籌莫展,然而這種機率過低了】。
【下面wiki的兩篇文章也是paxos很好的參考資料,一篇中文,一篇英文,然而內容並不重複】
http://en.wikipedia.org/wiki/Paxos_algorithm
http://www.wikilib.com/wiki/Paxos%E7%AE%97%E6%B3%95
【還有兩個重要的問題就是如何選舉leader,以及server間數據的同步,能夠參看zookeeper的實現;這些內容整理好下次再單獨發出來:)】oop
[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] Leslie Lamport. The part-time parliament. ACM Transactions on Computer Systems, 16(2):133–169, May 1998.
post