2-分佈式一致性協議

分佈式系統的設計,往往會反覆權衡可用性和一致性,於是就產生了一系列的一致性協議,其中最著名的就是二階段提交一些(2PC)、三階段提交協議(3PC)和Paxos協議。

一、二階段提交協議(2PC)

在分佈式系統中,各個節點之間在物理上相互獨立,通過網絡進行溝通和協調。由於存在事務機制,可以保證每個獨立節點上的數據操作可以滿足ACID。但是,相互獨立的節點之間無法準確的知道其他節點中的事務執行情況。所以從理論上講,兩臺機器理論上無法達到一致的狀態。如果想讓分佈式部署的多臺機器中的數據保持一致性,那麼就要保證在所有節點的數據寫操作,要不全部都執行,要麼全部的都不執行。但是,一臺機器在執行本地事務的時候無法知道其他機器中的本地事務的執行結果。所以他也就不知道本次事務到底應該commit還是 roolback。所以,常規的解決辦法就是引入一個“協調者”的組件來統一調度所有分佈式節點"參與者"的執行。

二階段提交的算法思路可以概括爲:參與者將操作成敗通知協調者,再由協調者根據所有參與者的反饋情報決定各參與者是否要提交操作還是中止操作。目前絕大部分的關係型數據庫都是採用二階段提交協議。

1、兩個階段

提交事務請求階段

 協調者發送請求給參與者,通知參與者提交或取消事務,參與者進入投票過程,每個參與者回覆給協調者自己的投票:同意(事務在本地執行成功)或取消(事務本地執行失敗)。

執行事務提交階段

 協調者對上一階段參與者的投票結果進行表決,當所有投票爲“同意”時提交提交事務,否者中止事務,並通知參與者,參與者接到通知後執行相應的操作。




2、優缺點

優點:原理簡單,方便實現

缺點:

同步阻塞:

各個參與者在等待其他參與者響應的過程中,都是處於阻塞過程中的,不能進行其他的任何操作。

單點問題:

協調者是各單點,一旦出現問題,整個分佈式事務將無法進行,如果在階段二中出現問題,其他參與者都將一直處於鎖定事務資源的狀態中。(如果是協調者掛掉,可以重新選舉一個協調者,但是無法解決因爲協調者宕機導致的參與者處於阻塞狀態的問題)

數據不一致:

在二階段提交的階段二中,當協調者向參與者發送commit請求之後,發生了局部網絡異常或者在發送commit請求過程中協調者發生了故障,這回導致只有一部分參與者接受到了commit請求。而在這部分參與者接到commit請求之後就會執行commit操作。但是其他部分未接到commit請求的機器則無法執行事務提交。於是整個分佈式系統便出現了數據部一致性的現象。

二階段無法解決的問題:

協調者再發出commit消息之後宕機,而唯一接收到這條消息的參與者同時也宕機了。那麼即使協調者通過選舉協議產生了新的協調者,這條事務的狀態也是不確定的,沒人知道事務是否被已經提交。

二、三階段提交(3PC)

三階段提交(Three-phase commit),也叫三階段提交協議(Three-phase commit protocol),是二階段提交(2PC)的改進版本。

與兩階段提交不同的是,三階段提交有兩個改動點。

1、引入超時機制。同時在協調者和參與者中都引入超時機制。
2、在第一階段和第二階段中插入一個準備階段。保證了在最後提交階段之前各參與節點的狀態是一致的。

也就是說,除了引入超時機制之外,3PC把2PC的準備階段再次一分爲二,這樣三階段提交就有CanCommit、PreCommit、DoCommit三個階段。


1、CanCommit階段

3PC的CanCommit階段其實和2PC的準備階段很像。協調者向參與者發送commit請求,參與者如果可以提交就返回Yes響應,否則返回No響應。

1.事務詢問 協調者向參與者發送CanCommit請求。詢問是否可以執行事務提交操作。然後開始等待參與者的響應。

2.響應反饋 參與者接到CanCommit請求之後,正常情況下,如果其自身認爲可以順利執行事務,則返回Yes響應,並進入預備狀態。否則反饋No

2、PreCommit階段

協調者根據參與者的反應情況來決定是否可以進行事務的PreCommit操作。根據響應情況,有以下兩種可能。

a)、假如協調者從所有的參與者獲得的反饋都是Yes響應,那麼就會執行事務的預執行

  • 發送預提交請求 協調者向參與者發送PreCommit請求,並進入Prepared階段。
  • 事務預提交 參與者接收到PreCommit請求後,會執行事務操作,並將undo和redo信息記錄到事務日誌中。
  • 響應反饋 如果參與者成功的執行了事務操作,則返回ACK響應,同時開始等待最終指令。

b)、假如有任何一個參與者向協調者發送了No響應,或者等待超時之後,協調者都沒有接到參與者的響應,那麼就執行事務的中斷。

  • 發送中斷請求 協調者向所有參與者發送abort請求。
  • 中斷事務 參與者收到來自協調者的abort請求之後(或超時之後,仍未收到協調者的請求),執行事務的中斷。

3、doCommit階段

該階段進行真正的事務提交,也可以分爲以下兩種情況。

a)、執行提交

  • 發送提交請求 協調接收到參與者發送的ACK響應,那麼他將從預提交狀態進入到提交狀態。並向所有參與者發送doCommit請求。
  • 事務提交 參與者接收到doCommit請求之後,執行正式的事務提交。並在完成事務提交之後釋放所有事務資源。
  • 響應反饋 事務提交完之後,向協調者發送Ack響應。
  • 完成事務 協調者接收到所有參與者的ack響應之後,完成事務。

b)、中斷事務 協調者沒有接收到參與者發送的ACK響應(可能是接受者發送的不是ACK響應,也可能響應超時),那麼就會執行中斷事務。

  • 發送中斷請求 協調者向所有參與者發送abort請求
  • 事務回滾 參與者接收到abort請求之後,利用其在階段二記錄的undo信息來執行事務的回滾操作,並在完成回滾之後釋放所有的事務資源。
  • 反饋結果 參與者完成事務回滾之後,向協調者發送ACK消息
  • 中斷事務 協調者接收到參與者反饋的ACK消息之後,執行事務的中斷。

在doCommit階段,如果參與者無法及時接收到來自協調者的doCommit或者rebort請求時,會在等待超時之後,會繼續進行事務的提交。(其實這個應該是基於概率來決定的,當進入第三階段時,說明參與者在第二階段已經收到了PreCommit請求,那麼協調者產生PreCommit請求的前提條件是他在第二階段開始之前,收到所有參與者的CanCommit響應都是Yes。(一旦參與者收到了PreCommit,意味他知道大家其實都同意修改了)所以,一句話概括就是,當進入第三階段時,由於網絡超時等原因,雖然參與者沒有收到commit或者abort響應,但是他有理由相信:成功提交的機率很大。 )

相對於2PC,3PC主要解決阻塞問題,因爲一旦參與者無法及時收到來自協調者的信息之後,他會默認執行commit。而不會一直持有事務資源並處於阻塞狀態。但是這種機制也會導致數據一致性問題,因爲,由於網絡原因,協調者發送的abort響應沒有及時被參與者接收到,那麼參與者在等待超時之後執行了commit操作。這樣就和其他接到abort命令並執行回滾的參與者之間存在數據不一致的情況。


三、Paxos算法

瞭解了2PC和3PC之後,我們可以發現,無論是二階段提交還是三階段都是存在單點的問題的。Google Chubby的作者Mike Burrows說過, there is only one consensus protocol, and that’s Paxos” – all other approaches are just broken versions of Paxos. 意即世上只有一種一致性算法,那就是Paxos,所有其他一致性算法都是Paxos算法的不完整版。

paxos算法的詳細描述請參考:https://zh.wikipedia.org/wiki/Paxos%E7%AE%97%E6%B3%95 ,維基百科對paxos算法的描述,屬於比較晦澀難懂的,直接看這個難度太大。

下面我們再回到上面的哪個問題,分佈式事務提交或者中止的問題,來通過實例的方式的介紹paxos算法。

分佈式一致性其實主要是指分佈式系統中的數據一致性問題,在一個分佈式數據庫系統中,如果各節點的初始狀態一致,每個節點都執行相同的操作序列,那麼他們最後能得到一個一致的狀態。爲保證每個節點執行相同的命令序列,需要在每一條指令上執行一個“一致性算法”以保證每個節點看到的指令一致。再簡化其實就是要滿足類似於上面2PC、3PC中的一次分佈式事務的提交,所有參與者要麼全部都是提交、要麼全部都是中止,這樣才能保證數據的一致性。

paxos算法過程描述

上面2PC和3PC都有各一個問題就是單點問題,協調者都是單點的。爲了解決單點問題,所以需要引入多個協調者,協調者可能出現響應慢、down機、重啓,協調者和參與者之前也會出現通信異常。paxos算法解決的就是在一個可能發生上述異常的分佈式系統中如何就某個事務的提交和中止達成一致,保證不論發生以上任何異常,都不會破壞決議的一致性。

我們這裏就把參與者叫做Proposer,協調者叫Acceptor,proposer需要向協調者acceptor提議此次事務是commit還是rollback,我們稱之爲提交提議,如果提議被一半以上的acceptor接受了,那麼就稱爲這個提議被批准了,成爲了決議,即這個決議表明這次事務是commit還是rollback。(因爲任意兩個半數以上的acceptor集合至少包含一個共同的成員)

1、proposer向acceptor提提議的過程需要編號,這個編號在所有proposer中需要全局遞增的(#1,#2,#3 ....

2、proposer向acceptor提提議需要先獲得acceptor的訪問權,也就是說每次提提議分爲兩個階段:首先申請訪問acceptor的權限(定義命令:apply(#1)),允許訪問後,才能向acceptor提交提議(定義命令:accept(#1,v), 這裏的v是commit或者rollback)。

3、acceptor會記錄當前訪問權限的編號,以及接受的提議的編號和值

4、acceptor提供兩個接口:

申請訪問權限:apply(#n),如果申請成功返回<ok,#m,v> #m和v表示當前接受提議的編號和內容,沒有接受過任何提議則返回<ok, null,null>;如果申請權限失敗則返回<error> 

提交提議:accept(#n,v),如果成功則返回<ok,#n,v> #n和v表示當前接受提議的編號和內容,如果失敗則返回<error>

5、acceptor需要堅持"喜新厭舊"的原則,這裏的新舊指的是編號的大小,編號越大越新。如果之前給了小的編號的訪問權,如果來了一個大的編號的申請訪問權,要把訪問權給大的編號的proposer,之後如果之前小的編號提交提議會直接返回<error>。

5、acceptor如果收到一個編號爲N的提議,如果它之前沒有接受過比N大的編號的提議,他就可以接收編號爲N的提議。

6、propser 申請權限後,如果收到了來自半數以上的acceptor的響應結果,如果某一個提議被半數以上acceptor接受,則這個提議就稱爲了決議,如果沒有達到半數,則選取編號最大的提議的值爲這次提交的值,如果所有的acceptor都沒有接受過提議,那麼這個proposer就可以自己指定提議的值。


協議的運行過程:

http://pan.baidu.com/s/1qYBtF0C


 一些問題:

在什麼情況下提議的值被批准也就是形成決議?

paxos的兩階段分別做什麼?

在第一階段結束後,如果獲取的值都爲空,爲何就能保證舊的編號無法形成決議?

如何保證新的編號不會破壞已經形成決議的值?

在形成決議後,如果出現半數一下acceptor故障,爲何決議不會被更改?


參考資料:

從Paxos到Zookeeper

http://www.hollischuang.com/archives/681

http://yale.iteye.com/blog/1541527

http://www.hollischuang.com/archives/693

http://iunknown.iteye.com/blog/2246484



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