漫談分散式事務的那些解決方案
事務我們都不陌生,我們常說的事務一般都是指單機事務,即本地事務。那分散式事務是什麼?分散式事務就是由多個本地事務組合而成的事務,一般在分散式場景下才會出現。
比如電商平台中,我們在購物的時候,下單支付這個過程看上去是一氣呵成的,但是背後可能是多個系統的分工合作。訂單系統、支付系統、物流系統等。這些系統部署在不同的伺服器上,執行的都是各種的事務,對於電商平台來說,這就是分散式事務。
本地事務都好解決,有一套現成的事務機制,分散式事務比本地事務就要複雜多。如何實現分散式事務呢?大概有 3 種解決方式:
- 基於 XA 協議的二階段提交協議。
- 三階段提交協議。
- 基於 MQ 的最終一致性。
基於 XA 協議的二階段提交
XA協議由 Tuxedo 首先提出的,並交給X/Open組織,作為資源管理器(資料庫)與事務管理器的介面標準。目前,Oracle、Informix、DB2和Sybase等各大資料庫廠家都提供對XA的支援。----來源百度百科
二階段提交也叫 2PC ,The two-phase commit protocol。首先在二階段提交中有兩個角色:
- 參與者:本地資源管理器,即事務的執行者,也就是各個業務系統。
- 協調者:分散式事務的大腦,負責指揮協調各個業務系統提交/回滾事務。
所謂得兩階段提交就是指投票(voting)和提交(commit) 兩個階段,跟選舉制度一樣,先投票,再決定。
投票階段,協調者向參與者發起執行事務操作的請求(CanCommit 請求),並等待參與者響應。
參與者接受到請求後,執行事務請求操作,記錄日誌資訊但不提交,記錄成功後,向協調者發生 「Yes」 消息,表示同意提交操作,若不成功,則發送「No」 消息,表示不同意這次操作。注意這個過程會鎖定數據。
投票階段得流程圖,大概就是下面這樣子:
提交階段,協調者接受到所有參與者的響應之後,根據返回來的資訊情況,向參與者發送提交或回滾請求。
-
若收到的響應消息都是 「Yes」,則向參與者發送 「DoCommit」 消息,參與者完成本地事務的其他操作並釋放資源,然後向協調者發送 「HaveCommitted」消息;
-
如果協調者收到的消息中包含「No」消息或者在規定時間內有參與者沒有響應,則向所有參與者發送「DoAbort」消息,此時發送「Yes」的參與者則會根據之前執行操作時的回滾日誌對操作進行回滾,然後所有參與者會向協調者發送「HaveCommitted」消息;
提交階段的流程,大概入下圖所示:
二階段提交協議容易理解,基於 XA 的二階段提交演算法滿足事務的 ACID 特性,看上去比較完美,但是缺點還是挺多的,主要有以下幾個問題:
- 同步阻塞問題:二階段提交在執行過程中,所有參與節點都是事務阻塞型的,參與者會鎖定數據,其他訪問者要訪問該數據的話,都會被阻塞。
- 單點故障問題。在二階段提交協議中,協調者只有一台,一旦協調者發送故障,整個系統都會處於停滯階段。特別是提交階段,如果協調者掛了的話,參與者就會一直等待協調者回應,會處於阻塞中。
- 數據不一致問題:在提交階段,協調者向參與者發送 DoCommit 請求後,由於網路抖動或者在發送請求的過程中,協調者發生故障,就會導致只有一部分參與者接收到了提交請求並執行提交操作,但其他未接到提交請求的那部分參與者則無法執行事務提交。於是整個分散式系統便出現了數據不一致的問題。
三階段提交
三階段提交協議(Three-phase commit protocol,3PC)是對二階段提交(2PC)的改進。解決了二階段提交的一些問題,三階段和二階段提交最大的不同是引入超時機制和準備階段。
先來說說超時機制,在二階段提交,只有協調者才有超時機制,如果協調者在規定時間內沒有接收到參與者的響應,就會根據當前狀態提交或者終止整個事務,但是如果協調者掛了,參與者並沒有超時機制,所以就一直等待,這也是二階段提交單點故障的問題。在三階段提交中,同時在協調者和參與者中引入超時機制。如果協調者或參與者在規定的時間內沒有接收到來自其他節點的響應,就會根據當前的狀態選擇提交或者終止整個事務。
三階段提交其實就是將二階段提交中的提交階段一分為二,三階段提交協議中的具體三階段是:CanCommit、PreCommit、DoCommit 三個階段
CanCommit 階段,CanCommit 階段與 2PC 的投票階段類似:協調者向參與者發送請求操作(CanCommit 請求),詢問參與者是否可以執行事務提交操作,然後等待參與者的響應;參與者收到 CanCommit 請求之後,回復 Yes,表示可以順利執行事務;否則回復 No。
PreCommit 階段,根據二階段提交中的提交階段相似,根據 CanCommit 階段返回的結果,來決定是否可以進行 PreCommit 操作。
這時候就存在兩種情況,如果所有參與者都回復 「Yes」,那麼執行流程是這樣的:
- 1、協調者發送預提交請求:協調者向參與者發送 PreCommit 請求,進入預提交階段.
- 2、事務預提交:參與者接收到 PreCommit 請求後執行事務操作,並將 Undo 和 Redo 資訊記錄到事務日誌中。
- 3、響應回饋:如果參與者成功執行了事務操作,則返回 ACK 響應,同時開始等待最終指令。
如果有參與者返回 「No」,或者協調者在規定時間內沒有收到參與者的響應,那麼將執行中斷事務操作。流程是這樣的:
- 1、發送中斷請求:協調者向所有參與者發送「Abort」消息。
- 2、中斷事務:參與者收到「Abort」消息之後,或超時後仍未收到協調者的消息,執行事務的中斷操作。
DoCommit 階段, 事務真正提交階段,協調者根據 PreCommit 階段參與者返回來的資訊,決定是進入提交階段還是事務中斷階段。
提交階段流程如下:
- 1、發送提交請求:協調者接收到所有參與者發送的 Ack 響應,從預提交狀態進入到提交狀態,並向所有參與者發送 DoCommit 消息。
- 2、事務提交:參與者接收到 DoCommit 消息之後,正式提交事務。完成事務提交之後,釋放所有鎖住的資源。
- 3、響應回饋:參與者提交完事務之後,向協調者發送 Ack 響應。
- 4、完成事務:協調者接收到所有參與者的 Ack 響應之後,完成事務。
事務中斷階段,流程如下:
- 1、發送中斷請求:協調者向所有參與者發送 Abort 請求。
- 2、事務回滾:參與者接收到 Abort 消息之後,利用其在 PreCommit 階段記錄的 Undo 資訊執行事務的回滾操作,並釋放所有鎖住的資源。
- 3、回饋結果:參與者完成事務回滾之後,向協調者發送 Ack 消息。
- 4、中斷事務:協調者接收到參與者回饋的 Ack 消息之後,執行事務的中斷,並結束事務。
基於分散式消息的最終一致性方案
不管是二階段提交還是三階段提交,都屬於強一致性的,滿足事務的 ACID 原則。它們都有兩個共同的問題:
- 1、需要鎖定數據,降低了系統性能。
- 2、因為網路等原因,並沒有完全解決數據一致性問題。
而基於 MQ 消息的分散式解決方案就不太一樣了,它採用的不是強一致性,而是最終一致性,這也就是 BASE 理論。並且我們直到 MQ 是非同步的,所以性能也比較快,可以說完美的解決了上面兩種方式帶來的問題。
基於 MQ 消息中間件解決分散式事務的思路是這樣的:主要是基於 MQ 消息投遞的可靠性,將分散式事務發送給 MQ 中間件之後,中間件將事務持久化,這一點非常重要,保證消息不丟失。消費者端非同步消費,如果遇到失敗情況,由於我們的消息是持久化的,所以可以根據業務規則不斷重試,有必要的話,人工補償,保證數據最終一致性。
關於基於分散式消息的最終一致性方案,我準備基於 RocketMQ 單獨開一個章節,詳細聊一聊,這裡就不多說了。
歡迎關注公眾號【互聯網平頭哥】。關注這個互聯網苟且偷生的程式設計師,願你我共同進步,今天最好的是明天最低的要求。

