如何選擇分布式事務形態(TCC,SAGA,2PC,基於消息最終一致性等等)


各種形態的分布式事務

分布式事務有多種主流形態,包括:

  • 基於消息實現的分布式事務
  • 基於補償實現的分布式事務
  • 基於TCC實現的分布式事務
  • 基於SAGA實現的分布式事務
  • 基於2PC實現的分布式事務

這些形態的原理已經在很多文章中進行了剖析,用“分布式事務”關鍵字就能搜到對應的文章,本文不再贅述這些形態的原理,並將重點放在如何根據業務選擇對應的分布式事務形態上。

何時選擇單機事務?

這個相信大家都很清楚,在條件允許的情況下,我們應該盡可能地使用單機事務,因為單機事務里,無需額外協調其他數據源,減少了網絡交互時間消耗以及協調時所需的存儲IO消耗,在修改等量業務數據的情況下,單機事務將會有更高的性能。

但單機數據庫由於 業務邏輯解耦等因素進行了數據庫垂直拆分、或者由於單機數據庫性能壓力等因素進行了數據庫水平拆分之后,數據分布於多個數據庫,這時若需要對多個數據庫的數據進行協調變更,則需要引入分布式事務。

分布式事務的模式有很多種,那究竟要怎么選擇適合業務的模式呢?以下我們將從使用場景、性能、開發成本這幾個方面進行分析。

何時選擇基於消息實現的事務?

基於消息實現的事務適用於分布式事務的提交或回滾只取決於事務發起方的業務需求,其他數據源的數據變更跟隨發起方進行的業務場景。

舉個例子,假設存在業務規則:某筆訂單成功后,為用戶加一定的積分。

在這條規則里,管理訂單數據源的服務為事務發起方,管理積分數據源的服務為事務跟隨者。

從這個過程可以看到,基於消息隊列實現的事務存在以下操作:

  • 訂單服務創建訂單,提交本地事務
  • 訂單服務發布一條消息
  • 積分服務收到消息后加積分

我們可以看到它的整體流程是比較簡單的,同時業務開發工作量也不大:

  • 編寫訂單服務里訂單創建的邏輯
  • 編寫積分服務里增加積分的邏輯

可以看到該事務形態過程簡單,性能消耗小,發起方與跟隨方之間的流量峰谷可以使用隊列填平,同時業務開發工作量也基本與單機事務沒有差別,都不需要編寫反向的業務邏輯過程。因此基於消息隊列實現的事務是我們除了單機事務外最優先考慮使用的形態。

何時選擇利用補償實現的事務?

但是基於消息實現的事務並不能解決所有的業務場景,例如以下場景:某筆訂單完成時,同時扣掉用戶的現金。

這里事務發起方是管理訂單庫的服務,但對整個事務是否提交並不能只由訂單服務決定,因為還要確保用戶有足夠的錢,才能完成這筆交易,而這個信息在管理現金的服務里。這里我們可以引入基於補償實現的事務,其流程如下:

  • 創建訂單數據,但暫不提交本地事務
  • 訂單服務發送遠程調用到現金服務,以扣除對應的金額
  • 上述步驟成功后提交訂單庫的事務

以上這個是正常成功的流程,異常流程需要回滾的話,將額外發送遠程調用到現金服務以加上之前扣掉的金額。

以上流程比基於消息隊列實現的事務的流程要復雜,同時開發的工作量也更多:

  • 編寫訂單服務里創建訂單的邏輯
  • 編寫現金服務里扣錢的邏輯
  • 編寫現金服務里補償返還的邏輯

可以看到,該事務流程相對於基於消息實現的分布式事務更為復雜,需要額外開發相關的業務回滾方法,也失去了服務間流量削峰填谷的功能。但其僅僅只比基於消息的事務復雜多一點,若不能使用基於消息隊列的最終一致性事務,那么可以優先考慮使用基於補償的事務形態。

(題外話:阿里GTS也是利用補償實現,只不過補償代碼自動生成,無需業務干預,同時接管應用數據源,禁止業務修改處於全局事務狀態中的記錄。)

何時選擇利用TCC實現的事務

然而基於補償的事務形態也並非能實現所有的需求,如以下場景:某筆訂單完成時,同時扣掉用戶的現金,但交易未完成,也未被取消時,不能讓客戶看到錢變少了。

這時我們可以引入TCC,其流程如下:

  • 訂單服務創建訂單
  • 訂單服務發送遠程調用到現金服務,凍結客戶的現金
  • 提交訂單服務數據
  • 訂單服務發送遠程調用到現金服務,扣除客戶凍結的現金

以上是正常完成的流程,若為異常流程,則需要發送遠程調用請求到現金服務,撤銷凍結的金額。

以上流程比基於補償實現的事務的流程要復雜,同時開發的工作量也更多:

  • 訂單服務編寫創建訂單的邏輯
  • 現金服務編寫凍結現金的邏輯
  • 現金服務編寫扣除現金的邏輯
  • 現金服務編寫解凍現金的邏輯

TCC實際上是最為復雜的一種情況,其能處理所有的業務場景,但無論出於性能上的考慮,還是開發復雜度上的考慮,都應該盡量避免該類事務。

何時選擇利用SAGA實現的事務?

SAGA可以看做一個異步的、利用隊列實現的補償事務。

其適用於無需馬上返回業務發起方最終狀態的場景,例如:你的請求已提交,請稍后查詢或留意通知 之類。

將上述補償事務的場景用SAGA改寫,其流程如下:

  • 訂單服務創建最終狀態未知的訂單記錄,並提交事務
  • 現金服務扣除所需的金額,並提交事務
  • 訂單服務更新訂單狀態為成功,並提交事務

以上為成功的流程,若現金服務扣除金額失敗,那么,最后一步訂單服務將會更新訂單狀態為失敗。

其業務編碼工作量比補償事務多一點,包括以下內容:

  • 訂單服務創建初始訂單的邏輯
  • 訂單服務確認訂單成功的邏輯
  • 訂單服務確認訂單失敗的邏輯
  • 現金服務扣除現金的邏輯
  • 現金服務補償返回現金的邏輯

但其相對於補償事務形態有性能上的優勢,所有的本地子事務執行過程中,都無需等待其調用的子事務執行,減少了加鎖的時間,這在事務流程較多較長的業務中性能優勢更為明顯。同時,其利用隊列進行進行通訊,具有削峰填谷的作用。

因此該形式適用於不需要同步返回發起方執行最終結果、可以進行補償、對性能要求較高、不介意額外編碼的業務場景。

但當然SAGA也可以進行稍微改造,變成與TCC類似、可以進行資源預留的形態。

2PC事務

其適用於參與者較少,單個本地事務執行時間較少,並且參與者自身可用性很高的場景,否則,其很可能導致性能下降嚴重。

並非一種事務形態就能打遍天下

通過分析我們可以發現,並不存在一種事務形態能解決所有的問題,我們需要根據特定的業務場景選擇合適的事務形態。甚至於有時需要混合多種事務形態才能更好的完成目標,如 上面提到的 訂單、積分、錢包混合的場景:訂單的成功與否需要依賴於錢包的余額,但不依賴於積分的多少,因此可以混合基於消息的事務形態以加積分 及 基於補償的事務形態以確保扣錢成功,從而得到一個性能更好,編碼量更少的形態。

然而目前很多框架都專注於某單一方面的事務形態,如TCC單獨一個框架,可靠消息單獨一個框架,SAGA單獨一個框架,他們各自獨立,容易導致以下問題:

  • 由於前期只采用了其中一種類型事務的框架,因為工具目前只有錘子,引入其他工具又涉及測試、閱讀代碼等過程,因此把所有問題都看做釘子,導致性能偏低或者實現不夠優雅
  • 由於不同框架管理事務的形態可能不一致,導致不能很好的協調工作,如某一個TCC框架和另一個基於消息的事務框架無法很好融合。

解決方案

為了解決上面提到的問題,EasyTransaction這個基於Spring的分布式事務框架,實現了上述除2PC以外的所有事務形態,並提供了統一的使用接口,完美地解決了以上的問題。其主要特性如下:

  • 一個框架包含多種事務形態,一個框架搞定所有類型的事務
  • 多種事務形態可混合使用
  • 高性能,若不啟用框架的冪等功能,對業務數據庫的額外消耗僅為寫入25字節的一行
  • 可選的框架冪等實現(包括調用次序錯亂處理),大幅減輕業務開發工作量
  • 業務代碼可實現完全無入侵
  • 支持嵌套事務
  • 無需額外部署協調者,不同APP的服務協調自身發起的事務
  • 分布式事務ID可關聯業務ID,業務類型,APPID,便於監控各個業務的分布式事務執行情況

若各位對ET興趣,可以到 https://github.com/QNJR-GROUP/EasyTransaction 查看詳細介紹及示例,本文不再深入介紹

總結

不同業務場景應按需引入不同的事務形態,在條件允許的情況下,個人建議按照如下次序選擇對應的事務形態:

單機事務》基於消息的事務》基於補償的事務》TCC事務

因SAGA事務的形態需要配合較為明顯的前端業務交互變更,個人建議在單一事務執行過程較長、存在較多子事務,並且無法使用基於消息的事務形態時使用。


免責聲明!

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



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