文章目錄
- 一、分布式事務基礎
- 什么是事務?
- 本地事物
- 分布式事務
- 分布式事務的場景
- 二、分布式事務解決方案
- 全局事務
- 可靠消息服務
- TCC 事務
- 三、Seata 分布式事務解決方案
- 3.1 Seata-At模式
- 3.2 秒殺項目集成 Seata
- 啟動 Seata-Server
- 項目集成seata配置
- AT模式代碼實現
- 3.3 Seata-TCC深度解析
- TCC模型圖
- 模型設計
- 扣錢業務邏輯
- 加錢業務邏輯
- 業務模型總結
- 并發控制
- 業務模型優化
- 異常處理
- 空回滾
- 冪等
- 防懸掛
- 異常處理流程圖
- Try方法
- Comfirm 方法
- Cancel方法
- TCC模式代碼實現
一、分布式事務基礎
什么是事務?
事務指的就是一個操作單元,在這個操作單元中的所有操作最終要保持一致的行為,要么所有操作
都成功,要么所有的操作都被撤銷。簡單地說,事務提供一種“要么什么都不做,要么做全套”機制
本地事物
本地事物其實可以認為是數據庫提供的事務機制。說到數據庫事務就不得不說,數據庫事務中的四
大特性:
-
A:原子性(Atomicity),一個事務中的所有操作,要么全部完成,要么全部不完成
-
C:一致性(Consistency),在一個事務執行之前和執行之后數據庫都必須處于一致性狀態
-
I:隔離性(Isolation),在并發環境中,當不同的事務同時操作相同的數據時,事務之間互不影響
-
D:持久性(Durability),指的是只要事務成功結束,它對數據庫所做的更新就必須永久的保存下來
數據庫事務在實現時會將一次事務涉及的所有操作全部納入到一個不可分割的執行單元,該執行單
元中的所有操作要么都成功,要么都失敗,只要其中任一操作執行失敗,都將導致整個事務的回滾
分布式事務
分布式事務指事務的參與者、支持事務的服務器、資源服務器以及事務管理器分別位于不同的分布
式系統的不同節點之上。
簡單的說,就是一次大的操作由不同的小操作組成,這些小的操作分布在不同的服務器上,且屬于不同
的應用,分布式事務需要保證這些小操作要么全部成功,要么全部失敗。
本質上來說,分布式事務就是為了保證不同數據庫的數據一致性。
分布式事務的場景
-
單體系統訪問多個數據庫
一個服務需要調用多個數據庫實例完成數據的增刪改操作
-
多個微服務訪問同一個數據庫
多個服務需要調用一個數據庫實例完成數據的增刪改操作
-
多個微服務訪問多個數據庫
多個服務需要調用一個數據庫實例完成數據的增刪改操作
二、分布式事務解決方案
全局事務
全局事務基于DTP模型實現。DTP是由X/Open組織提出的一種分布式事務模型——X/Open
Distributed Transaction Processing Reference Model。它規定了要實現分布式事務,需要三種角色:
-
AP: Application 應用系統 (微服務)
-
TM: Transaction Manager 事務管理器 (全局事務管理)
-
RM: Resource Manager 資源管理器 (數據庫)
整個事務分成兩個階段(2PC):
-
階段一: 表決階段,所有參與者都將本事務執行預提交,并將能否成功的信息反饋發給協調者。
-
階段二: 執行階段,協調者根據所有參與者的反饋,通知所有參與者,步調一致地執行提交或者回
滾。
優點
- 提高了數據一致性的概率,實現成本較低
缺點
-
單點問題: 事務協調者宕機
-
同步阻塞: 延遲了提交時間,加長了資源阻塞時間
-
數據不一致: 提交第二階段,依然存在commit結果未知的情況,有可能導致數據不一致
可靠消息服務
基于可靠消息服務的方案是通過消息中間件保證上、下游應用數據操作的一致性。假設有A和B兩個
系統,分別可以處理任務A和任務B。此時存在一個業務流程,需要將任務A和任務B在同一個事務中處
理。就可以使用消息中間件來實現這種分布式事務。
RocketMQ事務消息流程圖
1)事務消息發送及提交
(1) 發送消息(half消息)
(2) 服務端響應消息寫入結果
(3) 根據發送結果執行本地事務(如果寫入失敗,此時half消息對業務不可見,本地邏輯不執行)
(4) 根據本地事務狀態執行Commit或者Rollback(Commit操作生產消息索引,消息對消費者可見)
2) 事務補償
(1) 對沒有Commit/Rollback的事務消息(pending狀態的消息),從服務端發起一次“回查”
(2) Producer收到回查消息,檢查回查消息對于的本地事務的狀態
(3) 根據本地事務狀態,重新Commit或者Rollback
其中,補償階段用戶解決消息Commit或者Rollback發生超時或者失效的情況
3) 事務消息狀態
事務消息共有三種狀態,提交狀態,回查狀態,中間狀態:
- TransactionStatus.CommitTransaction: 提交事務,它允許消費者消費此消息
- TransactionStatus.RollbackTransaction: 回滾事務,它代表消息將被刪除,不允許被消費
- TransactionStatus.Unknown: 中間狀態,它代表需要消息隊列來確認狀態
消息生產者實現
發送代碼如下:
Message<OperateIntergralVo> message = MessageBuilder.withPayload(vo).setHeader("orderNo",orderNo).build();
TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction("tx_group", "tx_topic", message, orderNo);
String sendStatus = sendResult.getSendStatus().name();
String localTXState = sendResult.getLocalTransactionState().name();
og.info(">>>> send status={},localTransactionState={} <<<<",sendStatus,localTXState);
if(sendResult.getLocalTransactionState().equals(LocalTransactionState.COMMIT_MESSAGE)){return "退款成功";
}else{return "退款失敗";
}
創建事務消息生產者端的消息監聽器,注意是生產者,不是消費者,我們需要實現的是RocketMQLocalTransactionListener
接口,代碼如下:
@RocketMQTransactionListener(txProducerGroup = "tx_group")
@Slf4j
public class OrderTXMsgListener implements RocketMQLocalTransactionListener {@Autowiredprivate IOrderInfoService orderInfoService;@Overridepublic RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {log.info("執行本地事務");RocketMQLocalTransactionState result = RocketMQLocalTransactionState.COMMIT;try {String orderNo = (String) arg;orderInfoService.changeOrderStatusToRefund(orderNo);} catch (Exception e) {result = RocketMQLocalTransactionState.ROLLBACK;}return result;}@Overridepublic RocketMQLocalTransactionState checkLocalTransaction(Message msg) {String orderNo = (String) msg.getHeaders().get("orderNo");if(!StringUtils.isEmpty(orderNo)){OrderInfo orderInfo = orderInfoService.getOrderStatus(orderNo);if(OrderInfo.STATUS_REFUND.equals(orderInfo.getStatus())){return RocketMQLocalTransactionState.COMMIT;}}return RocketMQLocalTransactionState.ROLLBACK;}
}
優缺點:
- 優點:實現最終一致性,并發較高,性能較快
- 缺點:異步執行,無法直接獲取到遠程服務的返回結果
- 缺點:場景限制,只有消費者無論如何都能執行成功的場景,才適合采用此方案
- 缺點:可能存在短暫的不一致
TCC 事務
TCC 即為 Try Confirm Cancel,它屬于補償型分布式事務。TCC 實現分布式事務一共有三個步驟:
- Try:嘗試待執行的業務
這個過程并未執行業務,只是完成所有業務的一致性檢查,并預留好執行所需的全部資源
- Confirm:確認執行業務
確認執行業務操作,不做任何業務檢查, 只使用Try階段預留的業務資源。通常情況下,采用TCC
則認為 Confirm 階段是不會出錯的。即:只要Try成功,Confirm一定成功。若Confirm階段真的
出錯了,需引入重試機制或人工處理。
- Cancel:取消待執行的業務
取消Try階段預留的業務資源。通常情況下,采用TCC則認為Cancel階段也是一定成功的。若
Cancel階段真的出錯了,需引入重試機制或人工處理。
TCC 兩階段提交與 XA 兩階段提交的區別是:
-
XA 是資源層面的分布式事務,強一致性,在兩階段提交的整個過程中,一直會持有資源的鎖。
-
TCC 是業務層面的分布式事務,最終一致性,不會一直持有資源的鎖。
TCC 事務的優缺點:
-
優點:把數據庫層的二階段提交上提到了應用層來實現,規避了數據庫層的2PC性能低下問題。
-
缺點:TCC的Try、Confirm和Cancel操作功能需業務提供,開發成本高。
三、Seata 分布式事務解決方案
2019 年 1 月,阿里巴巴中間件團隊發起了開源項目 Fescar(Fast & EaSy Commit And
Rollback),其愿景是讓分布式事務的使用像本地事務的使用一樣,簡單和高效,并逐步解決開發者們
遇到的分布式事務方面的所有難題。后來更名為 Seata,意為:Simple Extensible Autonomous
Transaction Architecture,是一套分布式事務解決方案。
Seata 的設計目標是對業務無侵入,因此從業務無侵入的2PC方案著手,在傳統2PC的基礎上演進。
它把一個分布式事務理解成一個包含了若干分支事務的全局事務。全局事務的職責是協調其下管轄的分
支事務達成一致,要么一起成功提交,要么一起失敗回滾。此外,通常分支事務本身就是一個關系數據
庫的本地事務。
3.1 Seata-At模式
Seata主要由三個重要組件組成:
- TC:Transaction Coordinator 事務協調器,管理全局的分支事務的狀態,用于全局性事務的提交
和回滾。
-
TM:Transaction Manager 事務管理器,用于開啟、提交或者回滾全局事務。
-
RM:Resource Manager 資源管理器,用于分支事務上的資源管理,向TC注冊分支事務,上報分
支事務的狀態,接受TC的命令來提交或者回滾分支事務。
Seata-AT模式的執行流程如下:
-
A服務的TM向TC申請開啟一個全局事務,TC就會創建一個全局事務并返回一個唯一的XID
-
A服務的RM向TC注冊分支事務,并及其納入XID對應全局事務的管轄
-
A服務執行分支事務,向數據庫做操作4. A服務開始遠程調用B服務,此時XID會在微服務的調用鏈上傳播
-
B服務的RM向TC注冊分支事務,并將其納入XID對應的全局事務的管轄
-
B服務執行分支事務,向數據庫做操作
-
全局事務調用鏈處理完畢,TM根據有無異常向TC發起全局事務的提交或者回滾
-
TC協調其管轄之下的所有分支事務, 決定是否回滾
Seata-AT模式實現2PC與傳統2PC的差別:
- 架構層次方面,傳統2PC方案的 RM 實際上是在數據庫層,RM本質上就是數據庫自身,通過XA協
議實現,而 Seata 的 RM 是以 jar 包的形式作為中間件層部署在應用程序這一側的。
- 兩階段提交方面,傳統2PC無論第二階段的決議是commit還是rollback,事務性資源的鎖都要保
持到Phase2完成才釋放。而 Seata 的做法是在 Phase1 就將本地事務提交,這樣就可以省去Phase2
持鎖的時間,整體提高效率。
3.2 秒殺項目集成 Seata
啟動 Seata-Server
詳情請看部署文檔/seata-server部署文檔.md
項目集成seata配置
詳情請看 集成文檔/seata客戶端集成文檔.md
AT模式代碼實現
分布式事務發起方只需要貼@GlobalTransactional
注解即可
分支分布式事務貼上@Transactional
即可
3.3 Seata-TCC深度解析
TCC模型圖
模型設計
業務場景
1.賬戶支付,用戶賬戶金額減少
2.賬戶退款,用戶賬戶金額增加
表設計
CREATE TABLE `user_account` (`user_id` varchar(100) NOT NULL COMMENT '用戶UID',`gmt_create` datetime NOT NULL COMMENT '創建時間',`gmt_modified` datetime NOT NULL COMMENT '修改時間',`amount` bigint(20) NOT NULL COMMENT '用戶余額',PRIMARY KEY (`user_id`),KEY `idx_gmt_create` (`gmt_create`),KEY `idx_gmt_modified` (`gmt_modified`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
扣錢業務邏輯
場景: 賬戶A上有100元,要扣除其中的30元
Try: 檢查余額,扣除其中30元;
Confirm: 空提交
Cancel: 返還扣除的30元
加錢業務邏輯
Try: 空操作;
Confirm: 增加可用金額30元
Cancel: 空操作
業務模型總結
并發控制
賬戶A上有100元,事務T1要扣除其中30元,事務T2也要扣除30元,出現并發
Try: 檢查余額,扣除其中30元
T2 Confirm: 空提交
T1 Cancel: 釋放T1預留的30元
業務模型優化
表增加凍結金額字段
CREATE TABLE `user_account` (`user_id` varchar(100) NOT NULL COMMENT '用戶UID',`gmt_create` datetime NOT NULL COMMENT '創建時間',`gmt_modified` datetime NOT NULL COMMENT '修改時間',`amount` bigint(20) NOT NULL COMMENT '用戶余額',`freezed_amount` bigint(20) unsigned DEFAULT '0',PRIMARY KEY (`user_id`),KEY `idx_gmt_create` (`gmt_create`),KEY `idx_gmt_modified` (`gmt_modified`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
扣錢為例: 賬戶上有100元,要扣除其中30元(此時里面的可用金額=amount-freezed_amount)
Try: 檢查余額,扣除其中30元(freezed_amount=freezed_amount+30)
Confirm: 扣除30元( amount=amount-30 freezed_amount=freezed_amount-30)
Cancel: 釋放預留的30元(freezed_amount=freezed_amount-30)
加錢為例: 賬戶上有100元,要加30元(此時里面的可用金額=amount-freezed_amount)
Try: 空操作
Confirm: 增加30元( amount=amount+30)
Cancel: 空操作
異常處理
空回滾
Try 方法未執行,Cancel 執行了
出現原因:
- Try 超時
- 分布式事務回滾,觸發 Cancel
- 未收到 Try,收到 Cancel
解決方案: Cancel 方法需要識別出是否執行 Try 方法,如果執行了就正常執行 Cancel,如果沒有就直接結束
增加事務日志表來實現這個功能.
CREATE TABLE `account_transaction` (`tx_id` varchar(100) NOT NULL COMMENT '事務Txid',`action_id` varchar(100) NOT NULL COMMENT '分支事務id',`gmt_create` datetime NOT NULL COMMENT '創建時間',`gmt_modified` datetime NOT NULL COMMENT '修改時間',`user_id` varchar(100) NOT NULL COMMENT '賬戶Uid',`amount` varchar(100) NOT NULL COMMENT '變動金額',`type` varchar(100) NOT NULL DEFAULT '' COMMENT '變動類型',PRIMARY KEY (`tx_id`,`action_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
冪等
多次調用二階段方法
出現原因:
- 網絡異常
- 分支事務所在服務器宕機
解決方案: 做冪等性處理
CREATE TABLE `account_transaction` (`tx_id` varchar(100) NOT NULL COMMENT '事務Txid',`action_id` varchar(100) NOT NULL COMMENT '分支事務id',`gmt_create` datetime NOT NULL COMMENT '創建時間',`gmt_modified` datetime NOT NULL COMMENT '修改時間',`user_id` varchar(100) NOT NULL COMMENT '賬戶Uid',`amount` varchar(100) NOT NULL COMMENT '變動金額',`type` varchar(100) NOT NULL DEFAULT '' COMMENT '變動類型',`state` smallint(4) NOT NULL COMMENT '狀態: 1.初始化 2.已提交 3.已回滾',PRIMARY KEY (`tx_id`,`action_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
防懸掛
Cancel 比 Try 先執行
出現原因:
-
Try 超時(擁堵)
-
分布式事務回滾觸發 Cancel
-
擁堵的 Try 到達
要允許空回滾,但是要拒絕空回滾之后的 Try 方法.
解決方案: 在Try方法中, 如果根據全局事務ID能查詢出數據出來,說明在try方法之前執行了空回滾,此時就不能進行try方法。否則就正常執行try方法.
異常處理流程圖
Try方法
Comfirm 方法
Cancel方法
TCC模式代碼實現
分布式事務發起方只需要貼@GlobalTransactional
注解即可
分支事務需要完成下面步驟:
1.在接口上貼上@LocalTCC
和@TwoPhaseBusinessAction
注解,具體配置如下:
@LocalTCC
public interface IUsableIntegralService {/*** 增加積分*/@TwoPhaseBusinessAction(name = "incrIntergralTry", commitMethod = "incrIntergralCommit", rollbackMethod = "incrIntergralRollback")void incrIntergralTry(@BusinessActionContextParameter(paramName = "operateIntergralVo") OperateIntergralVo operateIntergralVo, BusinessActionContext context);void incrIntergralCommit(BusinessActionContext context);void incrIntergralRollback(BusinessActionContext context);
}
2.添加實現類,實現try
,confirm
,cancel
方法邏輯即可