區塊鏈共識機制:分散式系統的Paxos協議
前言:第一次接觸paxos可能很多人不理解這玩意兒有啥用,近幾天一直在研究paxos,不敢說理解的多到位,但是把自己理解的記錄下來,供大家參考。文章主要參考知行學社的《分散式系統與Paxos演算法視訊課程》和知乎話題https://zhuanlan.zhihu.com/p/29706905,希望能對大家有幫助。
一、什麼是Paxos,解決什麼問題?
(一)Paxos是分散式系統中,在非同步通訊環境下,可以容忍在只有多數派機器存活(容許半數一下節點宕機)的情況下,仍然能完成一個一致性寫入的協議。
非同步通訊環境並非只有paxos能解決一致性問題,經典的兩階段和三階段提交(參考:https://blog.yangx.site/2018/08/05/paxos/
(二)解決什麼問題
paxos用來確定分散式系統中確定一個不可變變數的取值。(不理解沒關係,往下看)
(1)取值可以是任意二進位制資料。
(2)一旦確定將不再更改,並且可以被獲取到(不可變性,可讀取性)
一般的分散式儲存系統中
(1)資料本身可變,採用多副本進行儲存。
(2)多個副本的更新操作序列【Op1,Op2,……,Opn】是相同的、不變的。
(3)用Paxos依次來確定不可變數Opi的取值,即第i個操作是什麼?(重點)
(4)沒確定完Opi之後,讓各個資料副本執行Opi,依次類推,從而實現分散式系統的一致性。
應用案例:google的chubby、Megastore和Spanner都採用Paxos來對資料副本的更新序列達成一致。
二、paxos的實現過程和實現原理是怎樣的?分為哪些步驟?
為方便理解,以下通過案例來循序漸進的講。
(一)案例設計:(分散式系統的理解可以參考資料劉傑的《分散式系統原理介紹》)
1、設計一個分散式系統,用來儲存名稱為var的變數。系統描述如下:
(1)系統內部有多個Acceptor組成,負責儲存和管理var變數。
(2)外部有多個Proposer機器任意併發呼叫API,向系統提交不同的var取值。
(3)var的取值可以是任意二進位制資料。
(4)系統對外的API庫介面為:propse(var,V) => <ok,f> or <error>,如果var的值已經被系統確定了,則返回V,如果沒被系統確定則返回error;如果系統中的某一Proposer將var的設定為V,則f就代表V,否則f為其他Proposer設定的值。
2、系統需要保證var的取值滿足一致性
(1)如果var的取值沒有確定,則var的取值為null;
(2)一旦var的取值被確定,則不可被更改。並且可以一直獲取到這個值。
3、系統需要滿足容錯特性
(1)可以容忍任意的Proposer機器出現故障。
(2)可以容忍半數一下的Acceptor出現故障。
4、注意,為了容易理解,暫不考慮
(1)網路故障。
(2)Acceptor故障丟失var的資訊。
5、設計這一系統的難點是什麼?
(1)管理多個Proposer的併發執行
(2)保證var變數的不可變性
(3)容忍任意Proposer機器故障
(4)容忍半數一下Acceptor機器故障
(二)方案一:基於互斥訪問權的實現:
1、基於互斥訪問權的acceptor的實現:
(1)Acceptor儲存變數var和一個互斥鎖lock
(2)介面Acceptor::prepare():
加互斥鎖,給予var的互斥訪問權,並返回var當前的取值f。
介面Acceptor::release():
解互斥鎖,收回var的互斥訪問權
介面Acceptor::accept(var,V):
如果已經加鎖,並且var沒有取值,則設定var為V。並且釋放鎖。
2、propose(var,V)的兩階段實現:
第一階段,通過Acceptor::prepare獲取互斥訪問權和當前var的取值,如果不能獲取到,說明鎖被他人所佔,返回<error>;如果可以獲取互斥訪問權,則進入第二階段,否則,結束。
第二階段,根據當前var的取值f,選擇執行。
如果f為null,則通過Acceptor::accept(var,V)提交資料V。
如果f不為空,則通過Acceptor::release()釋放訪問權,返回<ok,f>。
3、補充概念:互斥鎖(英語:英語:Mutual exclusion,縮寫 Mutex)是一種用於多執行緒程式設計中,防止兩條執行緒同時對同一公共資源(比如全域性變數)進行讀寫的機制。需要此機制的資源的例子有:旗標、佇列、計數器、中斷處理程式等用於在多條並行執行的程式碼間傳遞資料、同步狀態等的資源。
總結:
解決了多個Proposer的併發執行問題並保證var變數的不可變性,但是不能容忍任意Proposer機器故障,若Proposer在釋放互斥訪問權之前發生故障,會導致系統陷入死鎖。
方案二:引入搶佔式訪問權
1、特點:
(1)Acceptor可以讓某個Proposer獲取到的訪問權失效,不再接受它的訪問。之後,可以將訪問權發放給其他的Proposer,讓其他Proposer訪問acceptor。
(2)Proposor向Acceptor申請訪問權時指定標號epoch(越大的epoch越新),獲取到訪問權之後,才能向acceptor提交取值。
(3)Acceptor接受到更大的新epoch的申請,馬上讓舊的epoch的訪問權失效,不再接受他們提交的取值,然後給新epoch發放訪問權,只接受新epoch提交的取值。
2、為何能解決proposer機器故障:
新的epoch可以搶佔舊epoch,讓舊epoch的訪問權失效,舊epoch的proposer將無法執行,新epoch的proposer將開始執行。
為了保證一致性,不同epoch的proposer之間採用“後者認同前者”的原則:
- 在肯定舊epoch無法生成確定性取值時,新的epoch會提交自己的value,不會衝突。
- 一旦舊的epoch形成確定性取值,新的epoch肯定可以獲取到此取值,並且會認同此取值,不會破壞。
3、具體實現:
基於搶佔式訪問權的Acceptor實現:
(1)Acceptor儲存的狀態
當前var的取值<accepted_epoch, accepted_value>
最新發放訪問權的epoch(latest_prepared_epoch)
(2)Acceptor::prepare(epoch):
只接受比latest_prepared_epoch更大的epoch,並給予訪問權,
記錄latest_prepared_epoch = epoch,返回當前var的取值。
(3)Acceptor::accept(var,prepared_epoch,V):
驗證latest_prepared_epoch == prepared_epoch,
設定var的取值<accepted_epoch,accepted_value> = <prepared_epoch, v>
4、Propose(var,V)的兩階段實現:
第一階段,獲取epoch輪次的訪問權和當前var 的取值
簡單獲取當前時間戳為epoch,通過Acceptor::prepare(epoch),獲取eooch輪次的訪問權和當前var的取值,如果不能獲取,返回<error>。
第二階段,採用“後者確認前者”的原則執行。
(1)在肯定舊epoch無法生成確定性取值時,新的epoch會提交自己的value,不會衝突。
(2)一旦舊epoch形成確定性取值,新的epoch肯定可以獲取到此取值,並且會認同此取值,不會破壞。
- 如果var的取值為空,則肯定舊epoch無法生成確定性取值,則通過Acceptor::accept(var,epoch,V)提交資料V,成功後返回<ok,V>;如果accept失敗,返回<error>,此時Acceptor被新epoch搶佔或者acceptor出現故障。
- 如果var取值存在,則此值肯定是確定性取值,此時認同它不再更改,直接返回<ok,accepted_value>。
核心思想:讓Proposer將按照epoch遞增的順序搶佔式的一次執行,後者會認同前者。
解決問題:可以避免Proposer機器故障帶來的死鎖問題,並且仍可以保證var取值的一致性。
存在問題:仍需要引入多Acceptor。單機模組Acceptor可以使故障導致整個系統宕機,無法提供服務。
方案三、都讓開,Paxos要閃亮登場,亮瞎咱們的狗眼了,Paxos是在方案2的基礎上引入多Acceptor。
1、特點
(1)Acceptor的實現保持不變。仍採用“喜新厭舊”的原則執行。
(2)Paxos採用“少數服從多數”的思路,一旦某epoch的取值f被半數以上的Acceptor接受,則認為此var取值被確定為f,不再更改。
2、Paxos的組成元素:
(1)三個參與角色:
- Proposer:提議發起者。Proposer 可以有多個,Proposer 提出議案(value)。所謂 value,可以是任何操作,比如“設定某個變數的值為value”。不同的 Proposer 可以提出不同的 value,例如某個Proposer 提議“將變數 X 設定為 1”,另一個 Proposer 提議“將變數 X 設定為 2”,但對同一輪 Paxos過程,最多隻有一個 value 被批准。
- Acceptor:提議接受者;Acceptor 有 N 個,Proposer 提出的 value 必須獲得超過半數(N/2+1)的 Acceptor批准後才能通過。Acceptor 之間完全對等獨立。
- Learner:提議學習者。上面提到只要超過半數accpetor通過即可獲得通過,那麼learner角色的目的就是把通過的確定性取值同步給其他未確定的Acceptor。
2、Proposer的兩階段執行:(為方便理解,先不引入learner)
Proposer(var,V)第一階段:選定epoch,獲取epoch訪問權和對於的var取值,需要獲取半數以上的acceptor的訪問權和對於的一組var取值。
第二階段,採用“後者認同前者”的原則執行。
(1)在肯定舊epoch無法生成確定性取值時,新的 會提交自己的取值,不會衝突。
(2)一旦舊epoch形成確定性取值,新的epoch肯定可以獲取到此取值,並且會認同此取值,不會破壞。
3、多Acceptor實現:
(1)如果獲取的var取值都為空,則舊epoch無法形成確定性取值。此時努力使新的<epoch,V>成為確定性取值。向epoch對應的所有acceptor提交取值<epoch,V>;
如果收到半數以上成功,則返回<ok,V>;
否則,返回<error>,此時被新epoch搶佔或者acceptor傳送故障。
(2)如果var的取值存在(需同時詢問至少半數以上Acceptor),認同最大accepted_epoch對應的取值f,幫助取值f提交確認(參考下方原理圖),努力使<epoch,f>成為確定性取值。
如果f出現半數以上,則說明f已經被確定性取值,直接返回<ok,f>
否則,向epoch對應的所有acceptor提交取值<epoch,f>,按所能就接受到的最新的序號取值提交,比如接收到<#1,2>,<#2,3>和<#3,4>,則對所有的acceptor提交Accept(#3,4),最終達成一致性。
4、特殊情況彙總
(1)第一種情況:單proposer,三個acceptor,Proposer提議正常,未超過accpetor失敗情況
問題:如果第二階段,只有2個accpetor響應接收提議成功,另外1個沒有響應怎麼處理呢?
處理:proposer發現只有2個成功,已經超過半數,那麼還是認為提議成功,並把訊息傳遞給learner,由learner角色將確定的提議通知給所有accpetor,最終使最後未響應的accpetor也同步更新,通過learner角色使所有Acceptor達到最終一致性。
(2)第二種情況:單proposer,三個acceptor,Proposer提議正常,但超過accpetor失敗情況
問題:假設有2個accpetor失敗,又該如何處理呢?
處理:由於未達到超過半數同意條件,proposer要麼直接提示失敗,要麼遞增版本號重新發起提議,如果重新發起提議對於第一次寫入成功的accpetor不會修改,另外兩個accpetor會重新接受提議,達到最終成功。
(3)情況再複雜一點:還是一樣有3個accpetor,但有兩個proposer。
情況一:proposer1和proposer2序列執行
proposer1和最開始情況一樣,把value設定為v1,並接受提議。
proposer1提議結束後,proposer2發起提議流程:
第一階段A:proposer1發起prepare(epoch,v2)
第一階段B:Acceptor收到proposer的訊息,發現內部value已經寫入確定了,返回(#1,v1)
第二階段A:proposer收到3個Acceptor的響應,發現超過半數都是v1,說明name已經確定為v1,接受這個值,不在發起提議操作。
情況二:proposer1和proposer2交錯執行
proposer1提議accpetor1成功,但寫入accpetor2和accpetor3時,發現版本號已經小於accpetor內部記錄的版本號(儲存了proposer2的版本號),直接返回失敗。
proposer2寫入accpetor2和accpetor3成功,寫入accpetor1失敗,但最終還是超過半數寫入v2成功,value變數最終確定為v2;
proposer1遞增版本號再重試發現超過半數為v2,接受name變數為v2,也不再寫入v1。name最終確定還是為v2
情況三:proposer1和proposer2第一次都只寫成功1個Acceptor怎麼辦
都只寫成功一個,未超過半數,那麼Proposer會遞增版本號重新發起提議,這裡需要分多種情況:
- 3個Acceptor都響應提議,發現Acceptor1{#1,v1} ,Acceptor2{#2,v2},Acceptor{null,null},Processor選擇最大的{v2,n2}發起第二階段,成功後name值為v2;
- 2個Acceptor都響應提議,
- 如果是Acceptor1{#1,v1} ,Acceptor2{#2,v2},那麼選擇最大的{v2,n2}發起第二階段,成功後name值為v2;
- 如果是Acceptor1{#1,v1} ,Acceptor3{null,null},那麼選擇最大的{v1,n1}發起第二階段,成功後name值為v1;
- Acceptor2{#2,v2} ,Acceptor3{null,null},那麼選擇最大的{v2,n2}發起第二階段,成功後name值為v2;
- 只有1個Acceptor響應提議,未達到半數,放棄或者遞增版本號重新發起提議
可以看到,都未達到半數時,最終值是不確定的!
5、總結:
Paxos演算法的核心思想:
整個paxos協議過程看似複雜難懂,但只要把握和理解這兩點就基本理解了paxos的精髓:
第一階段accpetor的處理流程:如果本地已經寫入了,不再接受和同意後面的所有請求,並返回本地寫入的值;如果本地未寫入,則本地記錄該請求的版本號,並不再接受其他版本號的請求,簡單來說只信任最後一次提交的版本號的請求,使其他版本號寫入失效;
第二階段proposer的處理流程:未超過半數accpetor響應,提議失敗;超過半數的accpetor值都為空才提交自身要寫入的值,否則選擇非空值裡版本號最大的值提交,最大的區別在於是提交的值是自身的還是使用以前提交的。
特點
(1)在搶佔式訪問權的基礎上引入多acceptor
(2)保證一個epoch,只有一個proposer執行,proposer按照epoch遞增的順序依次執行。
(3)新epoch的proposer採用“後者認同前者”的思路進行。
- 在肯定舊epoch無法生成確定性取值時,新的epoch會提交自己的取值,不會衝突。
- 一旦舊epoch形成確定性取值,新的epoch肯定可以獲取到此取值,並且會認同此取值,不會破壞。
Paxos演算法可以滿足容錯要求
- 半數半數的acceptor出現故障時,存活的acceptor仍然可以生產var的確定性取值。
- 一旦var取值被確定,即使出現半數以下acceptor故障,此取值可以被獲取,並且將不再被更改。
Paxos演算法的Liveness問題
新輪次的搶佔會讓舊輪次停止執行,如果每一輪次在第二階段執行成功之前都被新一輪搶佔,則導致活鎖,如何解決??
答:
引入leader,也就是multi paxos的情形。先選舉leader,leader任期內不需要執行prepare,直接accept即可,在同一個leader任期內只有leader發起proposal,《Multi-Paxos: An Implementation and Evaluation》,不再展開。
思考題:
- 什麼情況下可以認為var的取值被確定,不再更改?
- Paxos兩階段分別在做什麼?
- 一個epoch是夠會有多個proposer進入第二階段執行?
- 什麼情況下,proposer可以將var的取值確定為自己提交的取值?
- 在第二階段,如果獲取的var的取值都為空,為什麼可以保證舊epoch無法過程確定性取值?
- 新epoch搶佔成功之後,舊epoch的proposer將如何執行?
- 如何保證新epoch不會破壞已經達成的確定性取值?
- 為什麼在第二階段存在var取值時,只需要考慮accepted_epoch最大的取值f?
- 在形成確定性取值之後出現任意半數一下的acceptor故障,為何確定性取值不會被更改?
- 如果proposer在執行過程中,任意半數以下的acceptor出現故障,此時將如何執行?
- 正在執行的proposer和任意半數以下acceptor都出現故障時,var的取值可能是什麼情況?為何之後新的proposer可以形成確定性取值?
參考答案:
- 半數以上Acceptor寫入var值就被確認,不再更改。
- 第一階段請求訪問權和儲存的值,第二階段空時提交;
- 不會,一個epoch只有一個proposer執行,其他的proposer遞增epoch順序。
- 自己提交時無確定的值,且自己的提交的值被確認的acceptor數量達到一半以上。
- Acceptor採用喜新厭舊的做法,而且現在至少有一半的acceptor接受提交新epoch,所以有舊epoch提交時會自動拒絕,這樣至少一半以上的epoch會拒絕舊epoch,舊epoch永遠無法確認。
- 一旦被新epoch搶佔成功,舊epoch的訪問權失效,舊epoch的proposer將無法執行。
- 已經達成確認的取值,節點數大於1/2,新epoch請求時,發現已確認了,半數以上節點都是這個值,不再提議,弱發現確認的節點數不到1/2,只要不全是空就會遞增版本號同樣提交取到的這個值,不會賦予新值(後者確認前者原則)。
- 喜新厭舊,存在新epoch的var值時,舊epoch會被acceptor自動拒絕。
- 確定性取值需要半數以上確認。
- 正常執行,容錯率達到了半數。
- 因為新的proposer仍然可以達到一半以上的acceptor確認。
推薦參考資料:
(下載地址:https://pan.baidu.com/s/1AwkZ8OHnEn_9I8GzBnj97g,包含以下1、4、5、8、9)
參考資料:
1、知行學社的《分散式系統與Paxos演算法視訊課程》
2、分散式系統一致性及Paxos詳解https://blog.yangx.site/2018/08/05/paxos/
3、知乎話題https://zhuanlan.zhihu.com/p/29706905
4、劉傑的《分散式系統原理介紹》 ,裡面有關於paxos的詳細介紹,例子非常多,也有包括paxos協議的證明過程,大而全,質量相當高的一份學習資料!
5、ppt《可靠分散式系統基礎 Paxos 的直觀解釋》,雖然是隻是一份ppt沒有講解視訊,但看ppt也能理解整個的paxos介紹和推導過程,寫的很具體,配圖很清晰明瞭;
6、微信的幾篇公眾號文章:《微信PaxosStore:深入淺出Paxos演算法協議》https://mp.weixin.qq.com/s/aJoXSQo9-zmukN2RsiZ3_g(微信PaxosStore:深入淺出Paxos演算法協議 )、《微信開源:生產級paxos類庫PhxPaxos實現原理介紹》https://mp.weixin.qq.com/s/6VWUA5EDV2UIq4NqmQYWUA(微信自研生產級paxos類庫PhxPaxos實現原理介紹 ),文章寫的都挺好,不適合入門,需要有一定基礎才好理解;
7、原始碼:微信開源的phxpaxos:https://github.com/tencent-wechat/phxpaxos,結合程式碼對協議理解更深。
8、paxos作者Lamport《paxos made simple》的論文。
9、《Multi-Paxos: An Implementation and Evaluation》論文。
以上均為個人結合參考資料理解,如有錯誤,懇請大家批評指正!