各類形態的分佈式事務
分佈式事務有多種主流形態,包括:html
- 基於消息實現的分佈式事務
- 基於補償實現的分佈式事務
- 基於TCC實現的分佈式事務
- 基於SAGA實現的分佈式事務
- 基於2PC實現的分佈式事務
這些形態的原理已經在不少文章中進行了剖析,用「分佈式事務」關鍵字就能搜到對應的文章,本文再也不贅述這些形態的原理,並將重點放在如何根據業務選擇對應的分佈式事務形態上。前端
什麼時候選擇單機事務?
這個相信你們都很清楚,在條件容許的狀況下,咱們應該儘量地使用單機事務,由於單機事務裏,無需額外協調其餘數據源,減小了網絡交互時間消耗以及協調時所需的存儲IO消耗,在修改等量業務數據的狀況下,單機事務將會有更高的性能。git
但單機數據庫因爲 業務邏輯解耦等因素進行了數據庫垂直拆分、或者因爲單機數據庫性能壓力等因素進行了數據庫水平拆分以後,數據分佈於多個數據庫,這時若須要對多個數據庫的數據進行協調變動,則須要引入分佈式事務。github
分佈式事務的模式有不少種,那究竟要怎麼選擇適合業務的模式呢?如下咱們將從使用場景、性能、開發成本這幾個方面進行分析。數據庫
什麼時候選擇基於消息實現的事務?
基於消息實現的事務適用於分佈式事務的提交或回滾只取決於事務發起方的業務需求,其餘數據源的數據變動跟隨發起方進行的業務場景。markdown
舉個例子,假設存在業務規則:某筆訂單成功後,爲用戶加必定的積分。網絡
在這條規則裏,管理訂單數據源的服務爲事務發起方,管理積分數據源的服務爲事務跟隨者。框架
從這個過程能夠看到,基於消息隊列實現的事務存在如下操做:異步
- 訂單服務建立訂單,提交本地事務
- 訂單服務發佈一條消息
- 積分服務收到消息後加積分
咱們能夠看到它的總體流程是比較簡單的,同時業務開發工做量也不大:分佈式
- 編寫訂單服務裏訂單建立的邏輯
- 編寫積分服務裏增長積分的邏輯
能夠看到該事務形態過程簡單,性能消耗小,發起方與跟隨方之間的流量峯谷可使用隊列填平,同時業務開發工做量也基本與單機事務沒有差異,都不須要編寫反向的業務邏輯過程。所以基於消息隊列實現的事務是咱們除了單機事務外最優先考慮使用的形態。
什麼時候選擇利用補償實現的事務?
可是基於消息實現的事務並不能解決全部的業務場景,例如如下場景:某筆訂單完成時,同時扣掉用戶的現金。
這裏事務發起方是管理訂單庫的服務,但對整個事務是否提交併不能只由訂單服務決定,由於還要確保用戶有足夠的錢,才能完成這筆交易,而這個信息在管理現金的服務裏。這裏咱們能夠引入基於補償實現的事務,其流程以下:
- 建立訂單數據,但暫不提交本地事務
- 訂單服務發送遠程調用到現金服務,以扣除對應的金額
- 上述步驟成功後提交訂單庫的事務
以上這個是正常成功的流程,異常流程須要回滾的話,將額外發送遠程調用到現金服務以加上以前扣掉的金額。
以上流程比基於消息隊列實現的事務的流程要複雜,同時開發的工做量也更多:
- 編寫訂單服務裏建立訂單的邏輯
- 編寫現金服務里扣錢的邏輯
- 編寫現金服務裏補償返還的邏輯
能夠看到,該事務流程相對於基於消息實現的分佈式事務更爲複雜,須要額外開發相關的業務回滾方法,也失去了服務間流量削峯填谷的功能。但其僅僅只比基於消息的事務複雜多一點,若不能使用基於消息隊列的最終一致性事務,那麼能夠優先考慮使用基於補償的事務形態。
(題外話:阿里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事務的形態須要配合較爲明顯的前端業務交互變動,我的建議在單一事務執行過程較長、存在較多子事務,而且沒法使用基於消息的事務形態時使用。