Linux網路IO模型
分散式事務顧名思義就是要在分散式系統中實現事務,它其實是由多個本地事務組合而成。
對於分散式事務而言幾乎滿足不了 ACID,其實對於單機事務而言大部分情況下也沒有滿足 ACID,不然怎麼會有四種隔離級別呢?所以更別說分佈在不同資料庫或者不同應用上的分散式事務了。
2PC
二階段提交是一種強一致性設計
2PC 引入一個事務協調者的角色來協調管理各參與者(也可稱之為各本地資源)的提交和回滾,二階段分別指的是準備(投票)和提交兩個階段。
準備階段協調者會給各參與者傳送準備命令,你可以把準備命令理解成除了提交事務之外啥事都做完了。
同步等待所有資源的響應之後就進入第二階段即提交階段(注意提交階段不一定是提交事務,也可能是回滾事務)。
假如在第一階段所有參與者都返回準備成功,那麼協調者則向所有參與者傳送提交事務命令,然後等待所有事務都提交成功之後,返回事務執行成功。
那可能就有人問了,那第二階段提交失敗的話呢?
這裡有兩種情況。
第一種是第二階段執行的是回滾事務操作,那麼答案是不斷重試,直到所有參與者都回滾了,不然那些在第一階段準備成功的參與者會一直阻塞著。
第二種是第二階段執行的是提交事務操作,那麼答案也是不斷重試,因為有可能一些參與者的事務已經提交成功了,這個時候只有一條路,就是頭鐵往前衝,不斷的重試,直到提交成功,到最後真的不行只能人工介入處理。
首先 2PC 是一個同步阻塞協議,像第一階段協調者會等待所有參與者響應才會進行下一步操作,當然第一階段的協調者有超時機制,假設因為網路原因沒有收到某參與者的響應或某參與者掛了,那麼超時後就會判斷事務失敗,向所有參與者傳送回滾命令。
3PC
PC 的出現是為了解決 2PC 的一些問題,相比於 2PC 它在參與者中也引入了超時機制,並且新增了一個階段使得參與者可以利用這一個階段統一各自的狀態。
3PC 包含了三個階段,分別是準備階段、預提交階段和提交階段,對應的英文就是:CanCommit、PreCommit 和 DoCommit
。
看起來是把 2PC 的提交階段變成了預提交階段和提交階段,但是 3PC 的準備階段協調者只是詢問參與者的自身狀況,比如你現在還好嗎?負載重不重?這類的。
TCC
2PC 和 3PC 都是資料庫層面的,而 TCC 是業務層面的分散式事務,就像我前面說的分散式事務不僅僅包括資料庫的操作,還包括髮送簡訊等,這時候 TCC 就派上用場了!
TCC 指的是Try - Confirm - Cancel
。
- Try 指的是預留,即資源的預留和鎖定,注意是預留。
- Confirm 指的是確認操作,這一步其實就是真正的執行了。
- Cancel 指的是撤銷操作,可以理解為把預留階段的動作撤銷了。
其實從思想上看和 2PC 差不多,都是先試探性的執行,如果都可以那就真正的執行,如果不行就回滾。
我們來看下流程,TCC模型還有個事務管理者的角色,用來記錄TCC全域性事務狀態並提交或者回滾事務。
可以看到流程還是很簡單的,難點在於業務上的定義,對於每一個操作你都需要定義三個動作分別對應Try - Confirm - Cancel
。
因此TCC 對業務的侵入較大和業務緊耦合,需要根據特定的場景和業務邏輯來設計相應的操作。
還有一點要注意,撤銷和確認操作的執行可能需要重試,因此還需要保證操作的冪等。
相對於 2PC、3PC ,TCC 適用的範圍更大,但是開發量也更大,畢竟都在業務上實現,而且有時候你會發現這三個方法還真不好寫。不過也因為是在業務上實現的,所以TCC可以跨資料庫、跨不同的業務系統來實現事務。
本地訊息表
本地訊息表其實就是利用了各系統本地的事務來實現分散式事務。
本地訊息表顧名思義就是會有一張存放本地訊息的表,一般都是放在資料庫中,然後在執行業務的時候將業務的執行和將訊息放入訊息表中的操作放在同一個事務中,這樣就能保證訊息放入本地表中業務肯定是執行成功的。
然後再去呼叫下一個操作,如果下一個操作呼叫成功了好說,訊息表的訊息狀態可以直接改成已成功。
如果呼叫失敗也沒事,會有後臺任務定時去讀取本地訊息表,篩選出還未成功的訊息再呼叫對應的服務,服務更新成功了再變更訊息的狀態。
這時候有可能訊息對應的操作不成功,因此也需要重試,重試就得保證對應服務的方法是冪等的,而且一般重試會有最大次數,超過最大次數可以記錄下報警讓人工處理。
可以看到本地訊息表其實實現的是最終一致性,容忍了資料暫時不一致的情況。
訊息事務
RocketMQ 就很好的支援了訊息事務,讓我們來看一下如何通過訊息實現事務。
第一步先給 Broker 傳送事務訊息即半訊息,半訊息不是說一半訊息,而是這個訊息對消費者來說不可見,然後傳送成功後傳送方再執行本地事務。
再根據本地事務的結果向 Broker 傳送 Commit 或者 RollBack 命令。
並且 RocketMQ 的傳送方會提供一個反查事務狀態介面,如果一段時間內半訊息沒有收到任何操作請求,那麼 Broker 會通過反查介面得知傳送方事務是否執行成功,然後執行 Commit 或者 RollBack 命令。
如果是 Commit 那麼訂閱方就能收到這條訊息,然後再做對應的操作,做完了之後再消費這條訊息即可。
如果是 RollBack 那麼訂閱方收不到這條訊息,等於事務就沒執行過。
可以看到通過 RocketMQ 還是比較容易實現的,RocketMQ 提供了事務訊息的功能,我們只需要定義好事務反查介面即可。
最大努力通知
其實我覺得本地訊息表也可以算最大努力,事務訊息也可以算最大努力。
就本地訊息表來說會有後臺任務定時去檢視未完成的訊息,然後去呼叫對應的服務,當一個訊息多次呼叫都失敗的時候可以記錄下然後引入人工,或者直接捨棄。這其實算是最大努力了。
事務訊息也是一樣,當半訊息被commit了之後確實就是普通訊息了,如果訂閱者一直不消費或者消費不了則會一直重試,到最後進入死信佇列。其實這也算最大努力。
所以最大努力通知其實只是表明了一種柔性事務的思想:我已經盡力我最大的努力想達成事務的最終一致了。
適用於對時間不敏感的業務,例如簡訊通知。
總結
可以看出 2PC 和 3PC 是一種強一致性事務,不過還是有資料不一致,阻塞等風險,而且只能用在資料庫層面。
而 TCC 是一種補償性事務思想,適用的範圍更廣,在業務層面實現,因此對業務的侵入性較大,每一個操作都需要實現對應的三個方法。
本地訊息、事務訊息和最大努力通知其實都是最終一致性事務,因此適用於一些對時間不敏感的業務。