即Two-phase Commit,參與者將操做成敗通知協調者,再由協調者根據全部參與者的反饋情報決定各參與者是否要提交操做仍是停止操做。php
第一階段(提交請求階段)算法
第二階段(提交執行階段)數據庫
成功安全
當協調者節點從全部參與者節點得到的相應消息都爲"贊成"時:服務器
失敗網絡
若是任一參與者節點在第一階段返回的響應消息爲"終止",或者 協調者節點在第一階段的詢問超時以前沒法獲取全部參與者節點的響應消息時:併發
存在的問題app
3PC是2PC的改進版本,將2PC的第一階段:提交事務階段一分爲二,造成CanCommit、PreCommit和doCommit三個階段組成的事務處理協議。分佈式
3PC爲了解決問題3參與者直到接受到doCommit請求才知道協調者決定而加入了canCommit階段。這樣即便出現了3問題,剩下的參與者也知道這次事務要提交,由於通過前兩階段的詢問,不管是參與者仍是協調者都已知道這次事物是要提交的。性能
爲了解決單點問題和阻塞問題,3PC在參與者這邊也引入了超時機制。在階段一和階段二不管協調者仍是參與者故障,那麼在等待超時後,將拒絕這次事務,故障機器在恢復後,也會拒絕這次事務。在階段三一旦參與者沒法及時收到來自協調者的信息以後,他會默認執行commit,而不會一直持有事務資源並處於阻塞狀態,而等到故障機器恢復,也會對事務進行提交從而達到數據的一致性。
不少博客說3PC爲了解決阻塞問題加入了canCommit階段,說canCommit階段沒有鎖定資源,下降了持有資源的時間。我認爲canCommit階段仍是持有了資源的,由於階段二隻是告知各參與者協調者所作的決定,並且階段三的超時自動提交依賴的是一階段參與者對事務的保證。
存在的問題
瞭解了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。
首先將一次決議角色分爲proposers,acceptors,和learners(容許身兼數職)。proposers提出提案,提案信息包括提案編號和提議的value;acceptor收到提案後能夠接受(accept)提案,若提案得到多數acceptors的接受,則稱該提案被批准(chosen);learners只能「學習」被批准的提案。
而後一致性要知足安全性和活性 :
那如今咱們有P1,P2,P3三個進程,要經過一個關於V的決議,顯而易見咱們獲得一個約束:
P1 acceptor必須接受它第一個收到的提案
那麼咱們就有這樣一個場景,P1決定令V=1,P2決定令V=2,P3決定令V=3,這種場景下acceptors沒法造成多數派,因此咱們須要新的約束。由於進程都是平等的,因此不能對進程進行約束,好比說拒絕某個進程的提案,不讓某個進程提出提案,因此咱們轉而提案進行加強。考慮維護一個全局的遞增序列對提案進行編號(P_ID),這樣P1,P2,P3就有足夠的憑證進行裁決,不妨假設接受大的P_ID。假設P3的P_ID最大,那麼必定時間後,三個進程中V的值將等於3,可是在這必定時間中的某段時間,V的值將被決定爲2(P2的提案提早於P3的提案到達P1),這違反了一致性的安全性。那麼怎麼讓P1拒絕P2或者P3的提案呢,P1在接受到P2的提案以前並無接受到任何消息,P2的提案到達時也只知道本身接受了P2的提案。對acceptor已經沒有更強的約束了,因此咱們轉而對proposer進行約束。既然只能選定一個值,那麼咱們要求proposer提出提案選值的時候必須是存活acceptors中的批准的P_ID最大的提案值。
P2 proposer提出提案選值的時候必須是回覆acceptors中的接受的P_ID最大的提案值
固然咱們來看看徹底體的算法是怎麼描述的
階段一
階段二
那麼如同2PC/3PC 中的協調者和部分參與者doCommit而致使的數據一致性問題解決了嗎?
咱們假設有五個進程ABCDE,某時刻A提出提案P1令V=1,並對BC發送了prepare請求,BC收到prepare請求回覆給了A,A對B發送的V=1的accept請求後終止,B在接受到Accept請求後也終止了。此時E深感本身責任重大,提出V=2的提案P2併發送prepare請求給CD,當C在接受到P2的preapre後,將回復P1{V=1}給E,E在收到C的響應後根據協議只能含淚把值改成1在發送Accept請求。
這樣看來好像是隻要先有提案提出,那麼之後提案的值都是第一次提案的值,這樣好像也知足了安全性和活性。可是根據Paxos算法,也沒有明確要求提案的值不能變。那麼被決定以前提案的值能不能變呢?有這樣一個場景,仍是有五個進程ABCDE,某時刻A提出提案P1令V=1,並對BC發送了prepare請求,可是可憐的C在還未收到prepare請求時掛掉了,固然A也沒收到多數派的響應,天然不會發送accept請求。然後C又成功上線,E命運使然的提出V=2的提案P2併發送prepare請求給CD,而後獲得了CD的接受造成了本身的多數派使得P2被批准,可是若是E發送prepare請求給含有A或B的多數派那結果將徹底不一樣。而不管哪一種狀況度沒有違反活性和安全性的要求,paxos的做用也僅僅是肯定一個值且在分佈式系統保持一致性,至於你想說的是個人值能夠進行屢次改寫啊,那就要將paxos和狀態機聯繫起來。
paxos經過多數派和限制提案的值完美的解決了咱們的問題,可是理論到實際是個艱難的過程。好比怎樣在分佈式環境下維持一個全局惟一遞增的序列,若是是靠數據庫的自增主鍵,那麼整個系統的穩定和性能的瓶頸全都集中於這個單點。paxos算法也沒有限制Proposer的個數,Proposer個數越多,那麼達成一致所形成的碰撞將越多,甚至產生活鎖,若是限制Proposer的個數爲一個,那麼就要考慮惟一的Proposer崩潰要怎麼處理。paxos只是肯定了一個值,離咱們實際運用想要屢次讀寫還有距離,上文也說過想要屢次讀寫,要將paxos和狀態機聯繫起來。
ZAB協議分爲2個部分,讓咱們來看看ZAB協議是怎麼解決這些問題的:
(如下內容來源於http://www.jianshu.com/p/fb527a64deee)
恢復(recovery):當服務初次啓動,或者 leader 節點掛了,系統就會進入恢復模式,直到選出了有合法數量 follower 的新 leader,而後新 leader 負責將整個系統同步到最新狀態
廣播(boardcast)
廣播的過程其實是一個簡化的二階段提交過程:
相比於完整的二階段提交,Zab 協議最大的區別就是不能終止事務,follower 要麼回 ACK 給 leader,要麼拋棄 leader,在某一時刻,leader 的狀態與 follower 的狀態極可能不一致,所以它不能處理 leader 掛掉的狀況,因此 Zab 協議引入了恢復模式來處理這一問題。從另外一角度看,正由於 Zab 的廣播過程不須要終止事務,也就是說不須要全部 follower 都返回 ACK 才能進行 COMMIT,而是隻須要合法數量(2f+1 臺服務器中的 f+1 臺) 的follower,也提高了總體的性能。
恢復(recovery)
因爲以前講的 Zab 協議的廣播部分不能處理 leader 掛掉的狀況,Zab 協議引入了恢復模式來處理這一問題。爲了使 leader 掛了後系統能正常工做,須要解決如下兩個問題:
已經被處理的消息不能丟
這一狀況會出如今如下場景:當 leader 收到合法數量 follower 的 ACKs 後,就向各個 follower 廣播 COMMIT 命令,同時也會在本地執行 COMMIT 並向鏈接的客戶端返回「成功」。可是若是在各個 follower 在收到 COMMIT 命令前 leader 就掛了,致使剩下的服務器並無執行都這條消息。
爲了實現已經被處理的消息不能丟這個目的,Zab 的恢復模式使用瞭如下的策略:
被丟棄的消息不能再次出現
這一狀況會出如今如下場景:當 leader 接收到消息請求生成 proposal 後就掛了,其餘 follower 並無收到此 proposal,所以通過恢復模式從新選了 leader 後,這條消息是被跳過的。 此時,以前掛了的 leader 從新啓動並註冊成了 follower,他保留了被跳過消息的 proposal 狀態,與整個系統的狀態是不一致的,須要將其刪除。 Zab 經過巧妙的設計 zxid 來實現這一目的。一個 zxid 是64位,高 32 是紀元(epoch)編號,每通過一次 leader 選舉產生一個新的 leader,新 leader 會將 epoch 號 +1。低 32 位是消息計數器,每接收到一條消息這個值 +1,新 leader 選舉後這個值重置爲 0。這樣設計的好處是舊的 leader 掛了後重啓,它不會被選舉爲 leader,由於此時它的 zxid 確定小於當前的新 leader。當舊的 leader 做爲 follower 接入新的 leader 後,新的 leader 會讓它將全部的擁有舊的 epoch 號的未被 COMMIT 的 proposal 清除。