Paxos 演算法是萊斯利蘭伯特（，LaTeX 中的「La」）於 1990 年提出的一種基於訊息傳遞且具有高度容錯特性的共識（consensus）演算法。Lamport 本人在 httpresearchmicrosoftcomuserslamportpubspubshtmllamportpaxos 中描寫了他用 9 年時間發表這個演算法的前前後後 需要注意的是，Paxos 常被誤稱為 一致性演算法。但是 一致性（consistency） 和 共識（consensus） 並不是同一個概念。Paxos 是一個共識（consensus）演算法。 Lamport L Paxos made simple J ACM Sigact News 2001 32 4 1825 問題和假設 分散式系統中的節點通訊存在兩種模型：共享記憶體（Shared memory）和訊息傳遞（Messages passing）。基於訊息傳遞通訊模型的分散式系統，不可避免的會發生以下錯誤：程序可能會慢、被殺死或者重啟，訊息可能會延遲、丟失、重複。在最普通的 Paxos 場景中，先不考慮可能出現「訊息篡改」（即拜占庭錯誤的情況）。Paxos 演算法解決的問題是在一個可能發生前述異常（即排除訊息篡改之外的其他任何異常）的分散式系統中，如何對某個值的看法相同，保證無論發生以上任何異常，都不會破壞決議的共識機制。一個典型的場景是，在一個分散式資料庫系統中，如果各節點的初始狀態一致，每個節點都執行相同的操作序列，那麼他們最後能得到一個一致的狀態。為保證每個節點執行相同的命令序列，需要在每一條指令上執行一個「共識演算法」以保證每個節點看到的指令一致。一個通用的共識演算法可以應用在許多場景中，是分散式計算中的重要問題。因此從 20 世紀 80 年代起對於共識演算法的研究就沒有停止過。 為描述 Paxos 演算法，Lamport 虛擬了一個叫做 Paxos 的希臘城邦，這個島按照議會民主制的政治模式制訂法律，但是沒有人願意將自己的全部時間和精力放在這種事情上。所以無論是議員，議長或者傳遞紙條的服務員都不能承諾別人需要時一定會出現，也無法承諾批准決議或者傳遞訊息的時間。但是這裡假設沒有拜占庭將軍問題（Byzantine failure，即雖然有可能一個訊息被傳遞了兩次，但是絕對不會出現錯誤的訊息）；只要等待足夠的時間，訊息就會被傳到。另外，Paxos 島上的議員是不會反對其他議員提出的決議的。 對應於分散式系統，議員對應於各個節點，制定的法律過程對應於系統的狀態。各個節點需要儘量保持一致的狀態，對應各議員推進法律決議推進。議員和服務員的不確定性對應於節點和訊息傳遞通道的不可靠性。 演算法 演算法的提出與證明 首先將議員的角色分為 proposers，acceptors，和 learners（允許身兼數職）。proposers 提出提案，提案資訊包括提案編號和提議的 value；acceptor 收到提案後可以接受（accept）提案，若提案獲得多數派（majority）的 acceptors 的接受，則稱該提案被批准（chosen）；learners 只能「學習」被批准的提案。劃分角色後，就可以更精確的定義問題： 決議（value）只有在被 proposers 提出後才能被批准（未經批准的決議稱為「提案（proposal）」）； 在一次 Paxos 演算法的執行例項中，只批准（chosen）一個 value； learners 只能獲得被批准（chosen）的 value。 在 Leslie Lamport 之後發表的 paper 中將 majority 替換為更通用的 quorum 概念，但在描述 classic paxos 的論文 Paxos made simple 中使用的還是 majority 的概念。 另外還需要保證 progress。這一點以後再討論。 作者透過不斷加強上述 3 個約束（主要是第二個）獲得了 Paxos 演算法。 批准 value 的過程中，首先 proposers 將 value 傳送給 acceptors，之後 acceptors 對 value 進行接受（accept）。為了滿足只批准一個 value 的約束，要求經「多數派（majority）」接受的 value 成為正式的決議（稱為「批准」決議）。這是因為無論是按照人數還是按照權重劃分，兩組「多數派」至少有一個公共的 acceptor，如果每個 acceptor 只能接受一個 value，約束 2 就能保證。 於是產生了一個顯而易見的新約束： P1：一個 acceptor 必須接受（accept）第一次收到的提案。 注意 P1 是不完備的。如果恰好一半 acceptor 接受的提案具有 value A，另一半接受的提案具有 value B，那麼就無法形成多數派，無法批准任何一個 value。 約束 2 並不要求只批准一個提案，暗示可能存在多個提案。只要提案的 value 是一樣的，批准多個提案不違背約束 2。於是可以產生約束 P2： P2：一旦一個具有 value v 的提案被批准（chosen），那麼之後批准（chosen）的提案必須具有 value v。 注：透過某種方法可以為每個提案分配一個編號，在提案之間建立一個全序關係，所謂 之後 都是指所有編號更大的提案。 如果 P1 和 P2 都能夠保證，那麼約束 2 就能夠保證。 批准一個 value 意味著多個 acceptor 接受（accept）了該 value。因此，可以對 P2 進行加強： P2a：一旦一個具有 value v 的提案被批准（chosen），那麼之後任何 acceptor 再次接受（accept）的提案必須具有 value v。 由於通訊是非同步的，P2a 和 P1 會發生衝突。如果一個 value 被批准後，一個 proposer 和一個 acceptor 從休眠中甦醒，前者提出一個具有新的 value 的提案。根據 P1，後者應當接受，根據 P2a，則不應當接受，這種場景下 P2a 和 P1 有矛盾。於是需要換個思路，轉而對 proposer 的行為進行約束： P2b：一旦一個具有 value v 的提案被批准（chosen），那麼以後任何 proposer 提出的提案必須具有 value v。 由於 acceptor 能接受的提案都必須由 proposer 提出，所以 P2b 蘊涵了 P2a，是一個更強的約束。 但是根據 P2b 難以提出實現手段。因此需要進一步加強 P2b。 假設一個編號為 m 的 value v 已經獲得批准（chosen），來看看在什麼情況下對任何編號為 n（nm）的提案都含有 value v。因為 m 已經獲得批准（chosen），顯然存在一個 acceptors 的多數派 C，他們都接受（accept）了 v。考慮到任何多數派都和 C 具有至少一個公共成員，可以找到一個蘊涵 P2b 的約束 P2c： P2c：如果一個編號為 n 的提案具有 value v，該提案被提出（issued），那麼存在一個多數派，要麼他們中所有人都沒有接受（accept）編號小於 n 的任何提案，要麼他們已經接受（accept）的所有編號小於 n 的提案中編號最大的那個提案具有 value v。 可以用數學歸納法證明 P2c 蘊涵 P2b： 假設具有 value v 的提案 m 獲得批准，當 nm1 時，採用反證法，假如提案 n 不具有 value v，而是具有 value w，根據 P2c，則存在一個多數派 S1，要麼他們中沒有人接受過編號小於 n 的任何提案，要麼他們已經接受的所有編號小於 n 的提案中編號最大的那個提案是 value w。由於 S1 和透過提案 m 時的多數派 C 之間至少有一個公共的 acceptor，所以以上兩個條件都不成立，匯出矛盾從而推翻假設，證明了提案 n 必須具有 value v； 若（m1）（N1）所有提案都具有 value v，採用反證法，假如新提案 N 不具有 value v，而是具有 value w 根據 P2c，則存在一個多數派 S2，要麼他們沒有接受過 m（N1）中的任何提案，要麼他們已經接受的所有編號小於 N 的提案中編號最大的那個提案是 value w。由於 S2 和透過 m 的多數派 C 之間至少有一個公共的 acceptor，所以至少有一個 acceptor 曾經接受了 m，從而也可以推出 S2 中已接受的所有編號小於 n 的提案中編號最大的那個提案的編號範圍在 m（N1）之間，而根據初始假設，m（N1）之間的所有提案都具有 value v，所以 S2 中已接受的所有編號小於 n 的提案中編號最大的那個提案肯定具有 value v，匯出矛盾從而推翻新提案 N 不具有 value v 的假設。根據數學歸納法，我們證明了若滿足 P2c，則 P2b 一定滿足。 P2c 是可以透過訊息傳遞模型實現的。另外，引入了 P2c 後，也解決了前文提到的 P1 不完備的問題。 演算法的內容 要滿足 P2c 的約束，proposer 提出一個提案前，首先要和足以形成多數派的 acceptors 進行通訊，獲得他們進行的最近一次接受（accept）的提案（prepare 過程），之後根據回收的資訊決定這次提案的 value，形成提案開始投票。當獲得多數 acceptors 接受（accept）後，提案獲得批准（chosen），由 acceptor 將這個訊息告知 learner。這個簡略的過程經過進一步細化後就形成了 Paxos 演算法。 在一個 paxos 例項中，每個提案需要有不同的編號，且編號間要存在全序關係。可以用多種方法實現這一點，例如將序數和 proposer 的名字拼接起來。如何做到這一點不在 Paxos 演算法討論的範圍之內。 如果一個沒有 chosen 過任何 proposer 提案的 acceptor 在 prepare 過程中回答了一個 proposer 針對提案 n 的問題，但是在開始對 n 進行投票前，又接受（accept）了編號小於 n 的另一個提案（例如 n1），如果 n1 和 n 具有不同的 value，這個投票就會違背 P2c。因此在 prepare 過程中，acceptor 進行的回答同時也應包含承諾：不會再接受（accept）編號小於 n 的提案。這是對 P1 的加強： P1a：當且僅當 acceptor 沒有回應過編號大於 n 的 prepare 請求時，acceptor 接受（accept）編號為 n 的提案。 現在已經可以提出完整的演算法了。 決議的提出與批准 透過一個決議分為兩個階段： prepare 階段： proposer 選擇一個提案編號 n 並將 prepare 請求傳送給 acceptors 中的一個多數派； acceptor 收到 prepare 訊息後，如果提案的編號大於它已經回覆的所有 prepare 訊息 回覆訊息表示接受 accept，則 acceptor 將自己上次接受的提案回覆給 proposer，並承諾不再回復小於 n 的提案； 批准階段： 當一個 proposer 收到了多數 acceptors 對 prepare 的回覆後，就進入批准階段。它要向回覆 prepare 請求的 acceptors 傳送 accept 請求，包括編號 n 和根據 P2c 決定的 value（如果根據 P2c 沒有已經接受的 value，那麼它可以自由決定 value）。 在不違背自己向其他 proposer 的承諾的前提下，acceptor 收到 accept 請求後即批准這個請求。 這個過程在任何時候中斷都可以保證正確性。例如如果一個 proposer 發現已經有其他 proposers 提出了編號更高的提案，則有必要中斷這個過程。因此為了最佳化，在上述 prepare 過程中，如果一個 acceptor 發現存在一個更高編號的提案，則需要通知 proposer，提醒其中斷這次提案。 例項 用實際的例子來更清晰地描述上述過程： 有 A1 A2 A3 A4 A5 5 位議員，就稅率問題進行決議。議員 A1 決定降稅率，因此它向所有人發出一個草案。這個草案的內容是： 現有的稅率是什麼？如果沒有決定，我來決定一下。提出時間：本屆議會第 3 年 3 月 15 日；提案者：A1 在最簡單的情況下，沒有人與其競爭；資訊能及時順利地傳達到其它議員處。 於是，A2A5 回應： 我已收到你的提案，等待最終批准 而 A1 在收到 2 份回覆後就釋出最終決議： 稅率已定為 10 新的提案不得再討論本問題。 這實際上退化為二階段提交協議。 現在我們假設在 A1 提出提案的同時，A5 決定將稅率定為 20 現有的稅率是什麼？如果沒有決定，我來決定一下。時間：本屆議會第 3 年 3 月 16 日；提案者：A5 草案要透過侍從送到其它議員的案頭 A1 的草案將由 4 位侍從送到 A2A5 那裡。現在，負責 A2 和 A3 的侍從將草案順利送達，負責 A4 和 A5 的侍從則不上班 A5 的草案則順利的送至 A4 和 A3 手中。 現在，A1 A2 A3 收到了 A1 的提案；A4 A3 A5 收到了 A5 的提案。按照協議，A1 A2 A4 A5 將接受他們收到的提案，侍從將拿著 我已收到你的提案，等待最終批准 的回覆回到提案者那裡。 而 A3 的行為將決定批准哪一個。 在討論之前我們要明確一點，提案是全域性有序的。在這個示例中，是說每個提案提出的日期都不一樣。即第 3 年 3 月 15 日只有 A1 的提案；第 3 年 3 月 16 日只有 A5 的提案。不可能在某一天存在兩個提案。 情況一 假設 A1 的提案先送到 A3 處，而 A5 的侍從決定放假一段時間。於是 A3 接受並派出了侍從 A1 等到了兩位侍從，加上它自己已經構成一個多數派，於是稅率 10 將成為決議 A1 派出侍從將決議送到所有議員處： 稅率已定為 10 新的提案不得再討論本問題。 A3 在很久以後收到了來自 A5 的提案。由於稅率問題已經討論完畢，開始討論某些議員在第 3 年 3 月 17 日提出的議案。因此這個 3 月 16 日提出的議案他不去理會。他自言自語地抱怨一句： 這都是老問題了，沒有必要討論了。 情況二 依然假設 A1 的提案先送到 A3 處，但是這次 A5 的侍從不是放假了，只是中途耽擱了一會。這次，A3 依然會將 接受 回覆給 A1 但是在決議成型之前它又收到了 A5 的提案。則： 1 如果 A5 提案的提出時間比 A1 的提案更晚一些，這裡確實滿足這種情況，因為 3 月 16 日晚於 3 月 15 日。，則 A3 回覆： 我已收到您的提案，等待最終批准，但是您之前有人提出將稅率定為 10 請明察。 於是，A1 和 A5 都收到了足夠的回覆。這時關於稅率問題就有兩個提案在同時進行。但是 A5 知道之前有人提出稅率為 10 於是 A1 和 A5 都會向全體議員廣播： 稅率已定為 10 新的提案不得再討論本問題。 共識到了保證。 2 如果 A5 提案的提出時間比 A1 的提案更早一些。假設 A5 的提案是 3 月 14 日提出，則 A3 直接不理會。 A1 不久後就會廣播稅率定為 10 決議的釋出 一個顯而易見的方法是當 acceptors 批准一個 value 時，將這個訊息傳送給所有 learners。但是這個方法會導致訊息量過大。 由於假設沒有 Byzantine failures，learners 可以透過別的 learners 獲取已經透過的決議。因此 acceptors 只需將批准的訊息傳送給指定的某一個 learner，其他 learners 向它詢問已經透過的決議。這個方法降低了訊息量，但是指定 learner 失效將引起系統失效。 因此 acceptors 需要將 accept 訊息傳送給 learners 的一個子集，然後由這些 learners 去通知所有 learners。 但是由於訊息傳遞的不確定性，可能會沒有任何 learner 獲得了決議批准的訊息。當 learners 需要了解決議透過情況時，可以讓一個 proposer 重新進行一次提案。注意一個 learner 可能兼任 proposer。 Progress 的保證 根據上述過程當一個 proposer 發現存在編號更大的提案時將終止提案。這意味著提出一個編號更大的提案會終止之前的提案過程。如果兩個 proposer 在這種情況下都轉而提出一個編號更大的提案，就可能陷入活鎖，違背了 Progress 的要求。一般活鎖可以透過 隨機睡眠 重試 的方法解決。這種情況下的解決方案是選舉出一個 leader，僅允許 leader 提出提案。但是由於訊息傳遞的不確定性，可能有多個 proposer 自認為自己已經成為 leader。Lamport 在 The PartTime Parliament 一文中描述並解決了這個問題。 MultiPaxos Paxos 的典型部署需要一組連續的被接受的值（value），作為應用到一個分散式狀態機的一組命令。如果每個命令都透過一個 Basic Paxos 演算法例項來達到一致，會產生大量開銷。 如果 Leader 是相對穩定不變的，第 1 階段就變得不必要。 這樣，系統可以在接下來的 Paxos 演算法例項中，跳過第 1 階段，直接使用同樣的 Leader。 為了實現這一目的，在同一個 Leader 執行每輪 Paxos 演算法時，提案編號 I 每次遞增一個值，並與每個值一起傳送。MultiPaxos 在沒有故障發生時，將訊息延遲 從 propose 階段到 learn 階段 從 4 次延遲降低為 2 次延遲。 MultiPaxos 中訊息流的圖形表示 MultiPaxos 沒有失敗的情況 在下面的圖中，只顯示了基本 Paxos 協議的一個例項 或 執行 和一個初始 Leader Proposer。注意，MultiPaxos 使用幾個 Basic Paxos 的例項。 Client Proposer Acceptor Learner First Request X Request X Prepare N XXX Promise NIVaVbVc X AcceptNIV XXX Accepted NIV XX Response 式中 V Va Vb Vc 中最新的一個。 跳過階段 1 時的 MultiPaxos 在這種情況下，Basic Paxos 的後續例項 由 I1 表示 使用相同的 Leader，因此，包含在 Prepare 和 Promise 的階段 1 Basic Paxos 協議的這些後續例項 將被跳過。注意，這裡要求 Leader 應該是穩定的，即它不應該崩潰或改變。 Client Proposer Acceptor Learner Following Requests X Request X AcceptNI1W XXX Accepted NI1W XX Response FastPaxos FastPaxos 將 BasicPaxos 進行了推廣，以減少端到端訊息延遲。在 BasicPaxos 中，從客戶端發起請求開始，到 Learn 階段結束的訊息延遲是 3 個訊息延遲。而 FastPaxos 允許 2 個訊息延遲，但要求： 1 系統由 3f 1 個 Acceptor 組成，以容忍最多 f 個錯誤 而不是 BasicPaxos 的 2f1； 2 客戶端需要直接將請求傳送到多個目標。 直觀上，如果 Leader 沒有提議任何 value，那麼客戶可以直接傳送 Accept 訊息到向接收方發。Acceptor 會像 BasicPaxos 一樣執行，向 Leader 和每個 Learner 傳送 Accepted 的訊息，從而實現從客戶端到 Learner 的兩訊息延遲。 如果 Leader 檢測到衝突，它透過發起新一輪投票，併發送 Accept 訊息來解決衝突，通常是一個 Accepted 訊息。這種有協調者參與的衝突恢復機制需要 4 個從客戶端到 Learner 的訊息延遲。 如果 Leader 提前指定了一種衝突恢復機制，就可以實現另一種最佳化，它允許 Acceptors 自己執行衝突恢復。因此，無協調的衝突恢復可能實現三個訊息延遲 如果所有的 Learner 都是接收者，那麼只有兩個訊息延遲。 訊息流 FastPaxos，無衝突 Client Leader Acceptor Learner X Any NIRecovery X AcceptNIW XXXX Accepted NIW XX Response W 訊息流：FastPaxos，衝突的建議 有協調這參與的衝突恢復。注意：協議沒有指定如何處理被丟棄的客戶端請求。 Client Leader Acceptor Learner Concurrent conflicting proposals received in different order by the Acceptors X AcceptNIV X AcceptNIW Acceptors disagree on value XX Accepted NIV XX Accepted NIW Detect collision recover X AcceptN1IW XXXX Accepted N1IW XX Response W 無協調者參與的相沖突恢復： Client Leader Acceptor Learner X Any NIRecovery Concurrent conflicting proposals received in different order by the Acceptors X AcceptNIV X AcceptNIW Acceptors disagree on value XX Accepted NIV XX Accepted NIW Detect collision recover XXXX Accepted N1IW XX Response W 訊息流：無協調者的衝突恢復、角色崩潰的 FastPaxos 合併的 AcceptorLearner Client Servers X Any NIRecovery Concurrent conflicting proposals received in different order by the Servers X AcceptNIV X AcceptNIW Servers disagree on value XX Accepted NIV XX Accepted NIW Detect collision recover XXXX Accepted N1IW XXXX Response W 應用 微軟公司為簡化的 Paxos 演算法申請了專利。但專利中公開的技術和本文所描述的不盡相同。 谷歌公司（Google 公司）在其中應用了 MultiPaxos 演算法。Chubby lock 應用於大表（Bigtable），後者在谷歌公司所提供的各項服務中得到了廣泛的應用。 谷歌公司（Google 公司）在其分散式資料庫 spanner 中使用 MultiPaxos 作為分散式共識保證的基礎元件 Apache ZooKeeper 使用一個類 MultiPaxos 的共識演算法作為底層儲存協同的機制。 參考文獻 外部連結 The PartTime Parliament Lamport 於 1998 年發表在 ACM Transactions on Computer Systems。 註：這是該演算法第一次公開發表。 Paxos Made Simple，2001 年。 註：Lamport 覺得同行無法接受他的幽默感，於是用容易接受的方法重新表述了一遍。 Pinewiki 對 Paxos 演算法的介紹 Category 分散式計算 Category 演算法