分布式事务六种解决方案
常見的分布式事務包括 2PC、3PC、TCC、本地消息表、消息事務、最大努力通知。
事務
嚴格意義上的事務實現應該是具備原子性、一致性、隔離性和持久性,簡稱 ACID。
- 原子性(Atomicity),可以理解為一個事務內的所有操作要么都執行,要么都不執行。
- 一致性(Consistency),可以理解為數據是滿足完整性約束的,也就是不會存在中間狀態的數據,比如你賬上有400,我賬上有100,你給我打200塊,此時你賬上的錢應該是200,我賬上的錢應該是300,不會存在我賬上錢加了,你賬上錢沒扣的中間狀態。
- 隔離性(Isolation),指的是多個事務并發執行的時候不會互相干擾,即一個事務內部的數據對于其他事務來說是隔離的。
- 持久性(Durability),指的是一個事務完成了之后數據就被永遠保存下來,之后的其他操作或故障都不會對事務的結果產生影響。
而通俗意義上事務就是為了使得一些更新操作要么都成功,要么都失敗。
說到這里可能有人會說,不對啊 Redis 的事務不能保證所有操作要么都執行要么都不執行,為什么它也叫事務啊?
首先你要知曉一般的中間件都會夸大其效果,人家團隊也是想更出名,吸引更多的人來使用他們的產品,所以我們得以辯證的角度來看待。
一般而言他們既然敢說出他們實現了什么什么,要么是真的實現了,要么是在某種特殊、定制或者極端的條件下才能滿足功能。
我們來看看 Redis 怎么說的。
這句話就是告訴大家事務中的某個命令失敗了,之后的命令還是會被處理,Redis 不會停止命令,意味著也不會回滾。
你說這不是扯嘛?這都偏離事務最核心的本意了啊。
別急,咱們來看看 Redis 怎么解釋的。
?
Redis 官網解釋了為什么不支持回滾,他們說首先如果命令出錯那都是語法使用錯誤,是你們自己編程出錯,而且這種情況應該在開發的時候就被檢測出來,不應在生產環境中出現。
然后 Redis 就是為了快!不需要提供回滾。
下面還有一段話我就不截圖了,就是說就算提供回滾也沒用,你這代碼都寫錯了,回滾并不能使你免于編程錯誤。而且一般這種錯也不可能進入到生產環境,所以選擇更加簡單、快速的方法,我們不支持回滾。
你看看這說的好像很有道理,我們不提供回滾,因為我們不需要為你的編程錯誤買單!
但好像哪里不對勁?角度、立場不同,大家自己品。
分布式事務
分布式事務顧名思義就是要在分布式系統中實現事務,它其實是由多個本地事務組合而成。
對于分布式事務而言幾乎滿足不了 ACID,其實對于單機事務而言大部分情況下也沒有滿足 ACID,不然怎么會有四種隔離級別呢?所以更別說分布在不同數據庫或者不同應用上的分布式事務了。
我們先來看下 2PC。
2PC
2PC(Two-phase commit protocol),中文叫二階段提交。?二階段提交是一種強一致性設計,2PC 引入一個事務協調者的角色來協調管理各參與者(也可稱之為各本地資源)的提交和回滾,二階段分別指的是準備(投票)和提交兩個階段。
注意這只是協議或者說是理論指導,只闡述了大方向,具體落地還是有會有差異的。
讓我們來看下兩個階段的具體流程。
準備階段協調者會給各參與者發送準備命令,你可以把準備命令理解成除了提交事務之外啥事都做完了。
同步等待所有資源的響應之后就進入第二階段即提交階段(注意提交階段不一定是提交事務,也可能是回滾事務)。
假如在第一階段所有參與者都返回準備成功,那么協調者則向所有參與者發送提交事務命令,然后等待所有事務都提交成功之后,返回事務執行成功。
假如在第一階段有一個參與者返回失敗,那么協調者就會向所有參與者發送回滾事務的請求,即分布式事務執行失敗。
那可能就有人問了,那第二階段提交失敗的話呢?
這里有兩種情況。
第一種是第二階段執行的是回滾事務操作,那么答案是不斷重試,直到所有參與者都回滾了,不然那些在第一階段準備成功的參與者會一直阻塞著。
第二種是第二階段執行的是提交事務操作,那么答案也是不斷重試,因為有可能一些參與者的事務已經提交成功了,這個時候只有一條路,就是頭鐵往前沖,不斷的重試,直到提交成功,到最后真的不行只能人工介入處理。
3PC
3PC 的出現是為了解決 2PC 的一些問題,相比于 2PC 它在參與者中也引入了超時機制,并且新增了一個階段使得參與者可以利用這一個階段統一各自的狀態。
讓我們來詳細看一下。
3PC 包含了三個階段,分別是準備階段、預提交階段和提交階段,對應的英文就是:CanCommit、PreCommit 和 DoCommit。
看起來是把 2PC 的提交階段變成了預提交階段和提交階段,但是 3PC 的準備階段協調者只是詢問參與者的自身狀況,比如你現在還好嗎?負載重不重?這類的。
而預提交階段就是和 2PC 的準備階段一樣,除了事務的提交該做的都做了。
不管哪一個階段有參與者返回失敗都會宣布事務失敗,這和 2PC 是一樣的(當然到最后的提交階段和 2PC 一樣只要是提交請求就只能不斷重試)。
TCC
2PC 和 3PC 都是數據庫層面的,而 TCC 是業務層面的分布式事務,就像我前面說的分布式事務不僅僅包括數據庫的操作,還包括發送短信等,這時候 TCC 就派上用場了!
TCC 指的是Try - Confirm - Cancel。
- Try 指的是預留,即資源的預留和鎖定,注意是預留。
- Confirm 指的是確認操作,這一步其實就是真正的執行了。
- Cancel 指的是撤銷操作,可以理解為把預留階段的動作撤銷了。
其實從思想上看和 2PC 差不多,都是先試探性的執行,如果都可以那就真正的執行,如果不行就回滾。
比如說一個事務要執行A、B、C三個操作,那么先對三個操作執行預留動作。如果都預留成功了那么就執行確認操作,如果有一個預留失敗那就都執行撤銷動作。
本地消息表
本地消息表其實就是利用了?各系統本地的事務來實現分布式事務。
本地消息表顧名思義就是會有一張存放本地消息的表,一般都是放在數據庫中,然后在執行業務的時候?將業務的執行和將消息放入消息表中的操作放在同一個事務中,這樣就能保證消息放入本地表中業務肯定是執行成功的。
然后再去調用下一個操作,如果下一個操作調用成功了好說,消息表的消息狀態可以直接改成已成功。
如果調用失敗也沒事,會有?后臺任務定時去讀取本地消息表,篩選出還未成功的消息再調用對應的服務,服務更新成功了再變更消息的狀態。
這時候有可能消息對應的操作不成功,因此也需要重試,重試就得保證對應服務的方法是冪等的,而且一般重試會有最大次數,超過最大次數可以記錄下報警讓人工處理。
可以看到本地消息表其實實現的是最終一致性,容忍了數據暫時不一致的情況。
消息事務
RocketMQ 就很好的支持了消息事務,讓我們來看一下如何通過消息實現事務。
第一步先給 Broker 發送事務消息即半消息,半消息不是說一半消息,而是這個消息對消費者來說不可見,然后發送成功后發送方再執行本地事務。
再根據本地事務的結果向 Broker 發送 Commit 或者 RollBack 命令。
并且 RocketMQ 的發送方會提供一個反查事務狀態接口,如果一段時間內半消息沒有收到任何操作請求,那么 Broker 會通過反查接口得知發送方事務是否執行成功,然后執行 Commit 或者 RollBack 命令。
如果是 Commit 那么訂閱方就能收到這條消息,然后再做對應的操作,做完了之后再消費這條消息即可。
如果是 RollBack 那么訂閱方收不到這條消息,等于事務就沒執行過。
可以看到通過 RocketMQ 還是比較容易實現的,RocketMQ 提供了事務消息的功能,我們只需要定義好事務反查接口即可。
可以看到消息事務實現的也是最終一致性。
最大努力通知
其實我覺得本地消息表也可以算最大努力,事務消息也可以算最大努力。
就本地消息表來說會有后臺任務定時去查看未完成的消息,然后去調用對應的服務,當一個消息多次調用都失敗的時候可以記錄下然后引入人工,或者直接舍棄。這其實算是最大努力了。
事務消息也是一樣,當半消息被commit了之后確實就是普通消息了,如果訂閱者一直不消費或者消費不了則會一直重試,到最后進入死信隊列。其實這也算最大努力。
所以最大努力通知其實只是表明了一種柔性事務的思想:我已經盡力我最大的努力想達成事務的最終一致了。
適用于對時間不敏感的業務,例如短信通知。
總結
可以看出 2PC 和 3PC 是一種強一致性事務,不過還是有數據不一致,阻塞等風險,而且只能用在數據庫層面。
而 TCC 是一種補償性事務思想,適用的范圍更廣,在業務層面實現,因此對業務的侵入性較大,每一個操作都需要實現對應的三個方法。
本地消息、事務消息和最大努力通知其實都是最終一致性事務,因此適用于一些對時間不敏感的業務。
?
總結
以上是生活随笔為你收集整理的分布式事务六种解决方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 索引失效的7种情况
- 下一篇: 谈谈Spring中都用到了那些设计模式