分佈式協議之 Paxos 算法

什麼是 Paxos 算法?

Paxos 算法是萊斯利·蘭伯特於 1990 年提出的一種基於消息傳遞且具有高度容錯特性的共識(consensus)算法。

蘭伯特提出的 Paxos 算法包含 2 個部分:

  • 一個是 Basic Paxos 算法,描述的是多節點之間如何就某個值(提案 Value)達成共識;
  • 另一個是 Multi-Paxos 思想,描述的是執行多個 Basic Paxos 實例,就一系列值達成共識。

什麼是共識問題?

假設您有一個計算機的集合,並希望它們所有人都同意某件事。這就是共識。共識意味着達成共識。

共識在分佈式系統設計中經常出現。我們之所以有多種理由,需要達成共識:就誰可以訪問資源(互斥)達成共識,就誰在負責資源(選舉)達成共識,或者就一系列計算機之間的事件共同排序達成共識(例如,接下來要採取的措施,或狀態機複製)。

共識問題可以用一種基本的,通用的方式來陳述: 一個或多個系統可以提出一些值。我們如何獲得一組計算機以就這些提議的值之一達成一致?

用個場景問題來解釋一下:

假設我們要實現一個分佈式集羣,這個集羣是由節點 A、B、C 組成,提供只讀 KV 存儲服務。

創建只讀變量的時候,必須要對它進行賦值,而且這個值後續沒辦法修改。因此一個節點創建只讀變量後就不能再修改它了,所以所有節點必須要先對只讀變量的值達成共識,然後所有節點再一起創建這個只讀變量。

那麼,當有多個客戶端(比如客戶端 1、2)訪問這個系統,試圖創建同一個只讀變量(比如 X),客戶端 1 試圖創建值爲 3 的 X,客戶端 2 試圖創建值爲 7 的 X,這樣要如何達成共識,實現各節點上 X 值的一致呢?

在這裏插入圖片描述
擴展說一下要解決共識問題,它的算法要求。

共識算法的要求:

達成共識的問題是使一組流程一致地就單個結果達成共識。這種算法有四個要求:

  • 有效性。結果必須是至少一個過程提交的值。共識算法不能僅僅彌補一個值。
  • 統一協議。所有節點必須選擇相同的值。
  • 誠信。節點只能選擇一個值。也就是說,節點無法宣佈一個結果,而後改變主意。
  • 終止。也稱爲進度,每個節點最終都必須做出決定。

只要大多數進程都能正常工作,該算法就必須起作用,並且假定:

  • 某些處理器可能會發生故障。流程以故障停止或故障重新啓動的方式運行。
  • 網絡是異步的並且不可靠。郵件可能會丟失,重複或亂序接收。
  • 沒有拜占庭式的失敗。如果傳遞了一條消息,則該消息沒有損壞或惡意。

Basic Paxos 角色

在這裏插入圖片描述

  • 提議者(Proposer):收到客戶端請求後,發起二階段提交,提議一個值,進行共識協商。

  • 接受者(Acceptor):投票協商和存儲數據,對提議的值進行投票,並接受達成共識的值,存儲保存。

  • 學習者(Learner):存儲數據,不參與共識協商,只接受達成共識的值,存儲保存。

一個節點(或進程)可以身兼多個角色。比如一個 3 節點的集羣,1 個節點收到了請求,那麼該節點將作爲提議者發起二階段提交,然後這個節點和另外 2 個節點一起作爲接受者進行共識協商,就像下圖的樣子:

在這裏插入圖片描述

Basic Paxos 如何達成共識?

在 Basic Paxos 中,蘭伯特使用提案代表一個提議。在提案中除了提案編號,還包含了提議值。

使用 [n, v] 表示一個提案,其中 n 爲提案編號,v 爲提議值。

假設客戶端 1 的提案編號爲 1,客戶端 2 的提案編號爲 5,並假設節點 A、B 先收到來自客戶端 1 的準備請求,節點 C 先收到來自客戶端 2 的準備請求。

準備(Prepare)階段:

提議者詢問所有工作接受者是否有人已經收到提議。如果答案是否定的,請提出一個值。

首先客戶端 1、2 作爲提議者,分別向所有接受者發送包含提案編號的準備請求:

在這裏插入圖片描述
當節點 A、B 收到提案編號爲 1 的準備請求,節點 C 收到提案編號爲 5 的準備請求後,將進行這樣的處理:

在這裏插入圖片描述

由於之前沒有通過任何提案,所以節點 A、B 將返回一個 “尚無提案”的響應。也就是說節點 A 和 B 在告訴提議者,我之前沒有通過任何提案呢,並承諾以後不再響應提案編號小於等於 1 的準備請求,不會通過編號小於 1 的提案。

節點 C 也是如此,它將返回一個 “尚無提案”的響應,並承諾以後不再響應提案編號小於等於 5 的準備請求,不會通過編號小於 5 的提案。

當節點 A、B 收到提案編號爲 5 的準備請求,和節點 C 收到提案編號爲 1 的準備請求的時候,將進行這樣的處理過程:

在這裏插入圖片描述
當節點 A、B 收到提案編號爲 5 的準備請求的時候,因爲提案編號 5 大於它們之前響應的準備請求的提案編號 1,而且兩個節點都沒有通過任何提案,所以它將返回一個 “尚無提案”的響應,並承諾以後不再響應提案編號小於等於 5 的準備請求,不會通過編號小於 5 的提案。

當節點 C 收到提案編號爲 1 的準備請求的時候,由於提案編號 1 小於它之前響應的準備請求的提案編號 5,所以丟棄該準備請求,不做響應。

準備階段做的事情:如果準備請求的提案編號,小於等於接受者已經響應的準備請求的提案編號,那麼接受者將承諾不響應這個準備請求。

接受(Accept)階段:

客戶端 1、2 在收到大多數節點的準備響應之後,會分別發送接受請求:

在這裏插入圖片描述
當客戶端 1 收到大多數的接受者(節點 A、B)的準備響應後,根據響應中提案編號最大的提案的值,設置接受請求中的值。因爲該值在來自節點 A、B 的準備響應中都爲空 (“尚無提案”),所以就把自己的提議值 3 作爲提案的值,發送接受請求[1, 3]。

當客戶端 2 收到大多數的接受者的準備響應後(節點 A、B 和節點 C),根據響應中提 案編號最大的提案的值,來設置接受請求中的值。因爲該值在來自節點 A、B、C 的準備響應中都爲空(“尚無提案”),所以就把自己的提議值 7 作爲提案的值,發送接受請求[5, 7]。

當三個節點收到 2 個客戶端的接受請求時,會進行這樣的處理:

在這裏插入圖片描述

當節點 A、B、C 收到接受請求[1, 3]的時候,由於提案的提案編號 1 小於三個節點承諾能通過的提案的最小提案編號 5,所以提案[1, 3]將被拒絕。

當節點 A、B、C 收到接受請求[5, 7]的時候,由於提案的提案編號 5 不小於三個節點承諾能通過的提案的最小提案編號 5,所以就通過提案 [5, 7],也就是接受了值 7,三個節點就 X 值爲 7 達成了共識。

接收階段做的事情:

  • 如果接受請求中的提案的提案編號,小於接受者已經響應的準備請求的提案編號,那麼接受者將承諾不通過這個提案。
  • 如果接受者之前有通過提案,那麼接受者將承諾,會在準備請求的響應中,包含已經通過的最大編號的提案信息。

深入理解

如果節點 A、B 已經通過了提案[5, 7],節點 C 未通過任何提案,那麼當客戶端 3 提案編號爲 9 時,通過 Basic Paxos 執行“SET X = 6”,最終三個節點上 X 值是多少呢?

最終節點值應該是[9,7],過程如下:

1、在準備階段,節點 C 收到客戶端 3 的準備請求[9,6],因爲節點 C 未收到任何提案,所以返回“尚無提案”的響應。這時如果節點 C 收到了之前客戶端的準備請求[5,7],根據提案編號 5 小於它之前響應的準備請求的提案編號9,會丟棄該準備請求。

2、客戶端 3 發送準備請求[9,6] 給節點 A,B,這時因爲節點 A,B 已經通過了提案[5,7],節點 A,B 會返回 [5,7] 給客戶端 3。

3、當客戶端 3 收到大多數的接受者的準備響應後(節點 A、B 和節點 C),根據響應中提案編號最大的提案的值,來設置接受請求中的值。因爲來自節點 A、B 的準備響應中爲[5, 7]和 C 的"尚無提案",所以就把節點 A、B 的響應值 7 作爲提案的值,發送接受請求[9, 7]。

4、當節點 A、B、C 收到接受請求[9, 7]的時候,由於提案的提案編號 9 不小於三個節點承諾能通過的提案的最小提案編號 9,所以就通過提案[9, 7],也就是接受了值 7,三個節點就 X 值爲 7 達成了共識。

繼續深入理解

如果只有一個節點 A 通過提案[5, 7],另外兩個節點 B,C 未通過任何提案,這個時候客戶端發起[9,6]的請求時,三個結點的最終值還會是[9,7]麼?

答案:最終的值,可能是 6,也可能是 7,取決於節點 A 的準備響應,是否是“大多數”中的一個。

Multi-Paxos

Basic Paxos 只能就單個值(Value)達成共識,一旦遇到爲一系列的值實現共識的時候,它就不管用了。蘭伯特提出可以通過多次執行 Basic Paxos 實例(比如每接收到一個值時,就執行一次 Basic Paxos 算法)實現一系列值的共識。

Multi-Paxos 是一種思想,不是算法。而 Multi-Paxos 算法是一個統稱,它是指基於 Multi-Paxos 思想,通過多個 Basic Paxos 實例實現一系列值的共識的算法(比如 Chubby 的 Multi-Paxos 實現、Raft 算法等)。

如果我們直接通過多次執行 Basic Paxos 實例,來實現一系列值的共識,就會存在這樣兩個問題:

  • 如果多個提議者同時提交提案,可能出現因爲提案衝突,在準備階段沒有提議者接收到大多數準備響應,協商失敗,需要重新協商。比如,一個 5 節點的集羣,如果 3 個節點作爲提議者同時提案,就可能發生因爲沒有提議者接收大多數響應(比如 1 個提議者接收到 1 個準備響應,另外 2 個提議者分別接收到 2 個準備響應)而準備失敗,需要重新協商。
  • 2 輪 RPC 通訊(準備階段和接受階段)往返消息多、耗性能、延遲大。

解決第一個問題:新增的角色領導者(Leader)

可以通過引入領導者節點,也就是說,領導者節點作爲唯一提議者,這樣就不存在多個提議者同時提交提案的情況,也就不存在提案衝突的情況了:

在這裏插入圖片描述

在論文中,蘭伯特沒有說如何選舉領導者,需要我們在實現 Multi-Paxos 算法的時候自己實現。 比如在 Chubby 中,主節點(也就是領導者節點)是通過執行 Basic Paxos 算法,進行投票選舉產生的。

解決第二個問題:優化 Basic Paxos 執行

採用“當領導者處於穩定狀態時,省掉準備階段,直接進入接受階段”這個優化機制,優化 Basic Paxos 執行。也就是說,領導者節點上,序列中的命令是最新的,不再需要通過準備請求來發現之前被大多數節點通過的提案,領導者可以獨立指定提案中的值。這時,領導者在提交命令時,可以省掉準備階段,直接進入到接受階段。

正常流程:

在這裏插入圖片描述
優化後流程:

在這裏插入圖片描述
和重複執行 Basic Paxos 相比,Multi-Paxos 引入領導者節點之後,因爲只有領導者節點一個提議者,只有它說了算,所以就不存在提案衝突。另外,當主節點處於穩定狀態時,就省掉準備階段,直接進入接受階段,所以在很大程度上減少了往返的消息數,提升了性能,降低了延遲。

爲什麼可以跳過準備階段?

在領導者節點上,序列中的命令是最新的,不再需要通過準備請求來發現之前被大多數節點通過的提案,領導者可以獨立指定提案中的值。

準備階段的意義是發現接受者節點上,已經通過的提案的值。如果在所有接受者節點上,都沒有已經通過的提案了,這時,領導者就可以自己指定提案的值了,那麼,準備階段就沒有意義了,也就是可以省掉了。

Chubby 的 Multi-Paxos 實現

1、首先,它通過引入主節點,實現了蘭伯特提到的領導者(Leader)節點的特性。也就是說,主節點作爲唯一提議者,這樣就不存在多個提議者同時提交提案的情況,也就不存在提案衝突的情況了。

2、其次,在 Chubby 中實現了蘭伯特提到的,“當領導者處於穩定狀態時,省掉準備階段,直接進入接受階段”這個優化機制。

3、最後,在 Chubby 中,實現了成員變更(Group membership),以此保證節點變更的時候集羣的平穩運行。

在 Chubby 中,主節點是通過執行 Basic Paxos 算法,進行投票選舉產生的,並且在運行過程中,主節點會通過不斷續租的方式來延長租期(Lease)。爲了實現了強一致性,讀操作也只能在主節點上執行。 也就是說,只要數據寫入成功,之後所有的客戶端讀到的數據都是一致的。

參考資料

擴展閱讀

The Part-Time Parliament

參考資料

極客時間專欄:分佈式技術原理與算法解析

Understanding Paxos

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