為什么不建議你用分布式事務?
原創【51CTO.com原創稿件】伴隨著業務的快速的發展、越來越高的業務復雜度,幾乎每個公司的系統都會從單體走向分布式,特別是轉向微服務架構。
圖片來自 包圖網
隨之而來就必然遇到分布式事務這個難題。而我的這篇文章總結了分布式事務的解決方案,希望給大家帶來幫助。
分布式事務基礎
①到底什么是事務呢?
什么是事務?舉個生活中的例子:你去小賣鋪買東西,“一手交錢,一手交貨”就是一個事務的例子,交錢和交貨必須全部成功,事務才算成功,任一個活動失敗,事務將撤銷所有已成功的活動。
明白上述例子,再來看事務的定義:事務可以看做是一次大的活動,它由不同的小活動組成,這些活動要么全部成功,要么全部失敗。
②先來回顧本地事務
在計算機系統中,更多的是通過關系型數據庫來控制事務,這是利用數據庫本身的事務特性來實現的,因此叫數據庫事務。
由于應用主要靠關系數據庫來控制事務,而數據庫通常和應用在同一個服務器,所以基于關系型數據庫的事務又被稱為本地事務。
回顧一下數據庫事務的四大特性 ACID:
- A(Atomic):原子性,構成事務的所有操作,要么都執行完成,要么全部不執行,不可能出現部分成功部分失敗的情況。
- C(Consistency):一致性,在事務執行前后,數據庫的一致性約束沒有被破壞。
比如:張三向李四轉 100 元,轉賬前和轉賬后的數據是正確狀態這叫一致性,如果出現張三轉出 100 元,李四賬戶沒有增加 100 元這就出現了數據錯誤,就沒有達到一致性。
- I(Isolation):隔離性,數據庫中的事務一般都是并發的,隔離性是指并發的兩個事務的執行互不干擾,一個事務不能看到其他事務運行過程的中間狀態。通過配置事務隔離級別可以避臟讀、重復讀等問題。
- D(Durability):持久性,事務完成之后,該事務對數據的更改會被持久化到數據庫,且不會被回滾。
數據庫事務在實現時會將一次事務涉及的所有操作全部納入到一個不可分割的執行單元,該執行單元中的所有操作要么都成功,要么都失敗,只要其中任一操作執行失敗,都將導致整個事務的回滾。
③分布式事務
銀行跨行轉賬業務是一個典型分布式事務場景,假設 A 需要跨行轉賬給 B,那么就涉及兩個銀行的數據,無法通過一個數據庫的本地事務保證轉賬的 ACID,只能夠通過分布式事務來解決。
分布式事務就是指事務的發起者、資源及資源管理器和事務協調者分別位于分布式系統的不同節點之上。
在上述轉賬的業務中,用戶 A-100 操作和用戶 B+100 操作不是位于同一個節點上。
本質上來說,分布式事務就是為了保證在分布式場景下,數據操作的正確執行。
分布式事務在分布式環境下,為了滿足可用性、性能與降級服務的需要,降低一致性與隔離性的要求,一方面遵循 BASE 理論(BASE 相關理論,涉及內容非常多,感興趣的程序員們,可以參考 BASE 理論)。
BASE 理論:
- 基本業務可用性(Basic Availability)
- 柔性狀態(Soft state)
- 最終一致性(Eventual consistency)
同樣的,分布式事務也部分遵循 ACID 規范:
- 原子性:嚴格遵循
- 一致性:事務完成后的一致性嚴格遵循;事務中的一致性可適當放寬
- 隔離性:并行事務間不可影響;事務中間結果可見性允許安全放寬
- 持久性:嚴格遵循
④分布式事務場景
典型的場景就是微服務架構:微服務之間通過遠程調用完成事務操作。
比如:訂單微服務和庫存微服務,下單的同時訂單微服務請求庫存微服務減庫存。簡言之:跨 JVM 進程產生分布式事務。
單體系統訪問多個數據庫實例:當單體系統需要訪問多個數據庫(實例)時就會產生分布式事務。
比如:用戶信息和訂單信息分別在兩個 MySQL 實例存儲,用戶管理系統刪除用戶信息,需要分別刪除用戶信息及用戶的訂單信息,由于數據分布在不同的數據實例,需要通過不同的數據庫鏈接去操作數據,此時產生分布式事務。
簡言之:跨數據庫實例產生分布式事務。
多服務訪問同一個數據庫實例:比如:訂單微服務和庫存微服務即使訪問同一個數據庫也會產生分布式事務,原因就是跨 JVM 進程,兩個微服務持有了不同的數據庫鏈接進行數據庫操作,此時產生分布式事務。
分布式事務的解決方案
①2PC(兩階段提交)/XA
2PC(Two-phase commit protocol),中文叫二階段提交。
二階段提交是一種強一致性設計,2PC 引入一個事務協調者的角色來協調管理各參與者(也可稱之為各本地資源)的提交和回滾,二階段分別指的是準備(投票)和提交兩個階段。
XA 是由 X/Open 組織提出的分布式事務的規范,XA 規范主要定義了(全局)事務管理器(TM)和(局部)資源管理器(RM)之間的接口。本地的數據庫如 MySQL 在 XA 中扮演的是 RM 角色。
XA 一共分為兩階段:
- 第一階段(prepare):即所有的參與者 RM 準備執行事務并鎖住需要的資源。參與者 ready 時,向 TM 報告已準備就緒。
- 第二階段 (commit/rollback):當事務管理者(TM)確認所有參與者(RM)都 ready 后,向所有參與者發送 commit 命令。
目前主流的數據庫基本都支持 XA 事務,包括 MySQL、Oracle、SQL Server、Postgre。
XA 事務由一個或多個資源管理器(RM)、一個事務管理器(TM)和一個應用程序(ApplicationProgram)組成。
如果有任何一個參與者 prepare 失敗,那么 TM 會通知所有完成 prepare 的參與者進行回滾。
XA 事務的特點是:
- 簡單易理解,開發較容易
- 對資源進行了長時間的鎖定,并發度低
②三階段提交(3PC)
三階段提交又稱 3PC,相對于 2PC 來說增加了 CanCommit 階段和超時機制。
如果段時間內沒有收到協調者的 commit 請求,那么就會自動進行 commit,解決了 2PC 單點故障的問題。但是性能問題和不一致問題仍然沒有根本解決。
下面我們還是一起看下三階段流程的是什么樣的?
第一階段:CanCommit 階段。這個階段所做的事很簡單,就是協調者詢問事務參與者,你是否有能力完成此次事務。
如果都返回 yes,則進入第二階段。有一個返回 no 或等待響應超時,則中斷事務,并向所有參與者發送 abort 請求。
第二階段:PreCommit 階段。此時協調者會向所有的參與者發送 PreCommit 請求,參與者收到后開始執行事務操作,并將 Undo 和 Redo 信息記錄到事務日志中。
參與者執行完事務操作后(此時屬于未提交事務的狀態),就會向協調者反饋“Ack”表示我已經準備好提交了,并等待協調者的下一步指令。
第三階段:DoCommit 階段。在階段二中如果所有的參與者節點都可以進行 PreCommit 提交,那么協調者就會從“預提交狀態”轉變為“提交狀態”。
然后向所有的參與者節點發送"doCommit"請求,參與者節點在收到提交請求后就會各自執行事務提交操作,并向協調者節點反饋“Ack”消息,協調者收到所有參與者的 Ack 消息后完成事務。
相反,如果有一個參與者節點未完成 PreCommit 的反饋或者反饋超時,那么協調者都會向所有的參與者節點發送 abort 請求,從而中斷事務。
③SAGA
Saga 是這一篇數據庫論文 saga 提到的一個方案。其核心思想是將長事務拆分為多個本地短事務,由 Saga 事務協調器協調,如果正常結束那就正常完成,如果某個步驟失敗,則根據相反順序一次調用補償操作。
把上面的轉賬作為例子,一個成功完成的 SAGA 事務時序圖如下:
SAGA 事務的特點:
- 并發度高,不用像 XA 事務那樣長期鎖定資源
- 需要定義正常操作以及補償操作,開發量比 XA 大
- 一致性較弱,對于轉賬,可能發生 A 用戶已扣款,最后轉賬又失敗的情況
④TCC
2PC 是數據庫層面的,而 TCC 是業務層面的分布式事務,就像我前面說的分布式事務不僅僅包括數據庫的操作,還包括發送短信等,這時候 TCC 就派上用場了!
TCC 的 3 個階段:
- Try 階段:嘗試執行,完成所有業務檢查(一致性), 預留必須業務資源(準隔離性)
- Confirm 階段:確認執行真正執行業務,不作任何業務檢查,只使用 Try 階段預留的業務資源,Confirm 操作要求具備冪等設計,Confirm 失敗后需要進行重試。
- Cancel 階段:取消執行,釋放 Try 階段預留的業務資源,也可以理解為可以理解為把預留階段的動作撤銷了。Cancel 階段的異常和 Confirm 階段異常處理方案基本上一致,要求滿足冪等設計。
把上面的轉賬作為例子,通常會在 Try 里面凍結金額,但不扣款,Confirm 里面扣款,Cancel 里面解凍金額。
一個成功完成的 TCC 事務時序圖如下:
TCC 特點如下:
- 并發度較高,無長期資源鎖定
- 開發量較大,需要提供 Try/Confirm/Cancel 接口
- 一致性較好,不會發生 SAGA 已扣款最后又轉賬失敗的情況
- TCC 適用于訂單類業務,對中間狀態有約束的業務
⑤本地消息表
本地消息表其實就是利用了 各系統本地的事務來實現分布式事務。
本地消息表顧名思義就是會有一張存放本地消息的表,一般都是放在數據庫中,然后在執行業務的時候將業務的執行和將消息放入消息表中的操作放在同一個事務中,這樣就能保證消息放入本地表中業務肯定是執行成功的。
然后再去調用下一個操作,如果下一個操作調用成功了好說,消息表的消息狀態可以直接改成已成功。
如果調用失敗也沒事,會有后臺任務定時去讀取本地消息表,篩選出還未成功的消息再調用對應的服務,服務更新成功了再變更消息的狀態。
這時候有可能消息對應的操作不成功,因此也需要重試,重試就得保證對應服務的方法是冪等的,而且一般重試會有最大次數,超過最大次數可以記錄下報警讓人工處理。
可以看到本地消息表其實實現的是最終一致性,容忍了數據暫時不一致的情況。
⑥消息事務
RocketMQ 就很好的支持了消息事務,讓我們來看一下如何通過消息實現事務。
第一步先給 Broker 發送事務消息即半消息,半消息不是說一半消息,而是這個消息對消費者來說不可見,然后發送成功后發送方再執行本地事務。再根據本地事務的結果向 Broker 發送 Commit 或者 RollBack 命令。
并且 RocketMQ 的發送方會提供一個反查事務狀態接口,如果一段時間內半消息沒有收到任何操作請求,那么 Broker 會通過反查接口得知發送方事務是否執行成功,然后執行 Commit 或者 RollBack 命令。
如果是 Commit 那么訂閱方就能收到這條消息,然后再做對應的操作,做完了之后再消費這條消息即可。
如果是 RollBack 那么訂閱方收不到這條消息,等于事務就沒執行過。可以看到通過 RocketMQ 還是比較容易實現的,RocketMQ 提供了事務消息的功能,我們只需要定義好事務反查接口即可。
同時也可以看到消息事務實現的也是最終一致性。
⑦最大努力通知
發起通知方通過一定的機制最大努力將業務處理結果通知到接收方。
具體包括:
- 有一定的消息重復通知機制。因為接收通知方可能沒有接收到通知,此時要有一定的機制對消息重復通知。
- 消息校對機制。如果盡最大努力也沒有通知到接收方,或者接收方消費消息后要再次消費,此時可由接收方主動向通知方查詢消息信息來滿足需求。
前面介紹的的本地消息表和消息事務都屬于可靠消息,與這里介紹的最大努力通知有什么不同?
可靠消息一致性,發起通知方需要保證將消息發出去,并且將消息發到接收通知方,消息的可靠性關鍵由發起通知方來保證。
最大努力通知,發起通知方盡最大的努力將業務處理結果通知為接收通知方,但是可能消息接收不到,此時需要接收通知方主動調用發起通知方的接口查詢業務處理結果,通知的可靠性關鍵在接收通知方。
解決方案上,最大努力通知需要:
- 提供接口,讓接受通知放能夠通過接口查詢業務處理結果
- 消息隊列 ACK 機制,消息隊列按照間隔 1min、5min、10min、30min、1h、2h、5h、10h 的方式,逐步拉大通知間隔 ,直到達到通知要求的時間窗口上限。之后不再通知
最大努力通知適用于業務通知類型,例如微信交易的結果,就是通過最大努力通知方式通知各個商戶,既有回調通知,也有交易查詢接口。
⑧AT 事務模式
這是阿里開源項目 seata 中的一種事務模式,在螞蟻金服也被稱為 FMT。優點是該事務模式使用方式,類似 XA 模式,業務無需編寫各類補償操作,回滾由框架自動完成,缺點也類似 AT,存在較長時間的鎖,不滿足高并發的場景。
在 Seata 項目中,最早由阿里巴巴中間件開源出的 AT 模式(Automatic Transaction) 是一套創新的、業務無侵入的分布式事務解決方案。
截止 Seata 的 GA 版本發布,AT 模式 已經在開源社區引起了廣泛關注,很多家企業用戶已經將 Seata 的 AT 模式應用于生產。
AT 模式一階段:首先,在 Seata 的組件中,如果你想開啟分布式事務,那么就應該在你的業務入口或者事務發起入口加上 @GlobalTransactional 注解。
如果你是 AT 模式就要做好數據源代理(seata1.0 后全面支持自動代理),并被 sqlsessionfactroy 使用(或者直接 jdbc 操作使用被代理數據源)。
可以發現比較關鍵的異步,與其他模式的區別便是代理數據源,而代理數據源又有什么奧秘呢?
AT 模式二階段提交:二階段如果是提交的話,因為“業務 SQL”在一階段已經提交至數據庫,所以 Seata 框架只需將一階段保存的快照數據和行鎖刪掉,完成數據清理即可。
AT 模式二階段回滾:二階段如果是回滾的話,Seata 就需要回滾一階段已經執行的“業務 SQL”,還原業務數據。
回滾方式便是用“before image”還原業務數據;但在還原前要首先要校驗臟寫,對比“數據庫當前業務數據”和 “after image”。
如果兩份數據完全一致就說明沒有臟寫,可以還原業務數據,如果不一致就說明有臟寫, 出現臟寫就需要轉人工處理。
小結
本文介紹了分布式事務的一些基礎理論,并對常用的分布式事務方案進行了講解。
分布式事務本身就是一個技術難題,業務中具體使用哪種方案還是需要不同的業務特點自行選擇。
但是我們也會發現,分布式事務會大大的提高流程的復雜度,會帶來很多額外的開銷工作,「代碼量上去了,業務復雜了,性能下跌了」。
所以,當我們真實開發的過程中,能不使用分布式事務就不使用。
作者:JackHu
簡介:水滴健康基礎架構資深技術專家
編輯:陶家龍
征稿:有投稿、尋求報道意向技術人請聯絡 editor@51cto.com
【51CTO原創稿件,合作站點轉載請注明原文作者和出處為51CTO.com】