《Paxos Made Simple》譯文

原文鏈接:https://www.microsoft.com/en-us/research/uploads/prod/2016/12/paxos-simple-Copy.pdf

                                        讓Paxos更簡單

                                                                  Leslie Lamport,2001年11月3日

摘要

當採用簡單直接的方式描述,Paxos算法是非常簡單的。

目錄

  1. 簡介
  2. 共識算法
    1. 問題
    2. 選中一個值
    3. 學習選中的值
    4. 進展
    5. 實現
  3. 實現狀態機

     參考文獻

 

 

1. 簡介

Paxos算法可用於實現容錯分佈式系統,或許是因爲之前以希臘神話故事的形式描述Paxos算法[5],對許多讀者來說,Paxos算法一直很難理解。事實上,Paxos算法在分佈式算法中,屬於較爲簡單和易理解的一類。Paxos算法的核心是一個共識算法——參考文獻[5]中的“會議”(synod)算法。在下一節,我們將展示,如何從我們期望共識算法所擁有的屬性派生出這個共識算法(“會議”算法)。最後一節解釋了完整的Paxos算法,將共識直接應用到構建分佈式系統的狀態機方法[4],即可得到Paxos算法。狀態機方法是衆所周知的一種方法,它可能是分佈式系統理論中最常被引用的一種方法。

2. 共識算法

2.1 問題

假設有一組進程可以提出值(propose values)。共識算法可以確保所有被提出的值中,只有一個值能被選中(chosen)。如果沒有值被提出,那麼沒有值會被選中。如果一個值被選中了,那麼進程應該可以學習(learn)這個被選中的值。

共識算法的安全性(safety)要求如下:

  1. 只有被提出的值纔可以被選中
  2. 只有一個值能被選中
  3. 除非值被選中了,否則進程不會知道它被選中了,即:進程只學習已經被選中的值

我們不會明確指定靈活性(liveness)要求。然而,靈活性的目標是確保某個被提出的值最終可以被選中,並且如果一個值被選中,進程最終可以學習該值。

在共識算法中,存在三種角色,由三類代理(agent)執行這些角色:提出者(proposer),接受者(acceptor)以及學習者(learner)。在實現中,單個進程可以充當多個代理。這裏,代理到進程的映射不會對我們產生影響。

假定代理們通過發送消息彼此交流。我們採用慣用的異步非拜占庭模型,其中:

  1. 代理以任意速度運行,可能會故障(故障表現爲停止運行),可能會重啓。既然所有的代理可能在某個值被選中後故障,並且隨後重啓,那麼除非信息能被故障後重啓的代理記住,否則共識算法是不可能實現的。
  2. 消息傳送所需的時間是不確定的,消息可以重複發送,可以丟失,但是不會損壞。

2.2 選中一個值

選中一個值最簡單的方法是隻有單個接受者代理。提出者發送一個提議(proposal)給接受者,接受者選中它收到的第一個被提出的值即可。儘管很簡單,但這種解決方法卻不那麼令人滿意。因爲在單個接受者故障之後,將不會取得任何進展(progress)。

所以,我們嘗試另一種方法來選中單個值。採用多個接受者代理。提出者發送提出的值給某個接受者集合。一個接受者可能會接受(accept)這個提出的值。當一個足夠大的接受者集合接受了這個值,這個值就被選中了。那麼多大才算足夠大呢?爲了確保只有單個值被選中,足夠大的集合包括了任意大多數代理(譯者注:假設有5個代理,任意大多數代理表示3個代理:5除以2的下界再加1)。因爲任意兩個大多數代理至少有一個共同的接受者。這種方法奏效的條件是一個接受者最多隻能接受一個值。(從參考文獻[3]開始,許多論文都觀察到了“大多數”)。

在沒有故障和消息丟失的情況下,即使只有單個提出者提出一個值,我們依然想要值被選中。這就需要:

P1. 接受者必須接受它收到的第一個提議。

但這個要求帶來一個問題。幾個值可能同時被不同的提出者提出,導致每個接受者接受了一個值,但是不存在被大多數接受者接受的單個值。即使只有兩個被提出的值,如果每個已經被半數接受者接受,一個接受者故障的情況也可能會導致不知道哪個值被選中。

P1以及只有當一個值被大多數接受者接受才被選中這一條件說明,一個接受者一定可以接受多個提出的值。我們通過爲每個提出的值分配一個編號來追蹤接受者接受的不同的值,所以一個提議(proposal)包含一個提議號和一個值。爲了防止混淆,不同的提議需要有不同的提議號。提議號的確定可以在實現過程中決定,現在我們假設有了提議號。包含單個值的單個提議被大多數接受者接受,就可以說這個值被選中。這種情況下,我們說這個提議(以及其中的值)被選中。

可以允許多個提議被選中,但是一定要確保所有被選中的提議有相同的值。通過對提議號進行歸納,發現這需要保證:

P2. 如果值爲v的提議被選中,那麼每一個被選中的更大提議號的提議的值均爲v。

既然提議號是全序的,條件P2確保了關鍵的安全性——只有一個值被選中。

至少具有一個接受者接受提議,提議才能被選中。所以,滿足下列條件即可滿足P2:

P2a. 如果值爲v的提議被選中,那麼接受者接受的每一個更大提議號的提議的值均爲v。

我們仍然需要滿足P1來確保存在提議被選中。因爲通信是異步的,可能在某個特定的接受者c從未收到任何提議的情況下,有一個提議被選中了。假定一個新的提出者提出了一個更大提議號的提議,但是值和選中的提議的值不同。遵循P1,c會接受這個提議,但會違反P2a。P1和P2同時滿足需要對P2a進一步改進:

P2b. 如果值爲v的提議被選中了,那麼任何提出者提出的更高提議號的提議的值均爲v。

既然在接受者接受提議之前,一定存在某個提出者提出了該提議,那麼滿足P2b就滿足P2a,滿足P2a就滿足P2。

爲了研究如何滿足P2b,考慮如何才能證明P2b被滿足。我們假設提議號爲m,值爲v的提議被選中了,接下來說明提議號(n)大於m的任何提議的值均爲v。可以通過在n上使用歸納法來簡化證明,所以可以通過證明:假設提議號在m…(n-1)中的提議的值爲v,那麼提議號爲n的提議的值爲v(其中,i…j表示從i到j的數值集合),來證明P2b被滿足。若提議號爲m的提議被選中,那麼一定有包括大多數接受者的某個集合C,C中每個接受者都接受了該提議。將這和歸納假設相結合,那麼m被選中的假設意味着:

C中每個接受者均接受了一個提議,其提議號在m…(n-1)中;任何接受者接受的提議號在m…(n-1)中的每個提議都有值v。

既然包含大多數接受者的任何集合S至少會包括C中的一個成員,因此通過確保滿足下列條件,來保證提議號爲n的提議的值爲v:

P2c:對於任何v和n,如果提議號爲n,值爲v的提議被提出,那麼有一個包含大多數接受者的集合S,S滿足下列條件之一:

(a)S中沒有接受者接受過提議號小於n的提議

(b)v是S中接受者接受的提議號比n小的提議中,具有最高提議號的提議的值。

因此,我們可以通過保證P2c,來滿足P2b。

爲了確保P2c,如果提出者想要提出一個提議號爲n的提議,那他一定要知道提議號小於n的提議中,具有最大提議號的提議(如果有的話),這個提議已經被或將要被某個大多數接受者集合中的每個接受者接受。想要知道已經被接受的提議是很簡單的;但是預測將要被接受的提議是很困難的。提出者不嘗試預測未來,而控制不會有這樣將要被接受的提議。換句話說,提出者請求接受者不再接受提議號小於n的任何提議。故而產生下列提出提議的算法:

1. 提出者選擇一個新的提議號n,並給某個接受者集合中的每個接受者發送一個請求,要求其回覆:

   承諾永遠不再接受提議號小於n的提議,並且

   回覆已經接受的提議號小於n的,最大提議號的提議(如果有的話)。

   將這樣的請求稱爲編號爲n的“準備”(prepare)請求。

2. 如果提出者從大多數接受者處收到“準備”請求的回覆,那麼它可以提出一個提議號爲n,值爲v的提議,其中,v或者是收到的回覆中具有最大提議號的提議的值,或者是提出者選中的任何值(如果接受者沒有回覆提議)

之後,提出者通過發送請求給某個接受者集合來提出提議,其中請求的內容是請求接受者接受該提議(不需要是和響應“準備”請求一樣的接受者集合)。我們稱之爲“接受”請求。

提出者的算法就此誕生。那接受者呢?接受者可能從提出者處收到兩種請求:“準備”請求和“接受”請求。接受者可以在不影響安全性(safety)的情況下忽略任何請求。所以,我們只需說明何時允許接受者響應請求。接受者始終可以響應“準備”請求。只有當接受者沒有承諾不接受“接受”請求的時候,它纔可以響應“接受”請求,接受提議。也就是說:

P1a. 當且僅當接受者沒有響應提議號比n更大的提議的“準備”請求時,它纔可以接受提議號爲n的提議。

觀察到,P1a包含P1。

假定提議號是唯一的,我們現在有一個完整的算法可以選擇滿足安全性的一個值。最終的算法還需進行一些小優化。

假設接受者收到編號爲n的“準備”請求,但是它已經響應了一個編號比n更大的“準備”請求,從而承諾不接受任何編號爲n的新提議。既然它不會接受編號爲n的提議的話,那麼接受者就沒有必要回復這個新收到的“準備”請求。所以,我們讓接受者忽略這樣的“準備”請求。同時,也會讓接受者忽略已經接受的提議的“準備”請求。

根據這個優化,接受者需要記住已經接受的最大提議號的提議以及響應的最大編號的“準備”請求的編號。由於無論什麼故障,必須保證P2c,所以即使接受者故障後重啓,也一定要記住這個信息。提出者可以隨時放棄提議並忘記所有提議——只要它永遠不用相同的提議號提出不同值的提議。

結合提出者和接受者的行爲,算法包含下列兩個階段:

階段1 (a)提出者選擇一個提議號n併發送附帶n的“準備”請求給大多數接受者。

          (b)如果接受者收到附帶n的“準備”請求,並且n大於它已經響應的任何“準備”請求,那麼會回覆請求,並承諾不會接受任何提議號比n小的提議,並且附帶它已經接受的最大提議號的提議(如果有的話)。

階段2 (a)如果提出者從大多數接受者處收到“準備”請求(附帶n)的回覆,那麼會發送提議號爲n,值爲v的提議的“接受”請求給某個大多數接受者的集合。其中,v是收到的回覆中提議號最大的提議的值,如果回覆中沒有任何提議,那麼v可以是任意值。

          (b)如果接受者收到提議號爲n的提議的“接受”請求,除非已經響應了編號大於n的“準備”請求,否則它會接受這個提議。

提出者可以提出多個提議,只要每個提議遵循這個算法即可。提出者在算法執行過程中,可以放棄提議。(即使在提議被放棄很久之後,提議的請求和/或回覆消息到達目的地,算法也可以保持正確性。)如果某個提議者已經開始嘗試提出更高編號的提議,放棄提議可能是一個好主意。因此,如果一個接受者因爲收到了更高編號的“準備”請求而忽視 “準備”請求或者“接受”請求,它應該通知提出者,提出者進而放棄自己的提議。這是一個不影響正確性的性能優化。

2.3 學習選中的值

要學習一個已經選中的值,學習者必須知道該提議已經被大多數接受者接受。明顯的算法是讓每個接受者在接受提議後,回覆所有學習者,告訴他們這個提議。這讓學習者可以儘快地發現一個選中的值,但是需要每個接受者回復每個學習者——回覆的數量等於接受者數乘以學習者數。

非拜占庭故障的假設使得一個學習者從另一個學習者處知道值被接受是很容易的。可以讓接受者回復他們的接受信息給一個特定的學習者。當一個值被選中的時候,該學習者通知其餘學習者。這種方法需要一個額外的輪次來讓所有學習者發現選中的值。也有點不可靠,因爲特定的學習者可能會故障。但是它需要的回覆數量只等於接受者數加上學習者數。

更一般地,接受者可以給一組特定的學習者發送他們的接受消息,其中,每個學習者可以在一個值被選中的時候通知所有的學習者。使用更多的特定學習者是以更高的通信複雜度爲代價提供更高的可靠性。

因爲消息會丟失,可能在沒有學習者知道的情況下,一個值被選中了。學習者可以詢問接受者他們接受了哪些提議,但是如果有一個接受者故障,學習者可能會不知道一個特定的提議是否被大多數接受者接受了。在這種情況下,只有當新提議被選中,學習者纔會知道哪個值被選中了。如果學習者需要知道一個值是否被選中,它可以讓提出者使用上述描述的算法提出一個提議。

2.4 進展(Progress

很容易構建一個場景,兩個提出者一直不斷地提出提議,提議號越來越大,沒有一個提議被選中。提出者p爲一個提議號n1完成了階段1。另一個提出者q接着爲提議號n2(n2>n1)完成了階段1。提出者p爲提議號n1的提議發出的階段2的“接受”請求被忽視了,因爲接受者承諾不去接受提議號小於n2的提議。所以,提出者p開始一個新的提議號n3(n3>n2)並完成了階段1,導致提出者q的階段2的“接受”請求被忽視了,以此類推。

爲了確保進展,一定要選擇一個特定的提出者作爲唯一嘗試去提出提議的人。如果特定的提出者可以和大多數接受者成功交流,並且使用的提議號比已經使用的任何提議號都大,那麼它將可以成功提出一個被接受的提議。如果它知道有一個更大提議號的提議,通過放棄一個提議,再次嘗試,這個特定的提出者最終可以選擇一個提議號足夠大的提議。

如果足夠多的系統組件(提議者,接受者和通信網絡)正常工作,則可以通過選擇一個特定的提出者來實現靈活性(liveness)。Fischer,Lynch和Paterson提出的著名的結論[1]表明,選擇一個提出者的可靠算法或者依賴隨機性或者依賴實時性——例如,通過使用超時。但是,無論選舉的成敗與否,始終能確保安全性(safety)。

2.5 實現

Paxos算法[5]假設一個進程網絡。在其共識算法中,每個進程都扮演着提出者,接受者和學習者的角色。算法選擇一個領導者,充當特定提出者和特定學習者的角色。Paxos共識算法就是上面描述的算法,其中請求和響應作爲普通消息發送。(響應消息標有相應的提議號以防止混淆。)在故障情況下依然具有的穩定存儲用於存儲接受者必須記住的信息。在發送回覆之前,接受者需先將回復記錄在穩定存儲中。

剩下的就是描述確保任意兩個提議的提議號都不同的機制。不同的提出者從不相交的數值集合中選擇他們的提議號,所以兩個不同的提出者不會提出相同提議號的提議。每個提出者記下(在穩定存儲中)它提出的最大的提議號,會使用比用過的提議號更大的提議號來開始階段1。

3. 實現狀態機

實現分佈式系統的一種簡單的方法是作爲客戶端集合提出命令到一箇中央服務器。可以將服務器描述爲以某種順序執行客戶端命令的確定狀態機。狀態機有一個當前狀態; 它的每一步是將一個命令作爲輸入,產生一個輸出和一個新狀態。例如:分佈式銀行系統的客戶端可能是櫃員,狀態機的狀態可能包括所有用戶的賬戶餘額。當且僅當餘額大於提取的金額時,可以通過執行一個減少賬戶餘額的狀態機命令來進行取款操作,會產生一個新舊餘額的輸出。

如果服務器故障,那麼使用單箇中央服務器的系統就會故障。因此我們使用一羣服務器,每個服務器獨立地實現狀態機。因爲狀態機是確定的,如果所有的服務器都執行相同的命令序列,那麼他們將產生相同的狀態序列和輸出序列。提出命令的客戶端就可以使用任何服務器產生的輸出。

爲了保證所有服務器執行相同的狀態機命令序列,我們實現了一系列單獨的Paxos共識算法實例(譯者注:上述提出的Paxos算法可以確保單個值被選中,實際應用中可能需要選中多個值,這裏實例對應於實際應用中需要多個值被選中的場景),第i個實例選中的值是命令序列中第i個狀態機命令。每個服務器在Paxos算法的每個實例中扮演所有的角色(提出者,接受者和學習者)。現在,假定服務器集合是固定的,因此共識算法的所有實例使用相同的代理集。

正常操作中,選擇單個服務器作爲領導者,它在共識算法的所有實例中充當特定的提出者(唯一嘗試提出提議的提出者)。客戶端發送命令給領導者,領導者決定命令序列中,每個客戶端命令應該出現的位置。如果領導者決定某個客戶端命令應該是第135條命令,領導者會使該命令作爲共識算法的第135條實例被選中。通常,領導者會成功。在故障的情況下,或者在另一個服務器也覺得自己是領導者並且關於第135條命令有不同的意見時,領導者可能會失敗。但是共識算法確保第135條命令最多隻有一個命令被選中。

這種方法的效率關鍵在於,在Paxos共識算法中,直到階段2被提出的值纔會被選中。回想一下,在提出者算法的階段1完成後,要麼確定要提出的值,要麼提出者可以提出任何值。

接下來,我會描述Paxos狀態機在正常操作期間是如何工作的。接着,我將討論哪些部分可能會出現問題。我會考慮領導者發生故障,新領導者被選舉時會發生什麼。(系統啓動是一種特殊情況,啓動時尚未提出任何命令。)

作爲共識算法所有實例的學習者,新領導者應該知道已經選中的大多數命令。假定它知道命令1-134,138以及139,也就是,它知道共識算法中實例1-134,138以及139中被選中的值(稍後我們會介紹爲什麼在命令序列中會出現這樣的間斷)。接着,領導者會執行實例135-137以及所有大於139的實例的階段1(下面會描述如何執行)。假定這些執行的結果會確定實例135和140可以提出的值,其餘實例可以提出的值不受約束。然後領導者會爲實例135和140執行階段2,從而選擇命令135和140。

領導者,和其他已經知道領導者知道的所有命令的服務器一樣,現在可以執行命令1-135。然而,不能執行命令138-140,因爲命令136和137還沒有被選中。領導者可以讓接下來收到的兩個客戶端命令作爲命令136和137。但是,我們讓領導者立即提出一個特別的“空操作”(“no-op”)作爲命令136和137,來填補這個間斷,以使狀態不被改變(對於實例136和137,領導者通過執行共識算法的階段2來實現選中“空操作”)。一旦這些空操作命令被選中,命令138-140就可以被執行。(譯者注:對於領導者爲什麼不使用收到的客戶端命令,而使用“空操作”填補實例136和137,在參考文獻[5]中有進行介紹,有興趣的讀者可以翻閱。)

命令1-140現在就被選中了。領導者已經完成了共識算法中所有大於140的實例的階段1,可以在階段2中自由地爲這些實例提出任何值。它將命令141分配給接下來客戶端提出的命令,在共識算法實例141的階段2提出的值爲該客戶端命令。領導者將收到的下一個客戶端命令作爲命令142提出,依此類推。

領導者提出命令141後,可以在知道141被選中之前提出命令142。有可能提出命令141所發送的所有消息都丟失了,並且在其他服務器學習領導者提出的命令141的值之前,命令142被選中了。如果領導者沒有收到實例141的階段2的任何回覆消息,他會重傳這些消息。如果一切順利,他提出的命令將會被選中。然而,也可能失敗,在命令序列中大家就不知道命令141,留下間斷。通常,假設領導者可以超前a個命令——也就是,在命令i被選中後,它可以提出命令i+1到i+a。可能會出現多達a-1個命令的間斷。

新選中的領導者會爲共識算法的所有實例(無窮多個)執行階段1——在上述情形中,是實例135-137以及大於139的所有實例。對於所有實例,領導者使用相同的提議號,通過給其他服務器發送一條“準備”請求來完成階段1。在階段1,只有接受者之前已經接受某個提出者發送的階段2 的消息,他纔會不只是回覆簡單的OK消息(附帶已經接受的提議,在上述情形中,是實例135和實例140的情況)。因此,一個服務器(接受者)可以用單個合理的短消息響應所有的實例。因此,階段1執行無數個實例不會產生任何問題。

由於領導者故障和選舉新領導者是極少發生的事件,執行狀態機命令的有效成本——即在命令/值上達成共識——僅是執行共識算法階段2的成本。可以證明,Paxos共識算法階段2的成本是在故障條件下達成共識的所有算法中最小的[2]。因此,Paxos算法基本上是最優的。

在系統正常操作的討論中,總是假定有一個領導者,除了當前領導者故障和新領導者選舉這段時間。在異常情況下,領導者選舉可能會失敗。如果沒有服務器作爲領導者,那麼將沒有新命令會被提出。如果多個服務器認爲他們是領導者,那麼他們都可以在共識算法的同一個實例中提出值,這會阻止任何值被選中。然而,安全性(safety)依然可以保證——兩個不同的服務器永遠不會對第i個狀態機命令選中的值產生不同意見。選舉單個領導者只是爲了確保進展(progress)。

如果服務器集合可以變更,那麼必須有某種方法來確定哪些服務器實現了共識算法的實例。最簡單的方法是通過狀態機本身來確定。當前服務器集合可以是狀態的一部分,並且可以通過普通的狀態機命令實現變更。可以在第i個狀態機命令中,決定執行共識算法第i+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 whentherearenofaults—atutorial. TechnicalReportMIT-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.

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章