分布式事物設計與實踐
數據一致性定義
- 任何人
- 任何時間
- 任何地點
- 任何接入方式
- 任何服務
- 數據都是一致的
數據不一致產生的原因
- 數據分散在多處
- 多個DB
- DB和緩存
- 二手交易平台案例
- 用戶,交易,商品等功能
分布式事物產生的原因
剛開始是一個單體進程

經過演變,單體式服務演變成微服務,每個服務都是單獨的進程

在用戶請求量大的時候,為了緩解數據庫的壓力,添加了分布式緩存

分布式事物案例
電商平台購買商品
下單->減庫存->支付

這就是分布式事物問題,當APP要買東西,這個操作會涉及到多個服務,意味着要操作多個數據庫,這樣本地事物就無法保證數據的一致性,所以就產生了分布式事物問題.
分布式事物場景
- 電商下單場景
- 下單
- 發送消息到MQ
- 一致性保證
- 本地事物
- 下單操作
- 發送MQ消息操作
- 放進一個本地事物
上述做法有什么問題?

問題:如果發送消息超時了,你是不知道MQ的返回結果是成功和失敗的,,timeout這操作不是一個原子的
分布式事物分類
- 剛性分布式事物
- 強一致性
- XA模型
- CAP
- CP
- 柔性分布式事物
- 最終一致性
- CAP,BASE理論
- AP
剛性分布式事物
滿足傳統事物特性
ACID( Atomicity-原子性, Consistency-一致性,Isolation-隔離性,Durability-持久性)
XA模型
- XA是X/Open CAE Specification(Distributed Transaction Processing)模型中定義,XA規范由AP,RM,TM組成
- 其中應用程序(Application Program簡稱AP),AP定義事物邊界(定義事物開始和結束)並訪問邊界事物內的資源
- 資源管理器(Resource Manager簡稱RM),RM管理計算機共享的資源,資源及數據庫等
- 事物管理器(Transaction Manager,簡稱TM),負責管理全局事物,分配事物唯一標識,監控事物的執行進度,並負責事物的提交,,回滾,失敗恢復等

2PC(兩階段提交-XA規范標准實現)
- 案例
- 組織爬山
- 過程
- 二階段提交,是XA規范的標准實現
- TM發起prepare投票
- RM都同意后,TM再發起Commit
- Commit 過程出現宕機等異常,節點服務重啟后,根據XA recover 再次進行commit補償
- 缺點
- 同步阻塞模型
- 數據庫資源鎖定時間過長
- 全局鎖(隔離級別-串行化),並發低
- 不適合長事物場景

柔性分布式事物
- CAP
- 分布式環境下P一定需要,CA權衡折中
- BASE理論
- Basically Available-基本可用
- Soft state 柔性狀態
- Eventual consistency 最終一致性
- 架構思考
- 柔性事物是對XA協議的妥協,他通過降低強一致性要求,從而降低數據庫資源鎖定時間,提升可用性
- 架構經典實現
- TCC模型
- Saga模型
TCC模型
- Try-confirm-cancel
- TCC模型完全交由業務實現,每個子業務都需要實現Try-Confirm-cancel接口,對業務侵入大
- 資源鎖定交由業務方
- try
- 嘗試執行業務,完成所有檢查,預留必要的業務資源
- confirm
- 真正執行業務,不再做業務檢查
- Cancel
- 釋放Try階段預留的業務資源
- 案例
- 匯款服務,收款服務案例
- A用戶向B用戶匯款500元
- 匯款服務
- try
- 檢查A賬戶的有效性,及查看A賬戶的狀態是否為"轉賬中"或者"凍結"
- 檢查A賬戶余額是否充足
- 從A賬戶中扣減500元,並將狀態設置為轉賬中
- 預留扣減資源,將從A往B賬戶轉賬500元這個事件存入消息或者日志中
- confirm
- 不做任何操作
- cancel
- A賬戶增加500元
- 從日志或者消息中,釋放扣減資源
- 收款服務
- try
- 檢查B賬戶是否有效
- confirm
- 讀取日志或消息,B賬戶增加500元
- 從日志或者消息中,釋放扣減資源
- cancel
- 不做任何操作
Saga模型
- 起源於1987年Hector & Kenneth發表的論文Sagas
- Saga模型把一個分布式事物拆分為多個本地事物,每個本地事物都有相應的執行模塊和補償模塊(對應TCC中的confirm和cancel)
- 當Saga事物中任意一個本地事物出錯時,可以通過調用相關的補償方法恢復之前的事物,到達事物最終一致性
- 當每個Saga子事物T1,T2,....TN都有對應的補償定義C1,C2,....CN-1,那么Saga系統可以保證
- 子事物序列T1,T2,.....TN得以完成(最佳情況)
- 或者序列T1,T2,...TJ,CJ-1,..., C2,C1,0<J<N,得以完成
- Saga隔離性
- 業務層控制並發
- 在應用層加鎖
- 應用層預先凍結資源等
- Saga恢復方式
- 向后恢復,補償所有已完成的事物,如果任意子事物的失敗
- 向前恢復,重試失敗的事物,假設每個子事物最終都會成功
剛性分布式事物VS柔性分布式事物
|
|
剛性事物(XA) |
柔性事物 |
| 業務改造 |
無 |
有 |
| 回滾 |
支持 |
實現補償接口 |
| 一致性 |
強一致(CP) |
最終一致性(AP) |
| 隔離性 |
原生支持 |
實現資源鎖定接口 |
| 並發性能 |
嚴重衰退 |
略微衰退 |
| 適合場景 |
短事物,並發較低 |
長事物,高並發 |
我們如何實踐
- 問題通用解決思路
- 解決這個問題本身
- 讓問題本身消失
- 圓珠筆筆芯漏油解決
- 圓珠筆筆芯在寫2W次就開始漏油,如果要解決這個問題本身,那么就是加入更好的材料,更高端的技術,如果是讓問題本身消失呢,就是固定一個次數,讓它只能寫1.5W次就沒油開始丟棄,這樣的兩種辦法
- 首選是讓問題本身消失,次選是解決這個問題本身
- 方案一:從業務場景消除分布式事物
- 思路:核心業務先處理,其他業務異步處理
- 方案二:柔性分布式事物
柔性分布式事物實踐
- 通用處理思路
- 本地事物-->短事物
- 分布式事物-->長事物
- 轉變成多個短事物
- 案例
- A[下單]->B[減庫存]->C[支付]
- A->DB1
- B->DB2
- C->DB3
- A/B/C都成功
- A/B成功,C失敗
- 補償
- 業務場景
- 異步場景
- 基於MQ消息驅動分布事物
- 同步場景
- 基於異步補償分布
異步場景分布式事物設計
異步場景
商品交易
下單,支付

方案一:業務方提供本地操作成功回查功能
- 事物消息:MQ提供類似X/Open XA的分布式事物功能,通過MQ事物消息能達到分布式事物的最終一致
- 半消息:暫不投遞的消息,發送方已將消息成功發送到了MQ服務端,但是服務端未收到生產者對該消息的二次確認,此時該消息被標記成"暫不能投遞"狀態,處於該種狀態下的消息即半消息
- 消息回查:由於網絡閃斷,生產者應用重啟等原因,導致某條事物消息的二次確認丟失,MQ服務端通過掃描發現某條消息長期處於半消息時,主要主動向消息生產者詢問該消息的最終狀態(Commit或Rollback),即消息回查
- MQ分布式事物設計方案

- MQ分布式事物消息設計
- MQ事物消息設計事物消息作為一種異步確保型事物,將兩個事物分支通過MQ進行異步解耦,MQ事物消息的設計流程同樣借鑒了兩階段提交理論,整體交互流程如上圖
- 事物發起方首先發送prepare消息到MQ
- 在發送prepare消息成功后執行本地事物
- 根據本地事物執行結果返回commit或rollback
- 如果消息是rollback,MQ將刪除該prepare消息,不進行下發,如果是commit消息,MQ會將消息發送給consumer端
- 如果執行本地事物過程中,執行端掛掉,或者超時,,MQ服務器端將不停的詢問producer來獲取事物狀態
- consumer端的消費成功機制有MQ保證
- 成本:
- MQ需要支持半消息
- MQ需要提供消息遍歷
- 業務方需要提供回查接口
- 業務方接入步驟

- 優點
- 通用
- 缺點
- 業務方需要提供回查接口,對業務侵入大
- 發送消息非冪等
- 消費端需要處理冪等
方案二:本地事物消息表
- 本地操作和發送消息通過本地事物強一致性
- 本地事物操作表
- 本地事物消息表
- mqMessages(msgid,content,topic,status)

- 發送端消息不冪等
- At least once (最少發一次)
- Once Only (只發一次)
- At more once(最多發一次)
- 消費端處理消息冪等
- 分布式鎖
- A->B->C
- A/B成功,C失敗
- 記錄錯誤日志
- 報警
- 人工介入
- 優點
- 業務入侵小
相比於提供消息回查接口(RockectMQ)來說,實際異步場景還是本地消息事物表使用的比較多
同步場景分布式事物設計
- 同步場景
- 首頁推薦商品列表
- 商品信息
- 用戶信息
- 社交信息
- 購買商品
- 下單->A
- 減庫存->B
- 支付->C


通過業務邏輯層驅動
- 解決方案
- 基於異步補償的分布式事物
- 架構設計的三大關鍵點

開始記錄調用請求的參數,如果失敗后基於參數做補償接口,接口需要保證冪等性
- 總體架構設計

場景:A下單,B減庫存,C支付,在調用接口的時候,A先走Proxy存入事物ID,狀態,參數等信息,然后執行本地事物,接着B,C走同樣的流程如果都成功,那么事物狀態改成2,也就是成功,如果在C失敗的時候可以更具參數,事物ID對A,B進行補償
業務邏輯層Proxy設計(基於AOP實現)
- 邏輯層調用上加上事物注解@Around("execution(**(..)) && @annotation(TX)")
- Proxy在真正業務邏輯被調用之前,生成一個全局唯一TXID標示事務組,TXID保存在ThreadLocal變量中,方法開始前寫入,完成后清除,並向遠端數據庫寫入TXID並把事務組制成開始狀態
- 業務邏輯層調用數據訪問層之前,通過RPCProxy代理記錄,當前調用請求參數
- 如果業務正常,調用完成后,當前方法的調用記錄刪除或者存檔
- 如果業務異常,查詢調用鏈反向補償

- 數據訪問層設計
- 原子接口
- 補償接口
- 誰來提供?
- 業務方提供
- 冪等性保證
- 采用本地資源鎖,鎖定唯一資源
- 基於原則接口方法,在方法名加注解標注補償方法名
- @Compensable(cancelMethod = "cancelRecord")


- 分布式事物補償服務
- 事物組表(數據庫表TDB)
- 記錄事物組狀態
- txid state timestamp
- 事物調用組表(數據庫表TDB)
- 記錄事物組內的每一次調用,以及相關參數
- txid actionid callmethod pramatype params
- 補償策略
- 調用執行失敗,修改事物組狀態
- 分布式事物補償服務異步執行補償
分布式事物成功案例
- 二手交易創建訂單事務組正常流程
- 鎖庫存->減紅包->創建訂單
- 代理層透明記錄調用請求參數
- 記錄事物域的開始與結束
- 在所有遠程調用成功時
- 對業務邏輯不做侵入

分布式事物失敗案例
- 二手交易創建訂單事務組異常流程
- 微服務數據訪問層失敗,代理更改事務組狀態
- 微服務業務正常執行
- 事物補償服務異步執行補償

好了,到這里分布式事物也就寫完了..休息一下,,哎,又到了找工作的時候了,有需要可以聯系我
