Paxos 協議:多狀態機的一致性解決方案

問題背景

正確理解二階段提交(Two-Phase Commit) 的文章中, 筆者解析了二階段提交協議是如何滿足一個分佈式原子性提交協議應該具有的性質。

二階段提交協議(Two-Phase Commit)出現的本質原因是, 分佈式系統中不同的結點有不同的功能, 不同功能背後對應的數據集不同, 不同功能又需要一定的協同性。

二階段協議中, 一個比較嚴重的問題是, 如果遇到結點宕機, 必須等到所有結點恢復以後, 協議才能繼續。 於是這裏就引出了如何提高分佈式系統可用性的問題

中心化架構帶來的單點失敗問題

正確理解二階段提交(Two-Phase Commit) 中提到的跨行轉賬場景爲例。 銀行A, 銀行B , 事務協調者 TC 都擁有並維護着各自的唯一的, 權威的賬目數據集。 任何一個結點停止工作都會導致其他結點也無法繼續工作。

所以我們希望提高分佈式系統的可用性。

複製數據

很自然的方案時將核心數據複製多份,由多個結點維護, 萬一有部分結點失效, 我們希望其他結點還能正常工作

數據一致性如何解決

只要存在多份數據,就一定會面臨如何解決數據一致性的問題。 當客戶通過請求修改了其中一份數據以後, 其他的數據該如何保持一致?

簡化問題爲多份狀態機

任意的服務器結點本質上都是一個狀態機。

  • 磁盤, 內存, CPU 緩存中的數據都屬於狀態
  • 通過指令, 狀態機的狀態發生變化
  • 用戶可以通過請求觸發狀態機執行特定的指令,從而進一步觸發狀態轉化

通過複製產生了多個位於不同結點的狀態機

  • 每一個狀態機副本必須接受到順序相同的指令集
  • 如果每一個指令所導致的狀態是確定的(非隨機), 副本狀態機在執行了相同的指令以後, 最終會達到相同的狀態。

如何確保副本收到相同順序的相同指令

首先可以指定一個特殊的副本結點作爲主結點

將其他的結點作爲這個主結點的備份

客戶統一將請求都發送給當前的主結點

主結點負責:

  • 爲接受到的客戶指令確定唯一排序
  • 把具有唯一順序的指令集發送給它的備份結點
  • 響應客戶

如何應對主結點宕機

顯而易見: 選出新的主結點

草稿方案

爲每一個結點標註一個編號, 當主結點宕機時, 現存結點中編號最小的成爲主結點

在主結點宕機後, 剩餘結點需要相互通信, 判斷哪些結點還存活

問題

  • 通信不可靠, 相互之間發送的數據可能發生丟失
    • 後果: 可能產生多個主結點
  • 通信可能存在時延
    • 後果: 可能產生多個主結點
  • 網絡分區(部分結點相互可通信, 另外一部分結點相互可通信)
    • 後果: 可能產生多個主結點

靈感:半數以上結點的兩個分區必有重合

要求: 至少要有超過半數以上的結點同意才能選出新的主結點

好處:多數結點所在的分區只能有一個, 如果存在兩個以上的分區選出了兩個主結點, 那麼多個分區中, 一定存在重合的結點, 重合的結點可以發現選出了多個主結點,進而“拉響警報”
在這裏插入圖片描述

Paxos: 視圖變化算法

理解 Paxos 的核心技巧是理解視圖的概念

首先明確如下的概念

  • 整個系統由多個狀態機副本結點組成
  • 多個結點構成一個結點集
  • 每個結點集都存在一個編號
  • 這個編號代表着結點集的共識, 每一個結點都知道這個值是多少。 可以將這個值理解爲整個結果集的一種狀態標誌
    • 例子:
      • 我們可以定義一個結點集,裏面包含 3 個結點(node A, node B ,node C), nodeA 是主結點
      • 進一步定義, 系統初始化後, 3 個結點的共識值爲 x
      • 進一步定義, 只要某一個結點在過去的 1 分鐘內, 沒有完整地收到所有其餘結點的心跳消息, 那麼可以認爲這個代表結點集共識的值被打破。 需要尋找新的共識值 y

由此引出視圖的定義:

  • 一個視圖(View) 由如下兩個元素構成
    • 視圖編號(View Number)
    • 結點集(set of nodes)
  • 提示: 請不要將視圖的編號理解爲結點集中主結點的 ID, 視圖的編號代表的是結點集的共識, 當一個視圖明確後, 確定唯一的主結點是非常簡單的事情, 例如可以直接定義結點集中, IP 地址最小的結點爲主結點。

按照視圖的定義, 視圖編號(View Number) + 結點集(set of nodes) 任意一個元素髮生了變化, 這個視圖也就變化了

當一個穩定的視圖, 因爲 視圖編號(View Number) 或 結點集(set of nodes) 的變化而被破壞時 , 我們就需要一個視圖變化算法, 幫我們確定, 一個視圖“打破”後, 下一個視圖應該是什麼。

Paxos 算法即是這個視圖變化算法的實現方式之一

Paxos 概覽

首先粗略展示 Paxos 的大致流程:

  • 一個(或多個結點) 決定要成爲 Leader
  • 想要成爲 Leader 的結點, 首先提出一個候選值(Proposed Value), 以期待大家對這個候選值可以達成共識
  • Leader 聯繫所有的參與結點, 嘗試收集到半數以上結點的同意回覆
    • 參與結點的含義:
      • 可以是一個預先配置好的結點集
      • 也可以是前一個視圖中的所有結點
  • 如果半數以上的人回覆了同意, 成功的達成了共識 !

Paxos 協議原來是這麼簡單的一個算法嗎 ?! 很遺憾, 並不是, 上面只是一個非常粗略的大致流程

  • 需要解決的問題:
    • 如果有兩個或更多結點同時決定要成爲 Leader 怎麼辦?
    • 如果出現了網絡分區隔離, 導致產生了兩個或更多 Leader 怎麼辦?
    • 如果決定要成爲 Leader 的結點在“說服” 了一部分結點後,自己宕機了怎麼辦?
    • 如果決定要成爲 Leader 的結點已經收集到了超過半數的同意意見
      • 還沒來及公佈結果, 然後自己宕機了
      • 向一部分結點送達了結果, 然後自己宕機了
    • 怎麼辦?

Paxos 詳解

  • 算法總共分3個階段
  • 如果算法執行到一半, 有結點宕機或者在發生響應等待超時, 算法可能需要重頭執行
  • 每一個運行 Paxos 算法的視圖中的任一結點維護一個狀態(該狀態由 4 個值構成):
    • n_a( n_accepted) 當前結點所認同的最大值 n ( 初始值爲 -1 )
    • v_a(value_accepted ) 與 n_a 一同接收到的值, 代表該結點當前所認同的視圖, 具體格式爲一對值: { 視圖編號, 結點集}
    • n_h (n_highest ) 所有已經收到的 Q1 類型的 message 中 n 的最大值 ( 初始值爲 -1 )
    • done, 該值爲 true 標識收到了某個 leader 發來的消息說多數結點共識已經達成, 現在可以使用新的視圖編號值(初始值爲 false

Paxos 第一階段(Phase 1)

  • 一個結點(可能多於一個結點)決定要成爲 Leader, 然後它就
    • 提出一個候選值 n
      • 這個 n 必須是唯一, 且大於所有該結點已經見過的候選值
      • 所以可以直接把已經見過候選值中最大值 n_h 作 +1 操作後, 再追加上該節點的 ID 標識 node_id 作爲新提出的候選值
    • 向所有結點(包括該結點自己)發送 Q1(n) message
      • Q1(n) 標識消息類型爲 Q1 , 消息中包含該節點新提出的候選值 n = n_known_max + 1 || node_id
  • 任意結點收到了 Q1(n) 的消息 ,且 n > n_h
    • n_h = n
    • 發送響應 R1(n_a,v_a)

Paxos 第二階段(Phase 2)

  • 如果 Leader 收到了半數以上結點(包含它自己)回覆的 R1(n,v) 響應消息

    • 如果一個或多個 R1(n,v)v 的值非空,
      • 找出其中 n 值最大的消息 R1(n_max, v)
      • 執行 v_a = R1(n_max, v).getV()
      • 執行 n_h = R1(n_max, v).getN()
    • 否則 Leader 就得自己產生一個 v_a
      • v_a = {舊的視圖編號+1, 目前能正常通信的結點集}
    • 發送 Q2(v_a, n_h)消息給所有回覆了 R1(n,v) 的結點
  • 如果任意結點收到了 Q2(n,v) 類型的消息 且消息中的 n >= n_h

    • 執行 n_h = n_a = n
    • 執行 v_a = v
    • 發送 R2() 類型的消息作爲 Q2 消息的響應
      • R2() 消息中沒有特別的內容, 單純就是 Q2 消息的響應

Paxos 第三階段 ( Phase 3)

  • 如果 leader 收到了半數以上結點回復的 R2() 消息
    • 發送 Q3() 消息給所有的參與結點
  • 如果任意結點收到了 Q3() 消息
    • 執行 done = true
      • 標識此時共識達成, 達成的共識視圖是 v_a
      • 主結點可以規定爲 v_a 中編號最小的那個結點

Paxos 通信超時應對方案

  • 所有的結點發送消息時, 都會設置一個等待超時時間。
  • 一旦發生了超時, 這個結點就宣佈自己要成爲 leader, 發起 Paxos 算法的第一階段 Phase 1

Paxos 圖解(一圖勝千言)

先看 Paxos 在最理想的情形下是如何正確執行的

首先假設系統初始狀態下有 5 個可以相互通信的,未達成共識的結點

在這裏插入圖片描述
然後 結點1 突然決定要站出來成爲 Leader, 先自行起草一個共識編號 n = 01
在這裏插入圖片描述初始化一個 Q1(n) 類型的消息, 將自己起草的這個編號 01 填進去, 發給Q1(01)所有參與結點, 包括自己
在這裏插入圖片描述所有收到 Q1(n) 消息的結點, 由於發現 n = 01 , 大於自己記錄的 n_h= -1 , 於是將 n_h 更新爲收到的 01
在這裏插入圖片描述

然後向 Leader 結點回復 R1(n_a, v_a)

在這裏插入圖片描述由於 Leader 結點收到超過半數(2.5)以上的 4 個結點的 R1 響應, 它進入了 Paxos 第二階段。

首先查看響應中是否有已經存在的共識內容 v , 結果發現 v 值都是空的

那 Leader 需要自行起草一個共識 v = { 視圖編號 + 結點集 } = { 1, {0,1,2,3,4} }
在這裏插入圖片描述
Leader 現在將自己提出的共識內容 v = {1,{0,1,2,3,4}} 填到 Q2 消息中, 發送給所有參與結點
在這裏插入圖片描述

任意結點收到了 Q2(n,v) 類型的消息 且消息中的 n >= n_h

  • 執行 n_h = n_a = n
  • 執行 v_a = v
    在這裏插入圖片描述
    然後回覆 R2 消息作爲 Q2 響應
    在這裏插入圖片描述

Leader 收到了半數以上結點回復的 R2 消息, 進入 Paxos 第三階段, 發送 Q3() 消息給所有的參與結點
在這裏插入圖片描述

  • 如果任意結點收到了 Q3() 消息
    • 執行 done = true
      • 標識此時共識達成, 達成的共識視圖是 v_a
      • 主結點可以規定爲 v_a 中編號最小的那個結點
        在這裏插入圖片描述
        至此 Paxos 協議執行完成, 所有結點都達成了共識內容{1,{0,1,2,3,4} } , 主結點就可以規定爲編號最小的 結點0

Paxos 幾個待思考的問題

上文的流程圖只是展示了, 只有一個 Leader, 且通信過程中無超時, 無結點宕機的情形, Paxos 成功使所有結點達成了共識。

但是, 根據 Paxos 協議的超時應對方案, 每當有結點通信超時時, 它就會自行嘗試成爲 Leader, 所以 Paxos 協議還得確保有多個 Leader 執行協議的正確性。

多個 Leader 出現的情形

加入 Paxos 協議發生通信超時等問題後, 就有可能產生不止一個 Leader 在發起 Paxos 協議

根據 Paxos 第一階段的協議, 每個 Leader 首先需要生成各自的 n, 生成方式爲:

  • n_known_max 作 +1 操作後, 再追加上該節點的 ID 標識 node_id

由於 n 生成方式中, 追加了各自結點的 ID, 兩個 Leader 生成的 n 肯定是不一樣的,假設:

  • Leader 1 生成了 n = 01
  • Leader 2 生成了 n = 02

由於 Paxos 協議的第一階段, Leader 只是將自己選出的 n 封裝在 Q1 消息中,向全部已知結點發送。

而收到 R1 消息的結點會簡單地根據 Q1(n) 中的 n 值與自己目前所見過的最大 n 的大小關係, 決定是否返回 R1

那我們可以假設, Leader 1 和 Leader 2 同時發送了 Q1, Leader 1 的消息 Q1 先到達了半數以上的結點, Leader 2 的消息 Q2 後到達了半數以上結點。

  • Tip: Leader 2 發送的 Q1 如果先到達了大部分結點, 這些結點就不會再向 Leader 1 回覆 R1 了, 我們也就不用擔心多 Leader 情形了

由於 Leader 2 提出的 n=02 比 Leader 1 提出的 n=01 要大, 那麼收到 Q1 消息的結點, 會先向 Leader 1 回覆 R1, 然後再向 Leader 2 回覆 R1 , 這樣兩個 Leader 都能收集到半數以上的 R1 消息。 成功通過 Phase 1 。

緊接着, Leader 1 和 Leader 2 會根據收集到的 R1 信息, 決定是否要提出一個共識, 或者使用 R2 中已經存在的共識。 這裏我們假設 Leader 1 和 Leader 2 收到的 R1 中 v 都爲 null 。 那麼他們會分別發送 Q2 給所有回覆了 R1 的結點。

這之後可能出現以下的情形:

  • 情形1: Leader 1 沒有收到半數以上結點的 R2 響應

    • 原因1 : 半數以上的結點都收到過了 Leader 2 的 Q1, 由於 Leader 2 中 Q1 中的 n 更大, 所以半數以上的結點都將自己維護的 n_h 更新爲了 02 , 這樣再收到來自 Leader 1 的 Q2 時, 不會再予以回覆, 至此, Leader 1 也就失去了繼續執行協議的能力, Paxos 協議運行到最終會獲得統一的共識
    • 原因2: Leader 1 目前已經處於一個被隔離開的網絡分區, 該分區中, 只有不到半數的結點可以相互通信。 至此, Leader 1 同樣失去繼續執行協議的能力。 Paxos 協議運行到最終會獲得唯一的視圖
  • 情形2: Leader 1 收到了半數以上結點的 R2 響應

    • Leader 1 就可能已經發送了 Q3 ! 注意, 收到了 Q3 的結點會直接將 Q3 中的共識 v 作爲新的共識, 並標識 Paxos 執行已經完成。 如果 Leader 1 和 Leader 2 在一個協議執行期間先後發送了共識內容不同的 Q3, 顯然會導致結點共識的混亂!
    • 但是, 仔細分析會發現, 如果 Leader 1 收到了半數以上的 R2, 那這些向 Leader 1 回覆 R2 的結點, 在收到 Leader 1 的 Q2 前, 肯定沒有收到過 Leader 2 的 Q1, 否則不會有人向 Leader 1 回覆 Q1
    • 因此, Leader 2 如果收到了來自了半數以上的 R1, 這半數以上的結點中, 必然有一個結點曾經收到過 Leader 1 的 Q1 , 這個結點向 Leader 2 回覆的 R1 就會是包含 Leader 1 挑選的共識 v
    • 這樣 Leader 2 就能感知到 Leader 1 提出的共識值 v
    • 進而 Leader 2 就會直接使用 Leader 1 的共識值 v 來發送 Q2 ,而不是新創建一個共識值
    • 至此, 就能確保, 協議最終達成的共識是 Leader 1 提出的共識!

協議執行過程中發生結點宕機

考慮 Leader 在發送 Q2 的過程中宕機後的情形

  • 一些結點會在超時收不到任何消息後, 主動跳出了, 作爲 Leader 發起 Paxos 協議的執行
  • 我們將宕機的 Leader 稱爲 Leader_old , 由於 Leader_old 還沒有發送 Q3 , 所以不需要擔心Leader_old 的突然宕機會導致 Paxos 協議達成的共識混亂
  • 我們將新出現的 Leader 稱爲 Leader_new, 如果 Leader_new 站出來以後, 提出的 n 值, 比 Leader_old 的 n值大, 那很好, 協議執行時, 結點都會響應 Leader_new
  • 如果 Leader_new 提出的 n 值, 比 Leader_old 的 n 值小, 那也不要緊, 收到過 Leader_Old 消息的結點都不會響應 Leader_new, 這樣必然會發生消息超時, 會有新的結點站出來, 最終最會有一個結點提出的 n 值比 Leader_old 的 n 值大

考慮 Leader 在成功發送 Q2 之後宕機的情形

  • 如果 Leader 是向少於半數的結點發送完 Q2 後宕機的
    • 結果和兩個 Leader 都成功進入了 Phase 2 的情形一樣
  • 如果 Leader 是向多於半數的結點發送完 Q2 後宕機的
    • 結果還是和有兩個 Leader 成功進入 Phase 2 的情形一樣

考慮參與結點中, 有結點在收到了 Q2 ,併發送 R2 之後發生宕機

  • 則需考慮該結點是否會重新啓動
  • 如果該結點會被重啓
    • 它必須從硬盤中恢復出來它曾經維護的 v_an_a
    • 因爲 Leader 可能在只向少數結點發送了 Q3 消息後就宕機了
    • 這個結點恰巧就是兩個含半數以上結點的網絡分區重合的唯一結點。
    • 新的 Leader 是隸屬於另外一個網絡分區中的結點, 它成功的收集了自己分區中的所有結點(假設恰好佔到 1/2 的結點) + 這個重啓結點的 R1 消息, 如果這個重啓結點沒有保存重啓前的 v_an_a , 就會導致新的 Leader 所在分區達成一個和老 Leader 所在分區不一樣的共識狀態, 違背 Paxos 協議。

總結

最初的目標:

  • 在一個存在多副本狀態機的系統中,即使有少數結點失效,系統也能繼續運行
  • 在每一次發生結點失效以後, 執行視圖變化算法 Paxos
  • 這樣可以使得系統中的多數狀態機達成新的共識狀態
  • 這樣, 系統中的每個結點都能有共同認可的主結點

注意 Paxos 並沒有討論, 如何在沒有結點宕機時, 確保多個副本狀態機的數據一致性。

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