分佈式事務最經典的七種解決方案

隨着業務的快速發展、業務複雜度越來越高,幾乎每個公司的系統都會從單體走向分佈式,特別是轉向微服務架構。隨之而來就必然遇到分佈式事務這個難題,這篇文章總結了分佈式事務最經典的解決方案,分享給大家。

基礎理論

在講解具體方案之前,我們先了解一下分佈式事務所涉及到的基礎理論知識。

我們拿轉賬作爲例子,A 需要轉 100 元給 B,那麼需要給 A 的餘額 - 100 元,給 B 的餘額 + 100 元,整個轉賬要保證,A-100 和 B+100 同時成功,或者同時失敗。看看在各種場景下,是如何解決這個問題的。

事務

把多條語句作爲一個整體進行操作的功能,被稱爲數據庫事務。數據庫事務可以確保該事務範圍內的所有操作都可以全部成功或者全部失敗。

事務具有 4 個屬性:原子性、一致性、隔離性、持久性。這四個屬性通常稱爲 ACID 特性。

分佈式事務

銀行跨行轉賬業務是一個典型分佈式事務場景,假設 A 需要跨行轉賬給 B,那麼就涉及兩個銀行的數據,無法通過一個數據庫的本地事務保證轉賬的 ACID,只能夠通過分佈式事務來解決。

分佈式事務就是指事務的發起者、資源及資源管理器和事務協調者分別位於分佈式系統的不同節點之上。在上述轉賬的業務中,用戶 A-100 操作和用戶 B+100 操作不是位於同一個節點上。本質上來說,分佈式事務就是爲了保證在分佈式場景下,數據操作的正確執行。

分佈式事務在分佈式環境下,爲了滿足可用性、性能與降級服務的需要,降低一致性與隔離性的要求,一方面遵循 BASE 理論(BASE 相關理論,涉及內容非常多,感興趣的同學,可以參考 BASE 理論):

基本業務可用性(Basic Availability)
柔性狀態(Soft state)
最終一致性(Eventual consistency)
同樣的,分佈式事務也部分遵循 ACID 規範:

原子性:嚴格遵循
一致性:事務完成後的一致性嚴格遵循;事務中的一致性可適當放寬
隔離性:並行事務間不可影響;事務中間結果可見性允許安全放寬
持久性:嚴格遵循

分佈式事務的解決方案

兩階段提交 / XA

XA 是由 X/Open 組織提出的分佈式事務的規範,XA 規範主要定義了 (全局) 事務管理器 (TM) 和(局部)資源管理器 (RM) 之間的接口。本地的數據庫如 mysql 在 XA 中扮演的是 RM 角色

XA 一共分爲兩階段:

第一階段(prepare):即所有的參與者 RM 準備執行事務並鎖住需要的資源。參與者 ready 時,向 TM 報告已準備就緒。
第二階段 (commit/rollback):當事務管理者 (TM) 確認所有參與者 (RM) 都 ready 後,向所有參與者發送 commit 命令。
目前主流的數據庫基本都支持 XA 事務,包括 mysql、oracle、sqlserver、postgre

XA 事務由一個或多個資源管理器(RM)、一個事務管理器(TM)和一個應用程序(ApplicationProgram)組成。

把上面的轉賬作爲例子,一個成功完成的 XA 事務時序圖如下:

如果有任何一個參與者 prepare 失敗,那麼 TM 會通知所有完成 prepare 的參與者進行回滾。

XA 事務的特點是:

如果讀者想要進一步研究 XA,go 語言可參考 DTM,java 語言可參考 seata

SAGA

Saga 是這一篇數據庫論文 saga 提到的一個方案。其核心思想是將長事務拆分爲多個本地短事務,由 Saga 事務協調器協調,如果正常結束那就正常完成,如果某個步驟失敗,則根據相反順序一次調用補償操作。

把上面的轉賬作爲例子,一個成功完成的 SAGA 事務時序圖如下:

SAGA 事務的特點:

論文裏面的 SAGA 內容較多,包括兩種恢復策略,包括分支事務併發執行,我們這裏的討論,僅包括最簡單的 SAGA

SAGA 適用的場景較多,長事務適用,對中間結果不敏感的業務場景適用

如果讀者想要進一步研究 SAGA,go 語言可參考 DTM,java 語言可參考 seata

TCC

關於 TCC(Try-Confirm-Cancel)的概念,最早是由 Pat Helland 於 2007 年發表的一篇名爲《Life beyond Distributed Transactions:an Apostate’s Opinion》的論文提出。

TCC 分爲 3 個階段

把上面的轉賬作爲例子,通常會在 Try 裏面凍結金額,但不扣款,Confirm 裏面扣款,Cancel 裏面解凍金額,一個成功完成的 TCC 事務時序圖如下:

TCC 特點如下:

如果讀者想要進一步研究 TCC,go 語言可參考 DTM,java 語言可參考 seata

本地消息表

本地消息表這個方案最初是 ebay 架構師 Dan Pritchett 在 2008 年發表給 ACM 的文章。設計核心是將需要分佈式處理的任務通過消息的方式來異步確保執行。

大致流程如下:

寫本地消息和業務操作放在一個事務裏,保證了業務和發消息的原子性,要麼他們全都成功,要麼全都失敗。

容錯機制:

本地消息表的特點:

適用於可異步執行的業務,且後續操作無需回滾的業務

事務消息

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

事務消息發送及提交:

正常發送的流程圖如下:

補償流程:

對沒有 Commit/Rollback 的事務消息(pending 狀態的消息),從服務端發起一次 “回查”
Producer 收到回查消息,返回消息對應的本地事務的狀態,爲 Commit 或者 Rollback
事務消息方案與本地消息表機制非常類似,區別主要在於原先相關的本地表操作替換成了一個反查接口

事務消息特點如下:

適用於可異步執行的業務,且後續操作無需回滾的業務

如果讀者想要進一步研究事務消息,可參考 rocketmq,爲了方便大家學習事務消息,DTM 也提供了簡單實現

最大努力通知

發起通知方通過一定的機制最大努力將業務處理結果通知到接收方。具體包括:

有一定的消息重複通知機制。因爲接收通知方可能沒有接收到通知,此時要有一定的機制對消息重複通知。
消息校對機制。如果盡最大努力也沒有通知到接收方,或者接收方消費消息後要再次消費,此時可由接收方主動向通知方查詢消息信息來滿足需求。
前面介紹的的本地消息表和事務消息都屬於可靠消息,與這裏介紹的最大努力通知有什麼不同?

可靠消息一致性,發起通知方需要保證將消息發出去,並且將消息發到接收通知方,消息的可靠性關鍵由發起通知方來保證。

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

解決方案上,最大努力通知需要:

最大努力通知適用於業務通知類型,例如微信交易的結果,就是通過最大努力通知方式通知各個商戶,既有回調通知,也有交易查詢接口

AT 事務模式

這是阿里開源項目 seata 中的一種事務模式,在螞蟻金服也被稱爲 FMT。優點是該事務模式使用方式,類似 XA 模式,業務無需編寫各類補償操作,回滾由框架自動完成,缺點也類似 AT,存在較長時間的鎖,不滿足高併發的場景。有興趣的同學可以參考 seata-AT

分佈式事務中的網絡異常

在分佈式事務的各個環節都有可能出現網絡以及業務故障等問題,這些問題需要分佈式事務的業務方做到防空回滾,冪等,防懸掛三個特性,下面以 TCC 事務說明這些異常情況:

空回滾:

  在沒有調用 TCC 資源 Try 方法的情況下,調用了二階段的 Cancel 方法,Cancel 方法需要識別出這是一個空回滾,然後直接返回成功。

  出現原因是當一個分支事務所在服務宕機或網絡異常,分支事務調用記錄爲失敗,這個時候其實是沒有執行 Try 階段,當故障恢復後,分佈式事務進行回滾則會調用二階段的 Cancel 方法,從而形成空回滾。

冪等:

  由於任何一個請求都可能出現網絡異常,出現重複請求,所以所有的分佈式事務分支,都需要保證冪等性

懸掛:

  懸掛就是對於一個分佈式事務,其二階段 Cancel 接口比 Try 接口先執行。

  出現原因是在 RPC 調用分支事務 try 時,先註冊分支事務,再執行 RPC 調用,如果此時 RPC 調用的網絡發生擁堵,RPC 超時以後,TM 就會通知 RM 回滾該分佈式事務,可能回滾完成後,RPC 請求才到達參與者真正執行。

下面看一個網絡異常的時序圖,更好的理解上述幾種問題

業務處理請求 4 的時候,Cancel 在 Try 之前執行,需要處理空回滾
業務處理請求 6 的時候,Cancel 重複執行,需要冪等
業務處理請求 8 的時候,Try 在 Cancel 後執行,需要處理懸掛

面對上述複雜的網絡異常情況,目前看到各家建議的方案都是業務方通過唯一鍵,去查詢相關聯的操作是否已完成,如果已完成則直接返回成功。相關的判斷邏輯較複雜,易出錯,業務負擔重。

在項目 DTM 中,出現了一種子事務屏障技術,使用該技術,能夠達到這個效果,看示意圖:

所有這些請求,到了子事務屏障後:不正常的請求,會被過濾;正常請求,通過屏障。開發者使用子事務屏障之後,前面所說的各種異常全部被妥善處理,業務開發人員只需要關注實際的業務邏輯,負擔大大降低。
子事務屏障提供了方法 ThroughBarrierCall,方法的原型爲:

func ThroughBarrierCall(db *sql.DB, transInfo *TransInfo, busiCall BusiFunc)

業務開發人員,在 busiCall 裏面編寫自己的相關邏輯,調用該函數。ThroughBarrierCall 保證,在空回滾、懸掛等場景下,busiCall 不會被調用;在業務被重複調用時,有冪等控制,保證只被提交一次。

子事務屏障會管理 TCC、SAGA、XA、事務消息等,也可以擴展到其他領域

子事務屏障技術的原理是,在本地數據庫,建立分支事務狀態表 sub_trans_barrier,唯一鍵爲全局事務 id - 子事務 id - 子事務分支名稱(try|confirm|cancel)

在此機制下,解決了網絡異常相關的問題

對於 SAGA 事務,也是類似的機制。

子事務屏障技術,爲 DTM 首創,它的意義在於設計簡單易實現的算法,提供了簡單易用的接口,在首創,它的意義在於設計簡單易實現的算法,提供了簡單易用的接口,在這兩項的幫助下,開發人員徹底的從網絡異常的處理中解放出來。

該技術目前需要搭配 DTM 事務管理器,目前 SDK 已經提供給 go 語言的開發者。其他語言的 sdk 正在規劃中。對於其他的分佈式事務框架,只要提供了合適的分佈式事務信息,能夠按照上述原理,快速實現該技術。

總結

本文介紹了分佈式事務的一些基礎理論,並對常用的分佈式事務方案進行了講解,在文章的後半部分還給出了事務異常的原因、分類以及優雅的解決方案。

來源:

https://segmentfault.com/a/1190000040321750

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/GI0fnwys6HMqRdGDzhsIEg