為了解決分散式系統的一致性問題,在長期的探索研究過程中,湧現出了一大批經典的一致性協議和演算法,其中最著名的就是二階段提交協議、三階段提交協議和Paxos演算法。
2PC與3PC
在分散式系統中,每一個機器節點雖然都能夠明確地知道自己在進行事務操作過程中的結果是成功或失敗,但卻無法直接獲取到其他分散式節點的操作結果。因此,當一個事務操作需要跨越多個分散式節點的時候,為了保持事務處理的ACID特性,就需要引入一個稱為”協調者“的元件來統一排程所有分散式節點的執行邏輯,這些被排程的分散式節點則被稱為“參與者”。協調者負責排程參與者的行為,並最終決定這些參與者是否要把事務真正進行提交。基於這個思想,衍生出了二階段提交和三階段提交兩種協議。
2PC
2PC,是Two-Phase Commit的縮寫,即二階段提交,是計算機網路尤其是在資料庫領域內,為了使基於分散式系統架構下的所有節點在進行事務處理過程中能夠保持原子性和一致性而設計的一種演算法。通常,二階段提交協議也被認為是一種強一致性協議,用來保證分散式系統資料的一致性。
2PC執行流程
二階段提交協議是將事務的提交過程拆分成了兩個階段來進行處理,其執行流程如下:
階段一:提交事務請求
1、事務詢問
協調者向所有的參與者傳送事務內容,詢問是否可以執行事務提交操作,並開始等待各參與者響應。
2、執行事務
各參與者節點執行事務操作,並將Undo和Redo資訊記入事務日誌中。
3、各參與者向協調者反饋事務詢問的響應
如果參與者成功執行了事務操作,那麼就反饋給協調者Yes響應,表示事務可以執行;如果參與者沒有成功執行事務,那麼就反饋給協調者No響應,表示事務不可以執行。
階段二:執行事務提交
協調者會根據各參與者的反饋情況來決定最終是否可以進行事務提交操作,正常情況下,包含以下兩種可能:
執行事務提交:假如協調者從所有的參與者獲得的反饋都是Yes響應,那麼就會執行事務提交。
1、 傳送提交請求
協調者向所有參與者節點發出Commit請求。
2、事務提交
參與者接收到Commit請求後,會正式執行事務提交操作,並在完成提交之後釋放在整個事務執行期間佔用的事務資源。
3、 反饋事務提交結果
參與者在完成事務提交之後,向協調者傳送Ack訊息。
4、完成事務
協調者接收到所有參與者反饋的Ack訊息後,完成事務。
中斷事務:假如任何一個參與者向協調者反饋了No響應,或者在等待超時之後,協調者尚無法接收到所有參與者的反饋響應,那麼就會中斷事務。
1、傳送回滾請求
協調者向所有參與者節點發出Rollback請求。
2、事務回滾
參與者接收到Rollback請求後,會利用其在階段一中記錄的Undo資訊來執行事務回滾操作,並在完成回滾之後釋放在整個事務執行期間佔用的資源。
3、反饋事務回滾結果
參與者在完成事務回滾之後,向協調者傳送Ack訊息。
4、中斷事務
協調者接收到所有參與者反饋的Ack訊息後,完成事務中斷。
簡單地講,二階段提交將一個事務的處理過程分為了投票和執行兩個階段,其核心是對每個事務都採用先嚐試後提交的處理方式,因此也可以將二階段提交看作一個強一致性的演算法。
2PC存在的問題
1、同步阻塞
在執行過程中,所有參與該事務操作的邏輯都處理於阻塞狀態。即節點之間在等待對方的相應訊息時,它將什麼也做不了。特別是,當一個節點在已經佔有了某項資源的情況下,為了等待其他節點的響應訊息而陷入阻塞狀態時,當第三個節點嘗試訪問該節點佔有的資源時,這個節點也將連帶陷入阻塞狀態。
2、 單點問題
一旦協調者出現問題,那麼整個二階段提交流程將無法運轉,更為嚴重的是,如果協調者是在階段二中出現問題的話,那麼其他參與者將會一直處於鎖定事務資源的狀態中,而無法繼續完成事務操作。
3、 資料不一致
當協調者向所有的參與者傳送Commit請求之後發生了區域性網路異常或者是協調者在尚未傳送完Commit請求之前自身發生了崩貴,導致最終只有部分參與者收到了Commit請求,於是整個分散式系統便出現了資料不一致性現象。
3、太過保守
二階段提交沒有設計較為完善的容錯機制,任意一個節點的失敗都會導致整個事務的失敗。
3PC
3PC,是Three-Phase Commit的縮寫,即三階段提交,是2PC的改進版。由CanCommit、PreCommit和doCommit三個階段組成的事務處理協議。
3PC執行流程
階段一:CanCommit
1、事務詢問。
協調者向所有的參與者傳送一個包含事務內容的canCommit請求,詢問是否可以執行事務提交操作,並開始等待各參與者的響應。
2、各參與者向協調者反饋事務詢問的響應。
參與者在接收到來自協調者的canCommit請求後,正常情況下,如果其自身認為可以順利執行事務,那麼會反饋Yes響應,並進入預備狀態,否則反饋No響應。
階段二:PreCommit
協調者會根據各參與者的反饋情況來決定最終是否可以進行事務提交操作,正常情況下,包含以下兩種可能:
執行事務預提交;假如協調者從所有的參與者獲得的反饋都是Yes響應,那麼就會執行事務的預執行。
1、傳送預提交請求
協調者向所有參與者節點出preCommit的請求,並進入prepared階段。
2、事務預提交
參與者接收到preCommit請求後,會執行事務操作,並將Undo和Redo資訊記錄到事務日誌中。
3、各參與者向協調者反饋事務執行的響應
如果參與者成功執行了事務操作,那麼就會反饋給協調者Ack響應,同時等待最終的指令:提交(commit)或中止(abort)。
中斷事務:假如任何一個參與者向協調者反饋了No響應,或者在等待超時之後,協調者尚無法接收到所有者的反饋響應,那麼就會中斷事務。。
1、傳送中斷請求
協調者向所有參與者節點發生abort請求。
2、中斷事務
無論是收到來自協調者的abort請求,或者是在等待協調者請求過程中出現超時,參與者都會中斷事務。
階段三:doCommit
該階段將進行真正的事務提交,會存在以下兩種可能的情況。
執行提交
1、傳送提交請求
進入這一階段,假如協調者處於正常狀態,並且它接收到了來自所有參與者的Ack響應,那麼它將從“預提交”狀態轉換到“提交”狀態,並向所有參與者傳送doCommit請求。
2、事務提交
參與者接收到doCommit請求後,會正式執行事務提交操作,並在完成提交之後釋放在整個事務執行期間佔用的事務資源。
3、反饋事務提交結果
參與者在完成事務提交之後,向協調者傳送Ack訊息。
4、完成事務
協調者接收到所有參與者反饋的Ack訊息後,完成事務。
中斷事務
進入這一階段,假設協調者處於正常工作狀態,並且有任意一個參與者向協調者反饋了No響應,或者在等待超時之後,協調者尚無法接收到所有參與者的反饋響應。
1、傳送中斷請求
協調者向所有的參與者節點傳送abort請求。
2、事務回滾
參與者接收到abort請求後,會利用其在階段二中記錄的Undo資訊來執行事務回滾操作,並在完成回滾之後釋放在整個事務執行期間佔用的資源。
3、反饋事務回滾結果
參與者在完成事務會滾之後,向協調者傳送Ack訊息。
4、中斷事務
協調者接收到所有參與者反饋的Ack訊息後,中斷事務。
在doCommit階段,如果參與者無法及時接收到來自協調者的doCommit或者abort請求時,在等待超時之後,會繼續進行事務的提交。即當進入第三階段時,由於網路超時等原因,雖然參與者沒有收到協調者的doCommit或者abort請求,但事務仍然會提交。
3PC存在的問題
三階段提交協議與兩階段提交協議有兩個主要的不同:
-
增加了一個詢問階段,詢問階段可以確保儘可能早的發現無法執行操作而需要中止的行為,但是它並不能發現所有的這種行為,只會減少這種情況的發生。
-
在準備階段以後,協調者和參與者執行的任務中都增加了超時,一旦超時,協調者和參與者都繼續提交事務,預設為成功,這也是根據概率統計上超時後預設成功的正確性最大。
三階段提交協議在去除阻塞的同時也引入了新的問題,那就是在參與者接收到preCommit訊息後,如果網路出現分割槽,此時協調者所在的節點和參與者無法進行正常的網路通訊,在這種情況下,該參與者依然會進行事務的提交,這必然出現資料的不一致性。
Paxos演算法
Paxos演算法是一種基於訊息傳遞且具有高度容錯特性的一致性演算法,是目前公認的解決分散式一致性問題最有效的演算法之一。
問題描述
在古希臘的有一個叫做Paxos的小島,島上採用會議的形式來通過法令,議會中的議員通過信使進行訊息的傳遞。值得注意的是,議員和信使都是兼職的,他們隨時有可能會離開議會廳,並且信使可能會重複的傳遞資訊,也可能一去不復返。因此,議會協議要保證在這個情況下法令仍然能夠正確的產生,並且不會出現衝突。
首先將議員的角色分為Proposer,Acceptor,和Learner(允許身兼數職)。Proposer提出提案,提案資訊包括提案編號和提議的value;Acceptor收到提案後可以接受(accept)提案,如果提案獲得多數Acceptors的接受,則稱該提案被批准(chosen);Learner只能“學習”被批准的提案。劃分角色後,就可以更精確的定義問題:
-
決議(value)只有在被Proposers提出後才能被批准(未經批准的決議稱為“提案”(proposal);
-
在一次Paxos演算法的執行例項中,只批准(chosen)一個value;
-
learners只能獲得被批准(chosen)的value。
由上面的三個語義可演化為下面幾個約束:
P1:一個Acceptor必須接受(accept)第一次收到的提案。
P2:一旦一個具有value v的提案被批准(chosen),那麼之後批准(chosen)的提案必須具有value v。
P2a:一旦一個具有value v的提案被批准(chosen),那麼之後任何Acceptor再次接受(accept)的提案必須具有value v。
P2b:一旦一個具有value v的提案被批准(chosen),那麼以後任何Proposer提出的提案必須具有value v。
P2c:如果一個編號為n的提案具有value v,那麼存在一個多數派,要麼他們中所有人都沒有接受(accept)編號小於n的任何提案,要麼他們已經接受(accept)的所有編號小於n的提案中編號最大的那個提案具有value v。
P1a:當且僅當Acceptor沒有迴應過編號大於n的prepare請求時,Acceptor接受(accept)編號為n的提案。
Paxos演算法內容
決議的提出與批准
階段一(決議提出)
1、Proposer選擇一個提案編號M,然後向Acceptors的某個超過半數的子整合員傳送編號為M的prepare請求。
2、如果一個Acceptor接收到一個編號為M的pepare請求,且編號M大於該Acceptor已經響應的所有prepare請求的編號,那麼它就會將它已經批准過的最大編號的提案作為響應反饋給Proposer,同時該Acceptor會承諾不會再批准任何編號小於M的提案。
階段二(批准階段)
1、如果Proposer收到來自半數以上的Acceptor對其發出的編號為M的prepare請求的響應,那麼它就會傳送一個針對[M,V]提案的accept請求給Acceptor。注意,V的值就是收到響應中編號最大的提案的值,如果響應中不包含任何提案,那麼它就是任意值。
2、如果Acceptor收到這個針對[M,V]提案的Accept請求,只要該Acceptor尚未對編號大於M的prepare請求做出響應,它就可以通過這個提案。
提案的獲取
如何讓Learner獲取提案,大體可以有以下幾種方案:
-
方案一
Learner獲取一個已經被選定的提案的前提是,該提案已經被半數以上的Acceptor批准了。因此,最簡單的做法就是一旦Acceptor批准了一個提案,就將該提案傳送給所有的Learner。這種做法雖然可以讓Learner儘快地獲取被選定的提案,但是卻需要讓每個Acceptor與所有Learner逐個進行一次通訊,通訊的次數至少為二者個數的乘積。
-
方案二
我們可以讓所有的Acceptor將它們對提案的批准情況,統一傳送給一個特定的Learner(這樣的Learner稱為“主Learner”),假定Learner之間可以通過訊息通訊來互相感知提案的選定情況。當主Learner被通知一個提案被選定時,它會負責通知其他的Learner。
方案二雖然需要多一個步驟才能將提案通知到所有的Learner,但其通訊次數卻大大減少了,通常只是Acceptor和Learner的個數總和。但同時,該方案引入了一個新的不穩定因素;主Learner隨時可能出現故障。
-
方案三
將主Learner的範圍擴大,即Acceptor可以將批准的提案傳送給一個特定的Learner集合,該集合中的每個Learner都可以在一個提案被選定後通知所有其他的Learner。這個Learner集合中的Learner個數越多,可靠性就越好,但同時網路通訊的複雜度也就越高。
通過選取主Proposer保證演算法的活性
假設存在這樣一種極端情況,有兩個Proposer依次提出了一系列編號遞增的議案,但是最終都無法被選定,具體流程如下:
Proposer P1提出了一個編號為M1的提案,並完成了上述階段一的流程。但與此同時,另外一個Proposer P2提出了一個編號為M2(M2>M1)的提案,同樣也完成了階段一的流程,於是Acceptor已經承諾不再批准編號小於M2的提案了。因此,當P1進入階段二的時候,其發出的accept請求將被Acceptor忽略,於是P1再次進入階段一併提出了一個編號為M3,(M3>M2)的提案,而這又導致P2在第二階段的accept請求被忽略,以此類推,提案的選定過程將陷入死迴圈。
為了保證Paxos演算法流程的可持續性,以避免陷入上述提到的“死迴圈”,就必須選擇一個主Proposer,並規定只有主Proposer才能提出議案。這樣一來,只要主Proposer和過半的Acceptor能夠正常進行網路通訊,那麼但凡主Proposer提出一個編號更高的提案被提出或正在接受批准,那麼它會丟棄當前這個編號較小的提案,並最終能夠選出一個編號足夠大的提案。因此,如果系統中有足夠多的組建(包括Proposer、Acceptor和其他網路通訊組建)能夠正常工作,那麼通過選擇一個主Proposer,整套Paxos演算法流程就能夠保持活性。
參考資料
從Paxos到Zookeeper分散式一致性原理與實踐