Zookeeper筆記二-各種一致性協議解釋


我們知道Zookeeper的一致性是解決分布式事務的。

那么分布式事務代表的是強一致性。

強一致性解決的代表有以下協議(注意這幾個協議跟zookeeper是沒任何關系的,這是分布式的理論基礎):

1.  2PC(二階提交),顧名思義它分成兩個階段,先由一方進行提議(propose)並收集其他節點的反饋(vote),再根據反饋決定提交(commit)或中止(abort)事務。我們將提議的節點稱為協調者(coordinator),其他參與決議節點稱為參與者(participants, 或cohorts)。

在階段1中,協調者發起一個提議,分別問詢各參與者是否接受。

在階段2中,協調者根據參與者的反饋,提交或中止事務,如果參與者全部同意則提交,只要有一個參與者不同意就中止。

如下圖:

 

在異步環境並且沒有節點宕機的模型下,2PC可以滿足全認同、值合法、可結束,是解決一致性問題的一種協議。但如果再加上節點宕機的考慮,2PC是否還能解決一致性問題呢? 

答案是可以的

因為協調者如果在發起提議后宕機,那么參與者將進入阻塞狀態、一直等待協調者回應以完成該次決議。這時需要另一角色把系統從不可結束的狀態中帶出來,我們把新增的這一角色叫協調者備份。協調者宕機一定時間后,協調者備份接替原協調者工作,通過問詢各參與者的狀態,決定階段2是提交還是中止。這也要求 協調者/參與者 記錄歷史狀態,以備協調者宕機后備份對參與者查詢、協調者宕機恢復后重新找回狀態。

從協調者接收到一次事務請求、發起提議到事務完成,經過2PC協議后增加了2次RTT(propose+commit),帶來的時延增加相對較少。

這種方式有一定的缺點,就是增加了備份參與者,節點的溝通就越頻繁,出現網絡問題的概率就越大。

因此二階段提交的總結是:2PC可以在異步網絡+節點宕機恢復的模型下實現一致性

 

2.  3PC(三階段提交)其實就是對二階段提交進行改進了

在二階段提交中中一個參與者的狀態只有它自己和協調者知曉,假如協調者提議后出現自身宕機,在備用啟用之前,一個參與者又宕機了,其他參與者就會進入既不能回滾、又不能強制commit的阻塞狀態,直到參與者宕機恢復。

那么會有這樣的疑問:

1.可不可以去掉阻塞,使系統可以在commit/abort前回滾到決議發起前的初始狀態呢?

2.當次決議中,參與者間可不可以知道對方的狀態,又或者參與者間根本不依賴對方的狀態

三階段提交增加了一個准備提交階段來解決以上問題。如下圖:

參閱這如果在不同階段宕機,3階段提交的處理方式:

第一階段:協調者或備份未收到宕機參與者的反饋,直接中止事務;宕機的參與者恢復后,讀取記錄發現未發出贊成反饋,則自行中止該次事務。

第二階段:協調者未收到宕機參與者的precommit ACK,之前已經收到了宕機參與者的贊成反饋,因為只有之前已經收到了宕機參與者的贊成反饋才進入了階段二,協調者進行事務提交;協調者備份可以通過問詢其他參與者獲得這些信息;宕機的參與者恢復后發現收到precommit或已經發出贊成反饋,則自行commit這次事務。

第三階段:即使協調者或協調者備份未收到宕機參與者的事務提交的 ACK,也要結束本次事務;宕機的參與者恢復后發現收到事務提交或者提交預授權,也將自行commit本次事務

3PC總結:三階段提交有了准備提交(prepare to commit)階段,3PC的事務處理延時也增加了1個RTT,變為3個RTT(propose+precommit+commit),但是它防止參與者宕機后整個系統進入阻塞態,增強了系統的高可用性,對一些特殊的業務場景是非常有用的。

 

這些2PC,3PC體現的層面,在JAVAEE中體現出來的是只要是JTA(Java Transaction API)。

JTA:提供了跨數據庫連接(或其他JTA資源)的事務管理能力。JTA事務管理則由JTA容器實現,J2ee框架中事務管理器與應用程序,資源管理器,以及應用服務器之間的事務通訊。

JTA提供的支持有Jboss,Jboss類似與Tomcat的一個容器。Tomcat是天生不支持JTA的,要引入JOTM(Java Open Transaction Manager)可以解決。

3.  NWR。N:代表數據分了多少片,即多少個節點。W:每次寫入多少個節點算成功。R:每次讀取多少個節點算成功。如下圖:

 

 這樣NWR也最終可以達到強一致性。

 

 Zookeeper的核心算法——Paxos算法(Master選舉算法)

Paxos協議分為兩個階段。

這里我們首先要明確什么是提案:提案:[編號,值]即[key,value]。當Master宕機后,從節點向Acceptor提出提案讓我這個從節點做Master,這些體驗要給Acceptor決策。

整個過程分為兩個階段:

  • phase1(准備階段)

    • Proposer向超過半數(n/2+1)Acceptor發起prepare消息(發送編號)

    • 如果prepare符合協議規則Acceptor回復promise消息,否則拒絕

  • phase2(決議階段或投票階段)

    • 如果超過半數Acceptor回復promise,Proposer向Acceptor發送accept消息(此時包含真實的值)

    • Acceptor檢查accept消息是否符合規則,消息符合則批准accept請求

約束條件:

   1.Acceptor必須接受他接收到的第一個提案。注意:這個是不完備的。如果恰好一半Acceptor接受的提案具有value A,另一半接受的提案具有value B,那么就無法形成多數派,無法批准任何一個value。

  2.當且僅當Acceptor沒有回應過編號大於n的prepare請求時,Acceptor接受(accept)編號為n的提案。

  3.只有Acceptor沒有接受過提案Proposer才能采用自己的Value,否者Proposer的Value提案為Acceptor中編號最大的Proposer Value;

  4.一個提案被選中需要過半數的Acceptor接受。

根據上述過程當一個proposer發現存在編號更大的提案時將終止提案。這意味着提出一個編號更大的提案會終止之前的提案過程。如果兩個proposer在這種情況下都轉而提出一個編號更大的提案,就可能陷入活鎖,違背了Progress的要求。這種情況下的解決方案是選舉出一個leader,僅允許leader提出提案。但是由於消息傳遞的不確定性,可能有多個proposer自認為自己已經成為leader。Lamport在The Part-Time Parliament一文中描述並解決了這個問題。

step1:,設置時鍾:proposer令localClock=globalClock.incrementAndGet()。為了讓這套系統能正確運行,我們需要一個精確的時鍾。由於操作系統的物理時鍾經常是有偏差的,所以我們決定采用一個邏輯時鍾。時鍾的目的是給系統中 發生的每一個事件編排一個序號。假設我們有一台單獨的機器提供了一個全局的計數器服務。它只支持一個方法:incrementAndGet()。這個方法 的作用是將計數器的值加一,並且返回增加后的值。我們將這個計數器稱為globalClock。globalClock的初始值為0。然后,系統中的每個其它機器,都有一個自己的localClock,它的初始值來自globalClock。

step2:prepare:proposer向所有Acceptor發送一個prepare消息。接收方應返回它最近一次accept的value,以及 accept的時間,若在它還沒有accept過value,那么就返回空。proposer只有在收到過半數的response之后,才可進入下一個階段。一旦收到reject消息,那么就重頭來。

step3:構造Proposal:proposer從prepare階段收到的所有values中選取時間戳最新的一個。如果沒有,那么它自己提議一個value。


step4:發送Proposal:proposer把value發送給其它所有機器,消息的時間戳取自localClock。接收方只要檢查消息時間戳合法,那么就接受此value,把這個value和時間戳寫入到硬盤上,然后答復OK,否則拒絕接受。proposer若收到任何的reject答復,則回到 step1。否則,在收到過半數的OK后,此Proposal被通過。

 

算法圖解如下:

 

 

 

Phase1(准備階段)
每個Server都向Proposer發消息稱自己要成為leader,Server1往Proposer1發、Server2往Proposer2發、Server3往Proposer3發;
現在每個Proposer都接收到了Server1發來的消息但時間不一樣,Proposer2先接收到了,然后是Proposer1,接着才是Proposer3;
Proposer2首先接收到消息所以他從系統中取得一個編號1,Proposer2向Acceptor2和Acceptor3發送一條,編號為1的消
息;接着Proposer1也接收到了Server1發來的消息,取得一個編號2,Proposer1向Acceptor1和Acceptor2發送一條,編號為2的消息; 最后Proposer3也接收到了Server3發來的消息,取得一個編號3,Proposer3向Acceptor2和Acceptor3發送一條,編號為3的消息;

這時Proposer1發送的消息先到達Acceptor1和Acceptor2,這兩個都沒有接收過請求所以接受了請求返回[2,null]給Proposer1,並承諾不接受編號小於2的請求;
此時Proposer2發送的消息到達Acceptor2和Acceptor3,Acceprot3沒有接收過請求返回[1,null]給Proposer2,並承諾不接受編號小於1的請求,但這時Acceptor2已經接受過Proposer1的請求並承諾不接受編號小於的2的請求了,所以Acceptor2拒絕Proposer2的請求;
最后Proposer3發送的消息到達Acceptor2和Acceptor3,Acceptor2接受過提議,但此時編號為3大於Acceptor2的承諾2與Accetpor3的承諾1,所以接受提議返回[3,null];
Proposer2沒收到過半的回復所以重新取得編號4,並發送給Acceptor2和Acceptor3,然后Acceptor2和Acceptor3通過


Phase2(決議階段)
Proposer3收到過半(三個Server中兩個)的返回,並且返回的Value為null,所以Proposer3提交了[3,server3]的議案;
Proposer1收到過半返回,返回的Value為null,所以Proposer1提交了[2,server1]的議案;
Proposer2收到過半返回,返回的Value為null,所以Proposer2提交了[4,server2]的議案;
Acceptor1、Acceptor2接收到Proposer1的提案[2,server1]請求,Acceptor2承諾編號大於4所以拒絕了通過,Acceptor1通過了請求;
Proposer2的提案[4,server2]發送到了Acceptor2、Acceptor3,提案編號為4所以Acceptor2、Acceptor3都通過了提案請求;
Acceptor2、Acceptor3接收到Proposer3的提案[3,server3]請求,Acceptor2、Acceptor3承諾編號大於4所以拒絕了提案;
此時過半的Acceptor都接受了Proposer2的提案[4,server2],Larner感知到了提案的通過,Larner學習提案,server2成為Leader;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM