這篇文章將介紹什么是分布式事務,分布式事務解決什么問題,對分布式事務實現的難點,解決思路,不同場景下方案的選擇,通過圖解的方式進行梳理、總結和比較。
相信耐心看完這篇文章,談到分布式事務,不再只是有“2PC”、“3PC”、“MQ的消息事務”、“最終一致性”、“TCC”等這些知識碎片,而是能夠將知識連成一片,形成知識體系。
1 什么是事務
介紹分布式事務之前,先介紹什么是事務。
事務的具體定義
事務提供一種機制將一個活動涉及的所有操作納入到一個不可分割的執行單元,組成事務的所有操作只有在所有操作均能正常執行的情況下方能提交,只要其中任一操作執行失敗,都將導致整個事務的回滾。
簡單地說,事務提供一種“ 要么什么都不做,要么做全套(All or Nothing)”機制。
數據庫事務的ACID屬性
事務是基于數據進行操作,需要保證事務的數據通常存儲在數據庫中,所以介紹到事務,就不得不介紹數據庫事務的ACID特性,指數據庫事務正確執行的四個基本特性的縮寫。包含:
- 原子性(Atomicity) 整個事務中的所有操作,要么全部完成,要么全部不完成,不可能停滯在中間某個環節。事務在執行過程中發生錯誤,會被 回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。 例如:銀行轉賬,從A賬戶轉100元至B賬戶,分為兩個步驟:
- (1)從A賬戶取100元
- (2)存入100元至B賬戶。 這兩步要么一起完成,要么一起不完成,如果只完成第一步,第二步失敗,錢會莫名其妙少了100元。
- 一致性(Consistency) 在事務開始之前和事務結束以后,數據庫數據的一致性約束沒有被破壞。 例如:現有完整性約束A+B=100,如果一個事務改變了A,那么必須得改變B,使得事務結束后依然滿足A+B=100,否則事務失敗。
- 隔離性(Isolation) 數據庫允許多個并發事務同時對數據進行讀寫和修改的能力,如果一個事務要訪問的數據正在被另外一個事務修改,只要另外一個事務未提交,它所訪問的數據就不受未提交事務的影響。隔離性可以防止多個事務并發執行時由于交叉執行而導致數據的不一致。 例如:現有有個交易是從A賬戶轉100元至B賬戶,在這個交易事務還未完成的情況下,如果此時B查詢自己的賬戶,是看不到新增加的100元的。
- 持久性(Durability) 事務處理結束后,對數據的修改就是永久的,即便系統故障也不會丟失。
本地事務ACID大家應該都知道了,統一提交,失敗回滾,嚴格保證了同一事務內數據的一致性!而分布式事務不能實現這種ACID,它只能實現CAP原則里的某兩個,CAP也是分布式事務的一個廣泛被應用的原型,CAP(Consistency, Availability, Partition Tolerance), 闡述了一個分布式系統的三個主要方面, 只能同時擇其二進行實現. 常見的有CP系統, AP系統。
應用于CP和AP的原則在業界出現了一些框架:
CP系統就有二階段提交(強一致性)
AP系統就有TCC(補償型事務)
其中最近接觸的aspnetcore.cap就是一個滿足最終一致性的異步消息方案實現的,其中它為MySQL,sqlserver都提供了解決方案,消息隊列可以有kafka和rabbitmq兩種選擇,根據自己的需要去安裝,源代碼在github上有開源,nuget上也有對應的包包!
方案簡介
本地消息表的方案最初是由ebay提出,核心思路是將分布式事務拆分成本地事務進行處理。
方案通過在事務主動發起方額外新建事務消息表,事務發起方處理業務和記錄事務消息在本地事務中完成,輪詢事務消息表的數據發送事務消息,事務被動方基于消息中間件消費事務消息表中的事務。
這樣設計可以避免”業務處理成功 + 事務消息發送失敗",或"業務處理失敗 + 事務消息發送成功"的棘手情況出現,保證2個系統事務的數據一致性。
處理流程
下面把分布式事務最先開始處理的事務方成為事務主動方,在事務主動方之后處理的業務內的其他事務成為事務被動方。
為了方便理解,下面繼續以電商下單為例進行方案解析,這里把整個過程簡單分為扣減庫存,訂單創建2個步驟,庫存服務和訂單服務分別在不同的服務器節點上,其中庫存服務是事務主動方,訂單服務是事務被動方。
事務的主動方需要額外新建事務消息表,用于記錄分布式事務的消息的發生、處理狀態。
整個業務處理流程如下:
步驟1 事務主動方處理本地事務。 事務主動發在本地事務中處理業務更新操作和寫消息表操作。 上面例子中庫存服務階段再本地事務中完成扣減庫存和寫消息表(圖中1、2)。
步驟2 事務主動方通過消息中間件,通知事務被動方處理事務通知事務待消息。 消息中間件可以基于Kafka、RocketMQ消息隊列,事務主動方法主動寫消息到消息隊列,事務消費方消費并處理消息隊列中的消息。 上面例子中,庫存服務把事務待處理消息寫到消息中間件,訂單服務消費消息中間件的消息,完成新增訂單(圖中3 - 5)。
步驟3 事務被動方通過消息中間件,通知事務主動方事務已處理的消息。 上面例子中,訂單服務把事務已處理消息寫到消息中間件,庫存服務消費中間件的消息,并將事務消息的狀態更新為已完成(圖中6 - 8)
為了數據的一致性,當處理錯誤需要重試,事務發送方和事務接收方相關業務處理需要支持冪等。具體保存一致性的容錯處理如下:
- 1、當步驟1處理出錯,事務回滾,相當于什么都沒發生。
- 2、當步驟2、步驟3處理出錯,由于未處理的事務消息還是保存在事務發送方,事務發送方可以定時輪詢為超時消息數據,再次發送的消息中間件進行處理。事務被動方消費事務消息重試處理。
- 3、如果是業務上的失敗,事務被動方可以發消息給事務主動方進行回滾。
- 4、如果多個事務被動方已經消費消息,事務主動方需要回滾事務時需要通知事務被動方回滾。
方案總結
方案的優點如下:
- 從應用設計開發的角度實現了消息數據的可靠性,消息數據的可靠性不依賴于消息中間件,弱化了對MQ中間件特性的依賴。
- 方案輕量,容易實現。
缺點如下:
- 與具體的業務場景綁定,耦合性強,不可公用。
- 消息數據與業務數據同庫,占用業務系統資源。
- 業務系統在使用關系型數據庫的情況下,消息服務性能會受到關系型數據庫并發性能的局限。
對消息確保型-最終一致性的分布式事務的理解:
- 服務A提交數據
- 向消息中心發送消息
- 消息中心向訂閱方推送消息
- 訂閱方處理自己的業務邏輯
- 失敗去反復去重試,直到成功,而不是向強一致性那樣,把A回滾的