1. 程式人生 > >分散式系統原理 之8 Paxos協議

分散式系統原理 之8 Paxos協議

分散式系統原理

Paxos協議

1. 簡介

Paxos 協議是少數在工程實踐中證實的強一致性、高可用的去中心化分散式協議。

Paxos 協議的流程較為複雜,但其基本思想卻不難理解,類似於人類社會的投票過程。Paxos 協議中,有一組完全對等的參與節點(稱為 accpetor),這組節點各自就某一事件做出決議,如果某個決議獲得了超過半數節點的同意則生效。Paxos 協議中只要有超過一半的節點正常,就可以工作,能很好對抗宕機、網路分化等異常情況。

2. 協議描述

2.1 節點角色

Paxos 協議中,有三類節點:

Proposer:提案者。Proposer 可以有多個,Proposer 提出議案(value)。所謂 value,在工程中可以是任何操作,例如“修改某個變數的值為某個值”、“設定當前 primary 為某個節點”等等。Paxos協議中統一將這些操作抽象為 value。不同的 Proposer 可以提出不同的甚至矛盾的 value,例如某個Proposer 提議“將變數 X 設定為 1”,另一個 Proposer 提議“將變數 X 設定為 2”,但對同一輪 Paxos過程,最多隻有一個 value 被批准。

Acceptor:批准者。Acceptor 有 N 個,Proposer 提出的 value 必須獲得超過半數(N/2+1)的 Acceptor批准後才能通過。Acceptor 之間完全對等獨立。

Learner:學習者。Learner 學習被批准的 value。所謂學習就是通過讀取各個 Proposer 對 value的選擇結果,如果某個 value 被超過半數 Proposer 通過,則 Learner 學習到了這個 value。回憶(2.4 )不難理解,這裡類似 Quorum 機制,某個 value 需要獲得 W=N/2 + 1 的 Acceptor 批准,從而學習者需要至少讀取 N/2+1 個 Accpetor,至多讀取 N 個 Acceptor 的結果後,能學習到一個通過的 value。

上述三類角色只是邏輯上的劃分,實踐中一個節點可以同時充當這三類角色。

2.2 流程描述

Paxos 協議一輪一輪的進行,每輪都有一個編號。每輪 Paxos 協議可能會批准一個 value,也可能無法批准一個 value。如果某一輪 Paxos 協議批准了某個 value,則以後各輪 Paxos 只能批准這個value。++上述各輪協議流程組成了一個 Paxos 協議例項,即一次 Paxos 協議例項只能批准一個 value,這也是 Paxos 協議強一致性的重要體現。++

每輪 Paxos 協議分為階段,++準備階段和批准階段++,在這兩個階段 Proposer 和 Acceptor 有各自的處理流程。

流程 2.8.1:Proposer 的流程
(準備階段)
1. 向所有的 Acceptor 傳送訊息“Prepare(b)”;  這裡 b 是 Paxos的輪數,每輪遞增
2. 如果收到任何一個 Acceptor 傳送的訊息“Reject(B)”,則對於這個 Proposer 而言本輪 Paxos 失敗,將輪數 b 設定為 B+1 後重新步驟 1;

(批准階段,根據收到的 Acceptor 的訊息作出不同選擇)
3. 如果接收到的 Acceptor 的“Promise(b, v_i)”訊息達到 N/2+1 個(N 為 Acceptor 總數,除法取整,下同);v_i 表示 Acceptor 最近一次在 i 輪批准過 value v。
    3.1 如果收到的“Promise(b, v)”訊息中,v 都為空,Proposer 選擇一個 value v,向所有 Acceptor廣播 Accept(b, v);
    3.2 否則,在所有收到的“Promise(b, v _ i)”訊息中,選擇 i 最大的 value v,向所有 Acceptor 廣播訊息 Accept(b,v);
4. 如果收到 Nack(B),將輪數 b 設定為 B+1 後重新步驟 1;
流程 2.8.2:Accpetor 流程
(準備階段)
1. 接受某個 Propeser 的訊息 Prepare(b)。引數 B 是該 Acceptor 收到的最大 Paxos 輪數編號;V 是 Acceptor 批准的 value,可以為空
    1.1 如果 b>B,回覆 Promise(b, V_B),設定 B=b;表示保證不再接受編號小於 b 的提案。
    1.2 否則,回覆 Reject(B)。

(批准階段)
2. 接收 Accept(b, v),
    2.1 如果 b < B, 回覆 Nack(B),暗示 proposer 有一個更大編號的提案被這個 Acceptor 接收了。
    2.2 否則設定 V=v。表示這個 Acceptor 批准的 Value 是 v。廣播 Accepted 訊息。

3. 例項

3.1 基本例子

基本例子裡有 5 個 Acceptor,1 個 Proposer,不存在任何網路、宕機異常。我們著重考察各個Accpetor 上變數 B 和變數 V 的變化,及 Proposer 上變數 b 的變化。

  1. 初始狀態

    - Acceptor 1 Acceptor 2 Acceptor 3 Acceptor 4 Acceptor 5
    B 0 0 0 0 0
    V NULL NULL NULL NULL NULL
    - Proposer 1
    b 1
  2. Proposer 向所有 Accpetor 傳送“Prepare(1)”,所有 Acceptor 正確處理,並回復 Promise(1, NULL)

    - Acceptor 1 Acceptor 2 Acceptor 3 Acceptor 4 Acceptor 5
    B 1 1 1 1 1
    V NULL NULL NULL NULL NULL
    - Proposer 1
    b 1
  3. Proposer 收到 5 個 Promise(1, NULL),滿足多餘半數的 Promise 的 value 為空,此時傳送Accept(1, v1 ),其中 v1 是 Proposer 選擇的 Value。

    - Acceptor 1 Acceptor 2 Acceptor 3 Acceptor 4 Acceptor 5
    B 1 1 1 1 1
    V v1 v1 v1 v1 v1
  4. 此時,v 1 被超過半數的 Acceptor 批准,v1 即是本次 Paxos 協議例項批准的 Value。如果 Learner學習 value,學到的只能是 v1

3.2 批准的 Value 無法改變

在同一個Paxos例項中,批准的Value是無法改變的,即使後續Proposer以更高的序號發起Paxos協議也無法改變 value。

  1. 例如,某次 Paxos 協議執行後,Acceptor 的狀態是:

    - Acceptor 1 Acceptor 2 Acceptor 3 Acceptor 4 Acceptor 5
    B 3 3 3 2 2
    V v1 v1 v1 NULL NULL

    5 個 Acceptor 中,有 3 個已經在第三輪 Paxos 協議批准了 v 1 作為 value。其他兩個 Acceptor 的 V為空,這可能是因為 Proposer 與這兩個 Acceptor 的網路中斷或者這兩個 Acceptor 宕機造成的。

  2. 此時,即使有新的 Proposer 發起協議,也無法改變結果。假設 Proposer 傳送“prepare(4)消 息”,由於 4 大於所有的 Accpetor 的 B 值,所有收到 prepare 訊息的 Acceptor 回覆 promise 訊息。但 前三個 Acceptor 只能回覆 promise(4, v 1 _3),後兩個 Acceptor 回覆 promise(4, NULL)。

    - Acceptor 1 Acceptor 2 Acceptor 3 Acceptor 4 Acceptor 5
    B 4 4 4 4 4
    V v1 v1 v1 NULL NULL
  3. 此時,Proposer 可能收到若干個 Acceptor 傳送的 promise 訊息,沒有收到的 promise 訊息可能是網路異常造成的。無論如何,Proposer 要收到至少 3 個 Acceptor 的 promise 訊息後才滿足協議中大於半數的約束,才能傳送accpet訊息。這3個promise訊息中,至少有1個訊息是promise(4, v 1 _3),至多 3 個訊息都是 promise(4,v 1 _3)。另一方面,Proposer 始終不可能收到 3 個 promise(4, NULL)訊息,最多收到 2 個。綜上,按協議流程,Proposer 傳送的 accept 訊息只能是“accept(4, v 1 )”而不能自由選擇 value。

    無論這個 accept 訊息是否被各個 Acceptor 接收到,都無法改變 v 1 是被批准的 value 這一事實。即從全域性看,有且只有 v 1 是滿足超過多數 Acceptor 批准的 value。例如,假設 accept(4, v 1 )訊息被Acceptor 1、Acceptor2、Acceptor4 收到,那麼狀態變為:

    - Acceptor 1 Acceptor 2 Acceptor 3 Acceptor 4 Acceptor 5
    B 4 4 4 4 4
    V v1 v1 v1 v1 NULL

    從這個例子我們可以看到一旦一個 value 被批准,此後永遠只能批准這個 value。

3.3 一種不可能出現的狀態

Paxos 協議的核心就在與“批准的 value 無法改變”,這也是整個協議正確性的基礎,為了更好的理解後續對 Paxos 協議的證明。這裡再看一種看似可能,實際違反協議的狀態,這種狀態也是後續反證法證明協議時的一種錯誤狀態。

- Acceptor 1 Acceptor 2 Acceptor 3 Acceptor 4 Acceptor 5
B 1 1 1 1 1
V v1 v1 v1 v2 v2

上述狀態中,3 個輪次為 1 的 Acceptor 的 value 為 v 1 ,2 個輪次更高的 Acceptor 的 value 為 v 1 。此時被批准的 value 是 v 1 。

假設此時發生新的一輪 b=3 的 Paxos 過程,Proposer 有可能收到 Acceptor 3、4、5 發出的 3 個promise 訊息分別為“promise(1, v 1 _1)”,“promise(2, v 2 _2)” “promise(2, v 2 _2)”。按協議,proposer選擇 value 編號最大的 promise 訊息,即 v 2 _2 的 promise 訊息,傳送訊息“Accept(3, v 2 )”,從而使得最終的批准的 value 成為 v 2 。就使得批准的 value 從 v 1 變成了 v 2 。

上述假設看似正確,其實不可能發生。這是因為本節中給出的初始狀態就是不可能出現的。這是因為,要到達成上述狀態,發起 prepare(2)訊息的 proposer 一定成功的向 Acceptor 4、Acceptor 5傳送了 accept(2, v 2 )。但傳送 accept(2, v 2 )的前提只能是 proposer 收到了 3 個“promise(2, NULL)”訊息。 然而,從狀態我們知道,在 b=1 的那輪 Paxos 協議裡,已經有 3 個 Acceptor 批准了 v 1 ,這 3 個Acceptor 在 b=2 時發出的訊息只能是 promise(2,v 1_ 1),從而造成 proposer 不可能收到 3 個“promise(2,NULL)”,至多隻能收到 2 個“promise(2, NULL)”。另外,只要 proposer 收到一個“promise(2,v 1 _1)”,其傳送的 accept 訊息只能是 accept(2, v 1 )。

從這個例子我們看到 Prepare 流程中的第 3 步是協議中最為關鍵的一步,它的存在嚴格約束了“批准的 value 無法改變”這一事實。在後續協議推導中我們將看到這一步是如何被設計出來的。

3.4 節點異常

這裡給一個較為複雜的異常狀態下Paxos 執行例項。本例子中有 5 個Acceptor和 2 個 Proposer。

  1. Proposer 1 發起第一輪 Paxos 協議,然而由於異常,只有 2 個 Acceptor 收到了 prepare(1)訊息。

    - Acceptor 1 Acceptor 2 Acceptor 3 Acceptor 4 Acceptor 5
    B 1 1 0 0 0
    V NULL NULL NULL NULL NULL
  2. Proposer 1 只收到 2 個 promise 訊息,無法發起 accept 訊息;此時,Proposer 2 發起第二輪 Paxos協議,由於異常,只有 Acceptor 1、3、4 處理了 prepare 訊息,併發送 promise(2, NULL)訊息

    - Acceptor 1 Acceptor 2 Acceptor 3 Acceptor 4 Acceptor 5
    B 2 1 2 2 0
    V NULL NULL NULL NULL NULL
  3. Proposer 2 收到了 Acceptor 1、3、4 的 promise(2, NULL) 訊息,滿足協議超過半數的要求,選擇了 value 為 v 1 ,廣播了 accept(2, v 1 )的訊息。由於異常,只有 Accptor 3、4 處理了這個訊息。

    - Acceptor 1 Acceptor 2 Acceptor 3 Acceptor 4 Acceptor 5
    B 2 1 2 2 0
    V NULL NULL v1 v1 NULL
  4. Proposer 1 以b=3發起新一輪的Paxos協議,由於異常,只有Acceptor 1、2、3、5處理了prepare(3)訊息。

    - Acceptor 1 Acceptor 2 Acceptor 3 Acceptor 4 Acceptor 5
    B 3 3 3 2 3
    V NULL NULL v1 v1 NULL
  5. 由於異常,Proposer 1 只收到 Acceptor1、2、5 的 promise(3, NULL)的訊息,符合協議要求,Proposer 1 選擇 value 為 v 2 ,廣播 accept(3, v 2 )訊息。由於異常,這個訊息只被 Acceptor 1、2 處理。

    - Acceptor 1 Acceptor 2 Acceptor 3 Acceptor 4 Acceptor 5
    B 3 3 3 2 3
    V v2 v2 v1 v1 NULL

    當目前為止,沒有任何value被超過半數的Acceptor批准,所以Paxos協議尚沒有批准任何value。然而由於沒有 3 個 NULL 的 Acceptor,此時能被批准的 value 只能是 v 1 或者 v 2 其中之一。

  6. 此時 Proposer 1 以 b=4 發起新的一輪 Paxos 協議,所有的 Acceptor 都處理了 prepare(4)訊息。

    - Acceptor 1 Acceptor 2 Acceptor 3 Acceptor 4 Acceptor 5
    B 4 4 4 4 4
    V v2 v2 v1 v1 NULL
  7. 由於異常,Proposer 1 只收到了 Acceptor3 的 promise(4, v 1 _3)訊息、Acceptor4 的 promise(4,v 1 _2)、Acceptor5 的 promise(4, NULL)訊息,按協議要求,只能廣播 accept(4, v 1 )訊息。假設 Acceptor2、3、4 收到了 accept(4, v 1 )訊息。由於批准 v 1 的 Acceptor 超過半數,最終批准的 value 為 v 1 。

    - Acceptor 1 Acceptor 2 Acceptor 3 Acceptor 4 Acceptor 5
    B 4 4 4 4 4
    V v2 v1 v1 v1 NULL

4. 競爭及活鎖

從前面的例子不難看出,Paxos 協議的過程類似於“佔坑”,哪個 value 把超過半數的“坑”(Acceptor)佔住了,哪個 value 就得到批准了。

這個過程也類似於單機系統並行系統的加鎖過程。假如有這麼單機系統:系統內有 5 個鎖,有多個執行緒執行,每個執行緒需要獲得 5 個鎖中的任意 3 個才能執行後續操作,操作完成後釋放佔用的鎖。我們知道,上述單機系統中一定會發生“死鎖”。例如,3 個執行緒併發,第一個執行緒獲得 2 個鎖,第二個執行緒獲得 2 個鎖,第三個執行緒獲得 1 個鎖。此時任何一個執行緒都無法獲得 3 個鎖,也不會主動釋放自己佔用的鎖,從而造成系統死鎖。

但在 Paxos 協議過程中,雖然也存在著併發競爭,不會出現上述死鎖。這是因為,Paxos 協議引入了輪數的概念,高輪數的 paxos 提案可以搶佔低輪數的 paxos 提案。從而避免了死鎖的發生。然而這種設計卻引入了“活鎖”的可能,即 Proposer 相互不斷以更高的輪數提出議案,使得每輪 Paxos過程都無法最終完成,從而無法批准任何一個 value。

  1. Proposer 1 以 b=1 提起議案,傳送 prepare(1)訊息,各 Acceptor 都正確處理,迴應 promise(1,NULL)

    - Acceptor 1 Acceptor 2 Acceptor 3 Acceptor 4 Acceptor 5
    B 1 1 1 1 1
    V NULL NULL NULL NULL NULL
  2. Proposer 2 以 b=2 提起議案,傳送 prepare(2)訊息,各 Acceptor 都正確處理,迴應 promise(2,NULL)

    - Acceptor 1 Acceptor 2 Acceptor 3 Acceptor 4 Acceptor 5
    B 2 2 2 2 2
    V NULL NULL NULL NULL NULL
  3. Proposer 1 收到 5 個 promise(1, NULL)訊息,選擇 value 為 v 1 傳送 accept(1, v 1 )訊息,然而這個訊息被所有的 Acceptor 拒絕,收到 5 個 Nack(2)訊息。

  4. Proposer 1 以 b=3 提起議案,傳送 prepare(3)訊息,各 Acceptor 都正確處理,迴應 promise(3,NULL)

    - Acceptor 1 Acceptor 2 Acceptor 3 Acceptor 4 Acceptor 5
    B 3 3 3 3 3
    V NULL NULL NULL NULL NULL
  5. Proposer 2 收到 5 個 promise(2, NULL)訊息,選擇 value 為 v 2 傳送 accept(2, v 2 )訊息,然而這個訊息被所有的 Acceptor 拒絕,收到 5 個 Nack(3)訊息。

上述過程交替進行,則永遠無法批准一個 value,從而形成 Paxos 協議活鎖。Paxos 協議活鎖問題也是這個協議的主要問題。

參考:《分散式系統原理介紹》 - 劉傑