前言:不斷學習就是程序員的宿命
一、Seata概述
1、背景
單體應用被拆分成微服務應用,原來的三個模塊被拆分成三個獨立的應用,分別使用不同的數據源,業務操作需要調用三個服務來完成。此時每個服務內部的數據一致性由本地事務來保證,但是全局的數據一致性問題沒法保證。
Seata是一款開源的分布式事務解決方案,致力於在微服務架構下提供高性能和簡單易用的分布式事務服務。
2、一個典型的分布式事務過程(1+3)-分布式事務處理過程的一個ID+三組件模型:
(1)Transaction ID XID:全局唯一的事務ID
(2)三組概念:
①Transaction Coordinator(TC事務協調器):維護全局和分支事務狀態,驅動全局事務提交或回滾
②Transaction Manager(TM事務管理器):控制全局事務的邊界,負責開啟一個全局事務,並最終發起全局提交或全局回滾的決議
③Resource Manager(RM資源管理器):控制分支事務,負責分支注冊、狀態匯報,並接受事務協調的指令,驅動分支(本地)事務的提交和回滾
3、Seata處理過程
①TM向TC申請開啟一個全局事務,全局事務創建成功並生成一個全局唯一的XID;
②XID在微服務調用鏈路的上下文傳播
③RM向TC注冊分支事務,將其納入XID對應全局事務的管轄
④TM向TC發起針對XID的全局提交或回滾決議;
⑤TC調度XID下管轄的全部分支事務完成提交或回滾事務
二、Seata分布式交易解決方案
案例需求:下訂單-->扣庫存--->減賬戶余額---->修改訂單狀態
環境准備:3個數據庫(訂單庫、庫存庫、賬戶庫)、3個微服務(訂單、庫存、賬戶服務)
當用戶下單時,會在訂單服務中創建一個訂單,然后通過遠程調用庫存服務來扣減下單商品的庫存。再通過遠程調用賬戶服務來扣減賬戶余額,最后在訂單服務中修改訂單狀態為已完成。該操作跨越三個數據庫,有兩次遠程調用,很明顯會有分布式事務問題。
三、Seata-Server安裝
下載地址:https://github.com/seata/seata/releases
1、修改file.conf
主要修改:自定義事務組名稱+事務日志存儲模式為db+數據連接信息(ps:記得備份file.conf)
1.1 service模塊
1.2store模塊
2、數據庫初始化
新建數據庫seata,執行初始表sql位置:/seata/conf/db_store.sql
3、修改registry.conf
4、分別啟動Nacos、seata-server
四、數據庫初始化
①seata_order:存儲訂單數據庫;
②seata_storage:存儲庫存數據;
③seata_account:存儲賬戶信息數據庫;

---seata biz create database seata_order; USE seata_order; CREATE TABLE `t_order` ( `int` bigint(11) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) DEFAULT NULL COMMENT '用戶id', `product_id` bigint(11) DEFAULT NULL COMMENT '產品id', `count` int(11) DEFAULT NULL COMMENT '數量', `money` decimal(11, 0) DEFAULT NULL COMMENT '金額', `status` int(1) DEFAULT NULL COMMENT '訂單狀態: 0:創建中 1:已完結', PRIMARY KEY (`int`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '訂單表' ROW_FORMAT = Dynamic; CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, `ext` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; create database seata_storage; USE seata_storage; DROP TABLE IF EXISTS `t_storage`; CREATE TABLE `t_storage` ( `int` bigint(11) NOT NULL AUTO_INCREMENT, `product_id` bigint(11) DEFAULT NULL COMMENT '產品id', `total` int(11) DEFAULT NULL COMMENT '總庫存', `used` int(11) DEFAULT NULL COMMENT '已用庫存', `residue` int(11) DEFAULT NULL COMMENT '剩余庫存', PRIMARY KEY (`int`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '庫存' ROW_FORMAT = Dynamic; INSERT INTO `t_storage` VALUES (1, 1, 100, 0, 100); CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, `ext` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE database seata_account; USE seata_account; DROP TABLE IF EXISTS `t_account`; CREATE TABLE `t_account` ( `id` bigint(11) NOT NULL COMMENT 'id', `user_id` bigint(11) DEFAULT NULL COMMENT '用戶id', `total` decimal(10, 0) DEFAULT NULL COMMENT '總額度', `used` decimal(10, 0) DEFAULT NULL COMMENT '已用余額', `residue` decimal(10, 0) DEFAULT NULL COMMENT '剩余可用額度', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '賬戶表' ROW_FORMAT = Dynamic; INSERT INTO `t_account` VALUES (1, 1, 1000, 0, 1000); CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, `ext` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
1、建立對應3個數據庫與對應的回滾日志表
(回滾日志表對應建表語句:/seata/conf/db_undo_log.sql)
2、初始數據
五、代碼實現及測試
1、代碼模塊
2001服務為訂單服務驅動業務
2002服務為庫存服務
2003服務為賬戶服務
代碼地址:
2、分布式事務測試
2.1正常情況測試
數據庫情況:
2.2不使用@GlobalTransactional注解超時異常測試
數據庫情況:訂單狀態未支付但用戶已扣錢且庫存已減
2.3使用@GlobalTransactional注解
測試情況:
發現使用@GlobalTransactional注解后,數據庫記錄進行了回滾。實現了分布式事務
六、Seata原理簡介
官網:http://seata.io/zh-cn/docs/dev/mode/at-mode.html(默認為AT模式)
(1)一階段加載
在一階段,Seata會攔截“業務SQL”:
①解析SQL語義,找到“業務SQL”要更新的業務數據,在業務數據被更新之前,將其保存成“before image”
②執行“業務SQL”更新業務數據,在業務數據更新之后,將其生成“after image”
③生成行鎖
以上操作全部在一個數據庫事務內完成,這樣保證了一階段操作的原子性。
(2)二階段提交
二階段如果是順利的話,因為“業務SQL”在一階段已經提交至數據庫,所以Seata框架只需將一階段保存的快照數據和行鎖刪掉,完成數據清理即可。
(3)二階段回滾
二階段如果回滾的話,Seata就需要回滾一階段已執行的“業務SQL”,還原業務數據
回滾方式便是用“before image”還原業務數據;但在還原之前首先要校驗臟寫,對比“數據當前業務數據”和“after image”,如果兩份數據完全一致就說明沒有臟寫,可以還原業務數據,如果不一致就說明臟鞋,需要人工處理。
ps:總結