事务(TRANSACTION)是一个不可分割的逻辑单元,包含了一组数据库操作命令,并且把所有的命令作为一个整体向系统提交,要么都执行、要么都不执行。
事务作为系统中必须考虑的问题,无论是在单体项目还是在分布式项目中都需要进行处理,而尤其在分布式微服务调用的情况下,事务的处理就变得复杂。
本篇博客进行了Seata分布式事务XA模式、AT模式、TCC模式的介绍和对比,阐述了三种模式的联系和不同,并结合案例分析了seata的作用,对比了XA模式和AT模式下对执行其他操作的影响。
本篇博客源码以及seata1.4版本的安装包见下面git地址
https://gitee.com/pet365/seta-demo
关于分布式事务,CAP理论见下面博客:
分布式事务——CAP理论 & 解决分布式事务的思路 & Seata组件初识 和 部署
【合集】Spring Cloud 组件——架构进化史话 & Eureka,Nacos,OpenFeign,Ribbon,Sentinel,Gateway . . .
1、XA规范使用两阶段提交(2PC,Two-Phase Commit)来保证所有资源同时提交或回滚任何特定的事务。
2、AT模式与XA模式
(1)Seata AT模式是两阶段提交协议的演变:
(2)AT模式与XA模式的区别
3、Tny-Confirm-Cancel,TCC模式
TCC是分布式事务中二阶段提交协议的实现,它的全称为Tny-Confirm-Cancel,即资源预留(Try)、确认操作(Confirm)、取消操作(Cancel),具体含义如下:
XA规范在上世纪90年代初就被提出。目前,几乎所有主流的数据库都对XA规范提供了支持。
定义了Java的XA接口,由具体的厂商提供实现,J2EE容器(WebLogic、JBoss)是JTA接口的实现者。
JTA的缺点:
优点:开发代价小,程序不需要有太多的变化。
http://seata.io/zh-cn/index.html
在Seata定义的分布式事务框架内,利用事务资源(数据库、消息服务等)对XA协议的支持,以XA协议的机制来管理分支事务的一种事务模式。
XA模式需要事务资源(数据库、消息服务等)支持XA协议,比如mysql把redolog分为两个阶段。
RM一阶段的工作,XA start/XA end/XA prepare+SQL+注册分支
注册分支事务到TC
执行分支事务但不提交
报告执行状态到TC
(1)优点
(2)缺点
XA模式下默认的事务的超时时间为60000毫秒,分支事务超时后会进行全局回滚。
但是如果协调者宕机,那么其中已经准备但未提交事务的所有参与者都会被阻塞。被阻塞的根本是锁。
例如在读已提交隔离级别上,数据库事务通常会获取到待修改行数据的行级排他锁来防止脏写。在分布式事务提交或中止前,参与者数据库在能释放这些锁,因此协调者宕机多久,这些锁就要持有多久(在没有人为干预的情况
下)。这些锁被持有的期间,导致其他事务不能修改这些数据(根据数据库的不同,读取操作也可能被阻塞),所以这些数据相关的业务都会被阻塞,导致应用大面积的不可用,直至存疑事务被解决(提交/中止)。
理论上,如果协调者崩溃并重新启动,它应该从日志中恢复事务的状态,并解决现存的疑虑事务,但是在实际生产中,仍然会有疑虑事务的出现(可能是事务日志被破坏)。
也许你可能会考虑将相关应用的数据库服务器重启,但是在2PC正确的实现中,为了原子性的保证,重启后也必须持有存疑事务的锁。那么这样唯一的解决方案是让管理员手动提交还是回滚事务。
所以,协调者的高可用是需要我们考虑的问题。
Seata AT模式同样是分阶段提交的事务模型,弥补了XA模式资源锁定周期过长的缺陷。缺点就是数据不是强一致性,因为它的数据会真实的提交到数据库的,而如果后面做分支事务有问题的话,回滚靠的是日志来实现最终一致。AT模式,是seatal的默认模式。
第一阶段RM的工作:
第二阶段RM的工作:
AT模式只需要将配置中的data-source-proxy-mode:XA改为AT,或者是直接去除这一行配置即可。
AT模式会使用到几个数据库表:
-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(0) NOT NULL,
`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(0) NOT NULL,
`log_created` datetime(0) NOT NULL,
`log_modified` datetime(0) NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
全局事务表跟分支事务表是一对多的关系,一个全局事务对应多个分支事务。
(1)Seata AT模式是两阶段提交协议的演变:
(2)AT模式与XA模式的区别
Tcc是分布式事务中二阶段提交协议的实现,它的全称为Tny-Confirm-Cancel,即资源预留(Try)、确认操作(Confirm)、取消操作(Cancel),具体含义如下:
Seata的Tcc模式,整体是两阶段提交的模型。全局事务是由若干分支事务组成的,分支事务要满足两阶段提交的模型要求,即需要每个分支事务都具备自己的:一阶段prepare、二阶段commit或rollback.
具体的执行流程如下:
根据两阶段行为模式的不同,我们将分支事务划分为Automatic(Branch)Transaction Mode和TCC(Branch) Transaction Mode.
AT模式基于支持本地ACID事务的关系型数据库:
相应的,TCC模式,不依赖于底层数据资源的事务支持:
·一阶段prepare行为:调用自定义的prepare逻辑。
·二阶段commit行为:调用自定义的commit逻辑。
·二阶段rollback行为:调用自定义的rollback逻辑。
所谓TCC模式,是指支持把自定义的分支事务纳入到全局事务的管理中。
我们以订单支付为例,Tcc模式下,我们需要手动编码实现事务处理,在ty阶段尝试预留资源,在commit阶段,再把ty阶段预留的资源转入最终表。因此我们需要在业务数据库表中增加预留字段
原始数据
try阶段对资源进行预处理
扣减余额:对余额进行扣减,及冻结余额
扣减库存:对库存进行扣减,增加冻结库存
更新订单:订单状态为支付中
confirm阶段业务执行提交,释放预留资源。
扣减余额:解除冻结余额
扣减库存:解除冻结库存
更新订单:修改订单状态为支付成功
TCC是一种侵入式的分布式事务解决方案,每个阶段都是独立的事务,与T模式不同的是需要我们手动编码来实现数据恢复。
TCC是一种侵入式的分布式事务解决方案,每个阶段都是独立的事务,与T模式不同的是需要我们手动编码来实现数据恢复。
对业务系统有着非常大的入侵性,设计相对复杂,对比T,它的优点是TCC完全不依赖数据库,并且因为数据回滚问题都是在业务层面解决的,所以不需要使用全局锁,故执行速度更快。
断点运行查看相关的undo log日志
查看这条日志啥
select CAST(rollback_info as char)rollback_info,branch_id,xid,context,
log_status from storage_db.undo_log
由于我查询这条SQL语句耗费了时间,超时后数据回滚
库存微服务两阶段回滚
账户抛出异常
订单数据回滚
库存数据两阶段回滚
如果没有开启全局事务,只是用transactional注解
库存扣减,数据提交
账户抛出异常,未执行SQL
执行后数据的对比,订单数据回滚,库存抛异常,未执行SQL,库存扣减成功,数据出现不一致的情况
(1)Seata AT模式是两阶段提交协议的演变:
(2)AT模式与XA模式的区别
设置一下睡眠时间,让seata一直占用资源
执行SQL语句,修改成功
AT模式一阶段直接提交,不锁定资源。
如果改成XA模式,XA模式一阶段不提交事务,锁定资源
锁定资源,一直等到seata执行完毕才能执行
/*
Navicat Premium Data Transfer
Source Server : 192.168.111.130_BookMall
Source Server Type : MySQL
Source Server Version : 80033
Source Host : 192.168.150.101:3306
Source Schema : account_db
Target Server Type : MySQL
Target Server Version : 80033
File Encoding : 65001
Date: 26/10/2023 10:42:19
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for account_tbl
-- ----------------------------
DROP TABLE IF EXISTS `account_tbl`;
CREATE TABLE `account_tbl` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
`money` int NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of account_tbl
-- ----------------------------
INSERT INTO `account_tbl` VALUES (1, '1', 50);
-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`branch_id` bigint NOT NULL,
`xid` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
`context` varchar(128) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
/*
Navicat Premium Data Transfer
Source Server : 192.168.111.130_BookMall
Source Server Type : MySQL
Source Server Version : 80033
Source Host : 192.168.150.101:3306
Source Schema : order_db
Target Server Type : MySQL
Target Server Version : 80033
File Encoding : 65001
Date: 26/10/2023 10:44:06
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for order_tbl
-- ----------------------------
DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
`commodity_code` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
`count` int NULL DEFAULT 0,
`money` int NULL DEFAULT 0,
`status` int NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of order_tbl
-- ----------------------------
INSERT INTO `order_tbl` VALUES (1, '1', '1001', 1, 100, 0);
-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`branch_id` bigint NOT NULL,
`xid` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
`context` varchar(128) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
/*
Navicat Premium Data Transfer
Source Server : 192.168.111.130_BookMall
Source Server Type : MySQL
Source Server Version : 80033
Source Host : 192.168.150.101:3306
Source Schema : storage_db
Target Server Type : MySQL
Target Server Version : 80033
File Encoding : 65001
Date: 26/10/2023 10:45:46
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for stock_tbl
-- ----------------------------
DROP TABLE IF EXISTS `stock_tbl`;
CREATE TABLE `stock_tbl` (
`id` int NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`count` int NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `commodity_code`(`commodity_code`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of stock_tbl
-- ----------------------------
INSERT INTO `stock_tbl` VALUES (1, '1001', 10);
-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`branch_id` bigint NOT NULL,
`xid` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
`context` varchar(128) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
1、XA规范使用两阶段提交(2PC,Two-Phase Commit)来保证所有资源同时提交或回滚任何特定的事务。
2、AT模式与XA模式
(1)Seata AT模式是两阶段提交协议的演变:
(2)AT模式与XA模式的区别
3、Tny-Confirm-Cancel,TCC模式
TCC是分布式事务中二阶段提交协议的实现,它的全称为Tny-Confirm-Cancel,即资源预留(Try)、确认操作(Confirm)、取消操作(Cancel),具体含义如下: