共識算法-Paxos

感謝 參考資料

場景

有一個變量v,分佈在N個進程中,每個進程都嘗試修改自身v的值,它們的企圖可能各不相同,例如進程A嘗試另v=a,進程B嘗試另v=b,但最終所有的進程會對v就某個值達成一致,即上述例子中如果v=a是v達成一致時的值,那麼B上,最終v也會爲a。

需要注意的是某個時刻達成一致並不等價於該時刻所有進程的本地的v值都相同,有一個原因是進程可能掛掉,你不能要求掛掉的進程任何事;

更像是最終所有存活的進程本地v的值都會相同。

一致性的三個要求

  • v達成一致時的值是由某個進程提出的。這是爲了防止像這樣的作弊方式:無論如何,最終都令每個進程的v爲同一個預先設置好的值,例如都令v=2,那麼這樣的一致也太容易了,也沒有任何實際意義。
  • 一旦v就某個值達成了一致,那麼v不能對另一個值再次達成一致。這個要求稱爲安全性。
  • 一致總是能夠達成,即v總會被決定爲某個值。這是因爲不想無休止的等待,這個要求也稱爲活性。

Paxos和分佈式存儲系統

Paxos用來確定一個不可變變量的取值

  • 取值可以是任意二進制數據
  • 一旦確定將不再更改,並且可以被獲取到(不可變性 & 可讀性

在分佈式存儲系統中應用Paxos

  • 數據本身可變,採用多副本進行存儲
  • 需要確保多個副本的更新操作序列[Op1, Op2, …, Opn]是相同的 & 不變的
  • 用Paxos依次來確定不可變變量Opi的取值(即第i個操作時什麼)
  • 每確定完Opi之後,讓每個數據副本執行Opi,依次類推

Google的Chubby & Megastore & Spanner都採用了Paxos來對數據副本的更新序列達成一致

Paxos希望解決的一致性問題

設計一個系統,來存儲名稱爲var的變量

  • 系統內部由多個Acceptor組成,負責存儲和管理var變量。
  • 外部有多個proposer機器任意併發調用API,向系統提交不同的var取值
  • var的取值可以時任意二進制數據
  • 系統對外的API庫接口爲:propose(var, V) => <ok, f> or <error>

系統需要保證var的取值滿足一致性

  • 如果var的取值沒有確定,則var的取值爲null
  • 一旦var的取值被確定,則不可被更改。並且可以一直獲取到這個值。

系統需要滿足容錯特性

  • 可以容忍任意proposer機器出現故障
  • 可以容忍少數Acceptor故障(半數以下)

其他考慮

  • 網絡分化
  • acceptor故障會丟失var的信息

確定一個不可變變量的難點

  1. 管理多個Proposer的併發執行
  2. 保證var變量的不可變性
  3. 容忍任意Proposer機器故障
  4. 容忍半數以下Acceptor機器故障

推導

方案一 (確定一個不可變變量的取值)

先考慮系統由單個Acceptor組成。通過類似互斥鎖機制,來管理併發的proposer運行

  1. Proposer首先向acceptor申請acceptor的互斥訪問權,然後才能請求Acceptor接受自己的取值。
  2. Acceptor給proposer發放互斥訪問權,誰申請到互斥訪問權,就接收誰提交的取值。
  3. 讓proposer按照互斥訪問權的順序依次訪問acceptor
  4. 一旦Acceptor接收了某個Proposer的取值,則認爲var取值被確定,其他Proposser不再更改

基於互斥訪問權的Acceptor的實現

Acceptor保存變量var和一個互斥鎖lock

Acceptor::prepare():
加互斥鎖,給予var的互斥訪問權,並返回var當前的取值f

Acceptor::release():
1. 如果已經加鎖,並且var沒有取值,則設置var爲V
2. 釋放鎖

Acceptor::release():
解互斥鎖,收回var的互斥訪問權

propose(var, V)的兩階段實現

第一階段

Proposer通過Acceptor::prepare獲取互斥訪問權和當前var的取值。如果不能,返回 (鎖被別人佔用)

第二階段

根據當前var的取值f,選擇執行。

  1. 如果f爲null,則通過Acceptor::accept(var, V)提交數據V
  2. 如果f不爲空,則通過Acceptor::release()釋放訪問權,返回

分析

通過Acceptor互斥訪問權讓Proposer序列運行,可以簡單地實現var取值的一致性。

Proposer在釋放互斥訪問權之前發生故障,會導致系統陷入死鎖。因此,此防範不能容忍任意Proposer機器故障。

方案二 (確定一個不可變變量的取值)

爲了解決方案一的問題,我們引入了搶佔式訪問權

acceptor可以讓某個proposer獲取到的訪問權失效,不再接收它的訪問
之後,可以讓訪問權發放給其他proposer,讓其他proposer訪問acceptor

搶佔規則

  1. Proposer向Acceptor申請訪問權時指定編號epoch(越大的epoch越新),獲取到訪問權之後,才能向acceptor提交取值。
  2. Acceptor採用喜新厭舊的原則
    • 一旦接收到更大的新epoch的申請,馬上讓舊epoch的訪問權失效,不再接收他們提交的取值
    • 然後給新epoch發放訪問權,只接收新epoch提交的取值

由搶佔規則引申出來的問題

新epoch可以搶佔舊epoch,讓舊epoch的訪問權失效。舊epoch的proposer將無法運行,新epoch的proposer將開始運行。

因此,爲了保持一致性,不同epoch的proposer之間採用後者認同前者的原則

  1. 在肯定舊epoch無法生成確定性取值時,新的epoch會提交自己的value。不會衝突。
  2. 一旦舊epoch形成確定性取值,新的epoch肯定可以獲取到此取值,並且會認同此取值,不會破壞。

基於搶佔式訪問權的Acceptor的實現

Acceptor保存的狀態
1. 當前var的取值<accepted_epoch, accepted_value>
2. 最新發放訪問權的epoch(latest_prepared_epoch)
注意:accepted_epoch不需要與latest_prepared_epoch相等

Acceptor::prepare(epoch):
1. 只接收比latest_prepared_epoch更大的epoch,並給予訪問權
2. 記錄latest_prepared_epoch = epoch 並 返回當前var的取值

Acceptor::accept(var, prepared_epoch, V):
1. 驗證latest_prepared_epoch == prepared_epoch,
2. 若驗證通過,設置var的取值<accepted_epoch, accepted_value> = <prepared_epoch, v>

思考

如果var取值存在,則proposer就直接返回,Proposer時不需要通過Acceptor::accept(var, epoch, V)提交數據V。

propose(var, V)的兩階段實現

第一階段

Proposer獲取epoch輪次的訪問權和當前var的取值

  1. 簡單選取當前時間戳爲epoch,通過Acceptor::prepare(epoch),獲取epoch輪次的訪問權和當前var的取值
  2. 如果不能獲取,返回

第二階段

採用“後者認同前者”的原則執行

  • 在肯定舊epoch無法生成確定性取值時,新的epoch會提交自己的value,不會衝突
  • 一旦舊epoch形成確定性取值,新的epoch肯定可以獲取到此取值,並會認同此取值,不會破壞

流程:
1. 如果var的取值爲空,則肯定舊epoch無法生成確定性取值,則通過Acceptor::accept(var, epoch, V)提交數據V:
1. 成功後Acceptor返回

分析

基於搶佔式訪問權的核心思想(讓Proposer將按照epoch遞增的順序搶佔式的依次運行,後者會認同前者),可以避免proposer機器故障帶來的死鎖問題,並且仍可以保證var取值的一致性。

注意:仍需要引入多acceptor,因爲單機模塊Acceptor是故障導致整個系統宕機,無法提供服務。

Paxos

Paxos在方案2的基礎上引入多Acceptor,即Acceptor的實現保持不變。仍採用“喜新厭舊”的原則運行。

因爲引入了多Acceptor,Paxos採用“少數服從多數”的思路。

容錯率50%的由來

一旦某epoch的取值v被半數以上acceptor接收(落盤了),則認爲此var取值被確定爲v,不再更改。

爲什麼是半數以上,因爲這樣能保證,Proposer在propose階段總能從n/2+1個Acceptor中的至少某一個Acceptor得知

propose(var, V)的兩階段實現

第一階段

選定epoch,獲取epoch訪問權和對應的var取值

具體:

選定epoch,獲取**半數以上**acceptor的訪問權和對應的一組var取值[

第二階段

採用“後者認同前者”的原則執行:
1. 在肯定舊epoch無法生成確定性取值時,新的epoch會提交自己的取值,不會衝突
2. 一旦舊epoch形成確定性取值,新的epoch肯定可以獲取到此取值,並且會認同此取值,不會破壞。

具體(前提上獲得半數以上的訪問權):

  1. 如果獲取的var取值都爲空,則舊epoch無法形成確定性取值。此時努力使

爲什麼選擇最大accepted_epoch對應的取值V

2.2情況中,有兩種可能,V已經被確定了 & V還沒被確定

a) 對於情況V還沒被確定,我們其實不需要選擇最大accepted_epoch對應的取值V,我們是可以按自己的想法乾的
b) 對於情況V已經被確定下來了,我們考慮最差的情況,n/2+1個Acceptor中,假設n爲奇數,n=5,此時V的取值已經確定下來了,然而,宕機了,留下了最差的局面:
1 : V ; 2 : V’ ; 3 : V’
怎麼識別出V纔是被確定下來的值呢?我們可以知道V肯定會比V’有更新的epoch。用反證法可以證明。

從查詢者的角度看到,(<epoch_3, V>, <epoch_2, V'>, <epoch_1, V'>),他不能區分情況a), b),V具有最新的epoch_3也不意味着它已經被確定,所以查詢者要向1, 2, 3提出accept請求,請求值爲V。這樣
1. 如果V未被確認,那麼走確認流程
2. 如果V已經被確認了,走多一次確認流程,V值不會被改變

Paxos算法

Paxos算法的核心思想

  • 在搶佔式訪問權的基礎上引入多acceptor
  • 保證一個epoch,只有一個proposer運行,proposer按照epoch遞增的順序依次運行
  • 新的epoch的proposer採用“後者認同前者”的思路進行
    • 在肯定舊epoch無法生成確定性取值時,新的epoch會提交自己的取值,不會衝突
    • 一旦舊epoch形成確定性取值,新的epoch肯定可以獲取到此取值,並且會認同此取值,不會破壞

Paxos算法可以滿足容錯要求

  • 半數以下acceptor出現故障時,存活的acceptor仍然可以生成var的確定性取值
  • 一旦var取值被確定(不是指存在),即使出現半數以下acceptor故障,此取值可以被獲取,並且將不再被更改

Paxos算法的Liveness問題

新輪次的搶佔會讓舊輪次停止運行,如果每一輪次在第二階段執行成功之前都被新一輪搶佔,則導致活鎖。怎麼解決?

更多

  1. Paxos在Google的Chubby/Megastore和Spanner中的應用
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章