分布式事務常見解決方案



首先大家想過沒:既然有了事務,並且使用 spring 的@Transactional 注解來控制事務是如此的方便,那為啥還要搞一個分布式事務的概念出來啊? 更進一步,分布式事務和普通事務到底是啥關系?有什么區別?分布式事務又是為了解決什么問題出現的?

各種疑問接踵而至,別着急,帶着這些思考,咱們接下來就詳細聊聊分布式事務。

既然叫分布式事務,那么必然和分布式有點關系啦!簡單來說,分布式事務指的就是分布式系統中的事務。

首先來看看下面的圖:

如上圖所示,一個單塊系統有 3 個模塊:員工模塊、財務模塊和請假模塊。我們現在有一個操作需要按順序去調用完成這 3 個模塊中的接口。

這個操作是一個整體,包含在一個事務中,要么同時成功要么同時失敗回滾。不成功便成仁,這個都沒有問題。

但是當我們把單塊系統拆分成分布式系統或者微服務架構,事務就不是上面那么玩兒了。

首先我們來看看拆分成分布式系統之后的架構圖,如下所示:

上圖是同一個操作在分布式系統中的執行情況。員工模塊、財務模塊和請假模塊分別給拆分成員工系統、財務系統和請假系統。

比如一個用戶進行一個操作,這個操作需要先調用員工系統預先處理一下,然后通過 http 或者 rpc 的方式分別調用財務系統和請假系統的接口做進一步的處理,它們的操作都需要分別落地到數據庫中。

這 3 個系統的一系列操作其實是需要全部被包裹在同一個分布式事務中的,此時這 3 個系統的操作,要么同時成功要么同時失敗。

分布式系統中完成一個操作通常需要多個系統間協同調用和通信,比如上面的例子。

三個子系統:員工系統、財務系統、請假系統之間就通過 http 或者 rpc 進行通信,而不再是一個單塊系統中不同模塊之間的調用,這就是分布式系統和單塊系統最大的區別。

一些平時不太關注分布式架構的同學,看到這里可能會說:我直接用 spring 的@Transactional 注解就 OK 了啊,管那么多干嘛!

但是這里極其重要的一點:單塊系統是運行在同一個 JVM 進程中的,但是分布式系統中的各個系統運行在各自的 JVM 進程中。

因此你直接加@Transactional 注解是不行的,因為它只能控制同一個 JVM 進程中的事務,但是對於這種跨多個 JVM 進程的事務無能無力。

分布式事務的幾種實現思路

搞清楚了啥是分布式事務,那么分布式事務到底是怎么玩兒的呢? 下邊就來給大家介紹幾種分布式事務的實現方案。

可靠消息最終一致性方案

整個流程圖如下所示:

我們來解釋一下這個方案的大概流程:

  1. A 系統先發送一個 prepared 消息到 mq,如果這個 prepared 消息發送失敗那么就直接取消操作別執行了,后續操作都不再執行。
  2. 如果這個消息發送成功過了,那么接着執行 A 系統的本地事務,如果執行失敗就告訴 mq 回滾消息,后續操作都不再執行。
  3. 如果 A 系統本地事務執行成功,就告訴 mq 發送確認消息。
  4. 那如果 A 系統遲遲不發送確認消息呢? 此時 mq 會自動定時輪詢所有 prepared 消息,然后調用 A 系統事先提供的接口,通過這個接口反查 A 系統的上次本地事務是否執行成功 如果成功,就發送確認消息給 mq;失敗則告訴 mq 回滾消息(后續操作都不再執行)。
  5. 此時 B 系統會接收到確認消息,然后執行本地的事務,如果本地事務執行成功則事務正常完成。
  6. 如果系統 B 的本地事務執行失敗了咋辦? 基於 mq 重試咯,mq 會自動不斷重試直到成功,如果實在是不行,可以發送報警由人工來手工回滾和補償。 這種方案的要點就是可以基於 mq 來進行不斷重試,最終一定會執行成功的。 因為一般執行失敗的原因是網絡抖動或者數據庫瞬間負載太高,都是暫時性問題。 通過這種方案,99.9%的情況都是可以保證數據最終一致性的,剩下的 0.1%出問題的時候,就人工修復數據唄。

適用場景: 這個方案的使用還是比較廣,目前國內互聯網公司大都是基於這種思路玩兒的。

最大努力通知方案

整個流程圖如下所示:

 

 

這個方案的大致流程:

  1. 系統 A 本地事務執行完之后,發送個消息到 MQ。
  2. 這里會有個專門消費 MQ 的最大努力通知服務,這個服務會消費 MQ,然后寫入數據庫中記錄下來,或者是放入個內存隊列。接着調用系統 B 的接口。
  3. 假如系統 B 執行成功就萬事 ok 了,但是如果系統 B 執行失敗了呢? 那么此時最大努力通知服務就定時嘗試重新調用系統 B,反復 N 次,最后還是不行就放棄。

這套方案和上面的可靠消息最終一致性方案的區別:

可靠消息最終一致性方案可以保證的是只要系統 A 的事務完成,通過不停(無限次)重試來保證系統 B 的事務總會完成。

但是最大努力方案就不同,如果系統 B 本地事務執行失敗了,那么它會重試 N 次后就不再重試,系統 B 的本地事務可能就不會完成了。

至於你想控制它究竟有“多努力”,這個需要結合自己的業務來配置。

比如對於電商系統,在下完訂單后發短信通知用戶下單成功的業務場景中,下單正常完成,但是到了發短信的這個環節由於短信服務暫時有點問題,導致重試了 3 次還是失敗。

那么此時就不再嘗試發送短信,因為在這個場景中我們認為 3 次就已經算是盡了“最大努力”了。

簡單總結:就是在指定的重試次數內,如果能執行成功那么皆大歡喜,如果超過了最大重試次數就放棄,不再進行重試。

適用場景: 一般用在不太重要的業務操作中,就是那種完成的話是錦上添花,但失敗的話對我也沒有什么壞影響的場景。

比如上邊提到的電商中的部分通知短信,就比較適合使用這種最大努力通知方案來做分布式事務的保證。

tcc 強一致性方案

TCC 的全稱是:

  • Try(嘗試)
  • Confirm(確認/提交)
  • Cancel(回滾)。

這個其實是用到了補償的概念,分為了三個階段:

  1. Try 階段:這個階段說的是對各個服務的資源做檢測以及對資源進行鎖定或者預留;
  2. Confirm 階段:這個階段說的是在各個服務中執行實際的操作;
  3. Cancel 階段:如果任何一個服務的業務方法執行出錯,那么這里就需要進行補償,就是執行已經執行成功的業務邏輯的回滾操作。

還是給大家舉個例子:

比如跨銀行轉賬的時候,要涉及到兩個銀行的分布式事務,如果用 TCC 方案來實現,思路是這樣的:

  1. Try 階段:先把兩個銀行賬戶中的資金給它凍結住就不讓操作了;
  2. Confirm 階段:執行實際的轉賬操作,A 銀行賬戶的資金扣減,B 銀行賬戶的資金增加;
  3. Cancel 階段:如果任何一個銀行的操作執行失敗,那么就需要回滾進行補償,就是比如 A 銀行賬戶如果已經扣減了,但是 B 銀行賬戶資金增加失敗了,那么就得把 A 銀行賬戶資金給加回去。

適用場景:這種方案說實話幾乎很少有人使用,我們用的也比較少,但是也有使用的場景。

因為這個事務回滾實際上是嚴重依賴於你自己寫代碼來回滾和補償了,會造成補償代碼巨大,非常之惡心。

比如說我們,一般來說跟錢相關的,跟錢打交道的,支付、交易相關的場景,我們會用 TCC,嚴格保證分布式事務要么全部成功,要么全部自動回滾,嚴格保證資金的正確性,在資金上不允許出現問題。

比較適合的場景:除非你是真的一致性要求太高,是你系統中核心之核心的場景,比如常見的就是資金類的場景,那你可以用 TCC 方案了。 你需要自己編寫大量的業務邏輯,自己判斷一個事務中的各個環節是否 ok,不 ok 就執行補償/回滾代碼。

而且最好是你的各個業務執行的時間都比較短。

但是說實話,一般盡量別這么搞,自己手寫回滾邏輯,或者是補償邏輯,實在太惡心了,那個業務代碼很難維護。

總結

本篇介紹了什么是分布式事務,然后還介紹了最常用的 3 種分布式事務方案。但除了上邊的方案外,其實還有兩階段提交方案(XA 方案)和本地消息表等方案。但是說實話極少有公司使用這些方案,鑒於篇幅所限,不做介紹。

鏈接:https://zhuanlan.zhihu.com/p/85790242


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM