你必須了解的分布式事務解決方案


前言

上一篇文章《就這?分布式 ID 發號器實戰》之后,我朋友輝哥在后台留言讓靚仔聊聊分布式事務,既然輝哥都開口了,那必須得滿足啊,安排!

溫馨提示:文章很干,請多喝水

img

什么是分布式事務

什么是事務想必大多數朋友應該都很清楚了,不清楚的可以看前面的文章《就這?一篇文章讓你讀懂 Spring 事務》。

分布式事務就是指事務的參與者、支持事務的服務器、資源服務器以及事務管理器分別位於不同的分布式系統的不同節點之上。

簡單來說,就是一個大的操作由 N 個小操作組成,這些小的操作分布在不同的服務器上,且屬於不同的應用,分布式事務需要保證這些小操作要么全部成功,要么全部失敗。比如存在一個訂單的微服務,一個庫存的微服務,當訂單完成需要同步減少庫存,這時候就要在事務上確保完整和一致。

相關理論

關於事務的特性(ACID)隔離級別這里就不再重復介紹了,可以看前面的文章,這里着重介紹下兩個新的知識:CAP 理論BASE 理論。

CAP 理論

  • 一致性Consistency)在分布式系統完成某寫操作后任何讀操作,都應該獲取到該寫操作寫入的那個最新的值。相當於要求分布式系統中的各節點時時刻刻保持數據的一致性。
  • 可用性Availability) 一直可以正常的做讀寫操作。簡單而言就是客戶端一直可以正常訪問並得到系統的正常響應。用戶角度來看就是不會出現系統操作失敗或者訪問超時等問題。
  • 分區容錯性PartitionTolerance)指的分布式系統中的某個節點或者網絡分區出現了故障的時候,整個系統仍然能對外提供滿足一致性和可用性的服務。也就是說部分故障不影響整體使用。事實上我們在設計分布式系統是都會考慮到 bug、硬件、網絡等各種原因造成的故障,所以即使部分節點或者網絡出現故障,我們要求整個系統還是要繼續使用的

CAP 是一個已經被證實的理論,在分布式系統中最多只能同時滿足這三項中的兩項,而分區容錯性是分布式系統必須滿足的,所以在分布式系統中常見的組合就是 CP 和 AP

  • CP:放棄可用性,注重一致性和分區容錯性,其實這就是所謂的強一致性,可能在銀行跨行轉賬這種強一致業務場景才會用到,具體得根據業務場景做取舍。
  • AP:放棄強一致性,注重可用性和分區容錯性,這是現在絕大多數分布式業務場景的選擇,只要最后能保證最終一致性( BASE 理論)即可。

BASE 理論

  • 基本可用Basically Available)基本可用是指分布式系統在出現故障的時候,允許損失部分可用性,即保證核心可用。電商大促時,為了應對訪問量激增,部分用戶可能會被引導到降級頁面,服務層也可能只提供降級服務。這就是損失部分可用性的體現。

  • 軟狀態Soft State)軟狀態是指允許系統存在中間狀態,而該中間狀態不會影響系統整體可用性。分布式存儲中一般一份數據至少會有三個副本,允許不同節點間副本同步的延時就是軟狀態的體現。MySQL Replication 的異步復制也是一種體現。

    最終一致性Eventual Consistency)最終一致性是指系統中的所有數據副本經過一定時間后,最終能夠達到一致的狀態。弱一致性和強一致性相反,最終一致性是弱一致性的一種特殊情況。

常見解決方案

1、兩階段提交

兩階段提交(Two-phaseCommit),簡稱為 2PC,兩階段提交是一種強一致性設計,它引入一個事務協調者的角色來協調管理各個參與者(也可稱之為各本地資源)的提交和回滾。

所謂的兩個階段是指:第一階段:准備階段(投票階段)和第二階段:提交階段(執行階段)。

  • 准備階段(Prepare Phase):首先協調器會向所有的參與者發送准備提交或者取消提交的請求,然后會收集參與者的決策。

  • 提交階段(Commit Phase):協調者會收集所有參與者的決策信息,當且僅當所有的參與者向協調器發送確認消息時協調器才會提交請求,否則執行回滾或者取消請求。

img

2PC 存在的問題:

  • 同步阻塞:所有的參與者都是事務同步阻塞型的。當參與者占有公共資源時,其他第三方節點訪問公共資源不得不處於阻塞狀態。
  • 單點故障:一旦協調者發生故障,系統不可用。
  • 數據不一致:當協調者發送 commit 之后,有的參與者收到 commit 消息,事務執行成功,有的沒有收到,處於阻塞狀態,這段時間會產生數據不一致。
  • 不確定性:當協調者發送 commit 之后,並且此時只有一個參與者收到了 commit,那么當該參與者與協調器同時宕機之后,重新選舉的協調器無法確定該條消息是否提交成功。

2PC 的優勢在於對業務沒有侵入,可以利用數據庫自身機制來進行事務的提交和回滾。

常見的基於 2PC 的具體落地方案有:JTA(XA 規范) 和 Seata( AT 模式)。

2、三階段提交

三階段提交(Three-phase commit),簡稱為 3PC,是 2PC 的改進版本。同時在協調者和參與者都引入了超時機制,還在 2PC 中的准備階段和提交階段中間增加了一個預提交階段。

  • 准備階段(CanCommit):協調者向各個參與者發送請求,詢問是否可以執行事務,但並不執行事務。
  • 預提交階段(PreCommit):如果從協調者得到的反饋是滿足執行條件,那么就發送預提交請求,並開始執行事務;如果從協調者得到的反饋是不滿足執行條件或者超時,則發送事務中斷請求。
  • 提交階段(DoCommit):如果預提交階段發送的是預提交請求,那么正常提交事務;如果預提交階段發送的是事務中斷請求,那么直接中斷事務。

img

相對於 2PC,3PC 主要解決的單點故障問題,並減少阻塞,因為一旦參與者無法及時收到來自協調者的信息之后,他會默認執行 commit。而不會一直持有事務資源並處於阻塞狀態。但是這種機制也會導致數據一致性問題,因為,由於網絡原因,協調者發送的中斷響應沒有及時被參與者接收到,那么參與者在等待超時之后執行了 commit 操作。這樣就和其他接到中斷命令並執行回滾的參與者之間存在數據不一致的情況。而且 3PC 整體的交互過程更長,性能也會有所下降。

3PC 目前似乎只存在於理論,還沒有具體落地方案。

3、TCC

2PC 和 3PC 都是依賴於數據庫的事務提交和回滾,但是有時候很多業務並不僅僅只涉及到數據庫,可能還會發送短息、消息等等,而 TCC 就是屬於業務層面或者說是應用層面的分布式事務。

TCC 方案分為Try-Confirm-Cancel三個階段,屬於補償性分布式事務。

  • Try 階段:完成所有業務檢查(一致性),預留業務資源(准隔離性)
  • Confirm 階段:確認執行業務操作,不再做任何業務檢查, 只使用Try階段預留的業務資源。
  • Cancel 階段:取消Try階段預留的業務資源。

img

有的朋友可能會問了,Try 成功了會執行 Confirm,失敗了會執行 Cancel,那 Confirm 階段失敗了怎么辦?這時候只能設置重試機制,不斷重試調失敗的 Confirm,直到成功為止,真有怎么也不成功的,就只能人工介入了。

TCC 需要根據每個場景和業務邏輯來設計相應的操作,所以很大程度增加了業務代碼的復雜度,對業務有很大的侵入。

雖說對業務有侵入,但是 TCC 沒有資源的阻塞,每一個方法都是直接提交事務的,如果出錯是通過業務層面的 Cancel 來進行補償,所以也稱補償性事務方法。

TCC 要注意的幾個問題:

  • 冪等問題:因為網絡調用無法保證請求一定能到達,所以都會有重調機制,因此對於 Try、Confirm、Cancel 三個方法都需要冪等實現,避免重復執行產生錯誤。

  • 空回滾問題:指的是 Try 方法由於網絡問題沒收到超時了,此時事務管理器就會發出 Cancel 命令,那么需要支持 Cancel 在未執行 Try 的情況下能正常的 Cancel。

  • 懸掛問題:這個問題也是指 Try 方法由於網絡阻塞超時觸發了事務管理器發出了 Cancel 命令,但是執行了 Cancel 命令之后 Try 請求到了。所以空回滾之后還得記錄一下,防止 Try 的再調用。

4、本地消息表

本地消息表分布式事務解決方案是國外的 eBay 提出的一套方案。其實就是利用了各系統本地的事務來實現分布式事務,在數據庫中存放一張事務消息表,在執行業務操作的時候, 將業務的執行和將消息放入消息表中的操作放在同一個事務中。

本地事務執行成功之后再調用其他服務,如果成功了就將消息表里的消息狀態改為成功,如果失敗了,則由定時任務去讀取本地事務表中未成功的消息,再去調用相應的服務,成功后再次修改狀態。

img

這里也要設置重試機制,一旦有實在不成功的,還需人工介入。這里要注意的是,也要保證對應服務的方法冪等性。

可以看出,本地消息表實現比較簡單,是一種最大努力通知思想,實現的是最終一致性,容忍了數據暫時不一致的情況。

缺點是嚴重依賴數據庫。

5、可靠消息最終一致性方案

在上面的本地消息表方案中,生產者需要額外創建消息表,還需要對本地消息表進行輪詢,業務負擔較重。阿里開源的 RocketMQ 4.3 之后的版本正式支持事務消息,該事務消息本質上是把本地消息表放到 RocketMQ 上,解決生產端的消息發送與本地事務執行的原子性問題。

服務 A,先給 Broker (消息中間件) 發送一個 Half Message(半消息),其實這個半消息已發送到 Broker 端,但是此消息的狀態被標記為"不能投遞",消費者還看不到,處於這種狀態下的消息稱為半消息。

發送完 半消息后,服務A 執行業務操作(本地事務),再根據操作結果:如果成功,則向 Broker 發送 一個 Commit 命令,這時半消息就變成了可以被消費者消息;如果失敗,則發送一個 RollBack 命令,該消息則會被刪除。

如果是 Commit 那么服務 B 就能收到這條消息,然后再做對應的操作,做完了之后再消費這條消息即可。

如果 RocketMQ 沒有收到服務 A 確認狀態的消息,那么半消息 RocketMQ 會自動定時輪詢回調你的接口,詢問這個處理的處理情況。借助這點,服務A實現一個回調,根據實際處理結果 Commit 或者 Rollback,加強一致性判斷。

img

在服務 B 執行的過程中也可能會失敗,這時也是需要重試,一直執行不成功也需要人工介入,同時也需要保證服務 B 方法的冪等性。

6、最大努力通知

最大努力通知型( Best-effort delivery)是最簡單的一種柔性事務,適用於一些最終一致性時間敏感度低的業務,且被動方處理結果不影響主動方的處理結果。典型的使用場景:如銀行通知、商戶通知等。

就本地消息表來說會有后台任務定時去查看未完成的消息,然后去調用對應的服務,當一個消息多次調用都失敗的時候可以記錄下然后引入人工,或者直接舍棄。這其實算是最大努力了

事務消息也是一樣,當半消息被commit了之后確實就是普通消息了,如果訂閱者一直不消費或者消費不了則會一直重試,到最后進入死信隊列。其實這也算最大努力。

最大努力通知,發起通知方盡最大的努力將業務處理結果通知為接收通知方,但是可能消息接收不到,此時需要接收通知方主動調用發起通知方的接口查詢業務處理結果,通知的可靠性關鍵在接收通知方。

總結

其實分布式事務解決方案還有很多,但是各自還是會存在很多問題,極端情況下也都需要人工去處理,而且大大提高了流程的復雜度,會帶來很多額外的開銷。

所以謹記,在真實的開發過程中,能不使用分布式事務就不要使用!

后面會給大家帶來分布式事務的實戰,沒點關注的可以點個關注,防止走丟了。

END

往期推薦

就這?分布式 ID 發號器實戰

略懂設計模式之工廠模式

就這?Spring 事務失效場景及解決方案

就這?一篇文章讓你讀懂 Spring 事務

SpringBoot+Redis 實現消息訂閱發布


免責聲明!

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



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