Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。Seata将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案¹。
Seata目前支持以下四种分布式事务模式:
下面我们来详细介绍每种模式的原理和示例。
XA 模式是基于XA协议实现的分布式事务模式,XA协议是由X/Open组织提出的一种两阶段提交协议,它定义了一个全局事务管理器(Transaction Manager)和多个资源管理器(Resource Manager)之间的接口。资源管理器通常是数据库或者消息队列等支持本地事务的组件。XA协议要求资源管理器实现以下接口:
XA协议的工作流程如下:
XA 模式可以保证分布式事务的强一致性,但是也有以下缺点:
Seata实现了XA模式,它将Seata服务端作为全局事务管理器,将Seata客户端作为资源管理器。Seata客户端通过拦截JDBC连接,实现了对XA接口的代理,从而可以与数据库进行通信。Seata客户端还通过注册中心(如Nacos)与Seata服务端进行通信,汇报分支事务的状态,并接收全局事务的命令。
下面是一个使用Seata XA 模式实现分布式事务的简单示例,假设有两个微服务:订单服务和库存服务,订单服务负责创建订单,库存服务负责扣减库存。当用户下单时,需要同时调用两个服务,并保证数据一致性。如果其中一个服务失败了,需要回滚另一个服务的操作。
在两个微服务的pom.xml文件中,添加Seata依赖:
<dependency>
<groupId>io.seatagroupId>
<artifactId>seata-spring-boot-starterartifactId>
<version>1.4.2version>
dependency>
在两个微服务的application.properties文件中,添加Seata相关配置:
# Seata 服务端地址
seata.service.vgroup-mapping.my_test_tx_group=default
seata.service.grouplist.default=127.0.0.1:8091
# Seata 应用ID
seata.application.id=order-service # 或者 inventory-service
# Seata 事务组ID
seata.tx-service-group=my_test_tx_group
# Seata 数据源代理模式
seata.datasource.proxy-mode=XA
# Seata 数据源配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/order_db?useUnicode=true&characterEncoding=utf8&useSSL=false # 或者 inventory_db
spring.datasource.username=root
spring.datasource.password=root
在两个微服务对应的数据库中,创建业务表:
-- 订单表
CREATE TABLE `order_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT '0',
`money` int(11) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `unique_user_commodity` (`user_id`,`commodity_code`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- 库存表
CREATE TABLE `storage_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `unique_commodity_code` (`commodity_code`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
在两个微服务中,分别编写订单服务和库存服务的业务代码,使用@GlobalTransactional注解标注分布式事务的入口方法,使用JdbcTemplate或者Mybatis等方式操作数据库:
// 订单服务
@Service
public class OrderService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private InventoryService inventoryService;
@GlobalTransactional // 分布式事务入口
public void createOrder(String userId, String commodityCode, int orderCount) {
// 扣减库存
inventoryService.deduct(commodityCode, orderCount);
// 创建订单
int orderMoney = calculate(commodityCode, orderCount);
jdbcTemplate.update("insert into order_tbl(user_id, commodity_code, count, money) values (?, ?, ?, ?)",
new Object[]{userId, commodityCode, orderCount, orderMoney});
// 模拟异常,测试回滚
if (orderCount == 2) {
throw new RuntimeException("create order failed");
}
}
private int calculate(String commodityCode, int orderCount) {
return 100 * orderCount;
}
}
// 库存服务
@Service
public class InventoryService {
@Autowired
private JdbcTemplate jdbcTemplate;
public void deduct(String commodityCode, int count) {
jdbcTemplate.update("update storage_tbl set count = count - ? where commodity_code = ?",
new Object[]{count, commodityCode});
}
}
下载Seata服务端的压缩包,解压后修改conf目录下的file.conf和registry.conf文件,配置好数据库和注册中心(如Nacos)等信息,然后运行bin目录下的seata-server.bat或者seata-server.sh文件,启动Seata服务端。
在两个微服务的项目目录下,运行mvn spring-boot:run命令,启动Seata客户端。
使用Postman或者curl等工具,向订单服务发送请求,创建订单:
curl -X POST http://localhost:8081/order/create?userId=U100001&commodityCode=C00321&orderCount=2
观察控制台输出和数据库变化,可以发现当orderCount为2时,会触发异常,并回滚订单服务和库存服务的操作,保证数据一致性。当orderCount为其他值时,会正常执行,并提交订单服务和库存服务的操作。
AT 模式是基于自动补偿机制实现的分布式事务模式,它不需要数据库支持XA接口,也不需要业务实现额外的操作。AT 模式的核心思想是:在第一阶段提交本地事务时,记录数据的前后镜像(before image and after image),在第二阶段根据镜像进行反向操作来实现数据回滚。AT 模式的隔离流程如下:
AT 模式可以实现分布式事务的最终一致性,但是也有以下缺点:
Seata实现了AT模式,它将Seata服务端作为全局事务协调器,将Seata客户端作为分支事务执行器。Seata客户端通过拦截JDBC连接,实现了对SQL语句的解析和执行,以及对undo_log表的操作。Seata客户端还通过注册中心(如Nacos)与Seata服务端进行通信,汇报分支事务的状态,并接收全局事务的命令。
下面是一个使用Seata AT 模式实现分布式事务的简单示例,假设有两个微服务:订单服务和库存服务,订单服务负责创建订单,库存服务负责扣减库存。当用户下单时,需要同时调用两个服务,并保证数据一致性。如果其中一个服务失败了,需要回滚另一个服务的操作。
在两个微服务的pom.xml文件中,添加Seata依赖:
<dependency>
<groupId>io.seatagroupId>
<artifactId>seata-spring-boot-starterartifactId>
<version>1.4.2version>
dependency>
在两个微服务的application.properties文件中,添加Seata相关配置:
# Seata 服务端地址
seata.service.vgroup-mapping.my_test_tx_group=default
seata.service.grouplist.default=127.0.0.1:8091
# Seata 应用ID
seata.application.id=order-service # 或者 inventory-service
# Seata 事务组ID
seata.tx-service-group=my_test_tx_group
# Seata 数据源代理模式
seata.datasource.proxy-mode=AT
# Seata 数据源配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/order_db?useUnicode=true&characterEncoding=utf8&useSSL=false # 或者 inventory_db
spring.datasource.username=root
spring.datasource.password=root
在两个微服务对应的数据库中,创建业务表和undo_log表:
-- 订单表
CREATE TABLE `order_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT '0',
`money` int(11) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `unique_user_commodity` (`user_id`,`commodity_code`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- 库存表
CREATE TABLE `storage_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `unique_commodity_code` (`commodity_code`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- undo_log 表(两个数据库都需要)
CREATE TABLE `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(100) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME NOT NULL COMMENT 'modify datetime',
`ext` VARCHAR(100) DEFAULT NULL COMMENT 'reserved field',
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
在两个微服务中,分别编写订单服务和库存服务的业务代码,使用@GlobalTransactional注解标注分布式事务的入口方法,使用@DataSourceProxy注解标注数据源代理对象,使用JdbcTemplate或者Mybatis等方式操作数据库:
// 订单服务
@Service
public class OrderService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private InventoryService inventoryService;
@GlobalTransactional // 分布式事务入口
public void createOrder(String userId, String commodityCode, int orderCount) {
// 扣减库存
inventoryService.deduct(commodityCode, orderCount);
// 创建订单
int orderMoney = calculate(commodityCode, orderCount);
jdbcTemplate.update("insert into order_tbl(user_id, commodity_code, count, money) values (?, ?, ?, ?)",
new Object[]{userId, commodityCode, orderCount, orderMoney});
// 模拟异常,测试回滚
if (orderCount == 2) {
throw new RuntimeException("create order failed");
}
}
private int calculate(String commodityCode, int orderCount) {
return 100 * orderCount;
}
}
// 库存服务
@Service
public class InventoryService {
@Autowired
private JdbcTemplate jdbcTemplate;
@DataSourceProxy // 数据源代理
public void deduct(String commodityCode, int count) {
jdbcTemplate.update("update storage_tbl set count = count - ? where commodity_code = ?",
new Object[]{count, commodityCode});
}
}
下载Seata服务端的压缩包,解压后修改conf目录下的file.conf和registry.conf文件,配置好数据库和注册中心(如Nacos)等信息,然后运行bin目录下的seata-server.bat或者seata-server.sh文件,启动Seata服务端。
在两个微服务的项目目录下,运行mvn spring-boot:run命令,启动Seata客户端。
使用Postman或者curl等工具,向订单服务发送请求,创建订单:
curl -X POST http://localhost:8081/order/create?userId=U100001&commodityCode=C00321&orderCount=2
观察控制台输出和数据库变化,可以发现当orderCount为2时,会触发异常,并回滚订单服务和库存服务的操作,保证数据一致性。当orderCount为其他值时,会正常执行,并提交订单服务和库存服务的操作。
TCC 模式是基于补偿机制实现的分布式事务模式,它不需要数据库支持XA接口,但需要业务实现三个操作:Try、Confirm和Cancel。TCC 模式的核心思想是:在第一阶段执行Try操作,预留或锁定相关资源;在第二阶段根据全局事务的状态,执行Confirm操作或者Cancel操作,释放或回滚相关资源。TCC 模式的工作流程如下:
TCC 模式可以实现分布式事务的最终一致性,但是也有以下缺点:
Seata实现了TCC模式,它将Seata服务端作为全局事务协调器,将Seata客户端作为分支事务执行器。Seata客户端通过注解方式,标注业务服务的三个操作,并通过反射机制调用相应的方法。Seata客户端还通过注册中心(如Nacos)与Seata服务端进行通信,汇报分支事务的状态,并接收全局事务的命令。
下面是一个使用Seata TCC 模式实现分布式事务的简单示例,假设有两个微服务:订单服务和库存服务,订单服务负责创建订单,库存服务负责扣减库存。当用户下单时,需要同时调用两个服务,并保证数据一致性。如果其中一个服务失败了,需要回滚另一个服务的操作。
在两个微服务的pom.xml文件中,添加Seata依赖:
<dependency>
<groupId>io.seatagroupId>
<artifactId>seata-spring-boot-starterartifactId>
<version>1.4.2version>
dependency>
在两个微服务的application.properties文件中,添加Seata相关配置:
# Seata 服务端地址
seata.service.vgroup-mapping.my_test_tx_group=default
seata.service.grouplist.default=127.0.0.1:8091
# Seata 应用ID
seata.application.id=order-service # 或者 inventory-service
# Seata 事务组ID
seata.tx-service-group=my_test_tx_group
# Seata 数据源配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/order_db?useUnicode=true&characterEncoding=utf8&useSSL=false # 或者 inventory_db
spring.datasource.username=root
spring.datasource.password=root
在两个微服务对应的数据库中,创建业务表:
-- 订单表
CREATE TABLE `order_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT '0',
`money` int(11) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `unique_user_commodity` (`user_id`,`commodity_code`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- 库存表
CREATE TABLE `storage_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `unique_commodity_code` (`commodity_code`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
在两个微服务中,分别编写订单服务和库存服务的业务代码,使用@GlobalTransactional注解标注分布式事务的入口方法,使用@TCC注解标注业务服务的三个操作,使用JdbcTemplate或者Mybatis等方式操作数据库:
// 订单服务
@Service
public class OrderService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private InventoryService inventoryService;
@GlobalTransactional // 分布式事务入口
public void createOrder(String userId, String commodityCode, int orderCount) {
// 扣减库存
inventoryService.deduct(commodityCode, orderCount);
// 创建订单
int orderMoney = calculate(commodityCode, orderCount);
order(userId, commodityCode, orderCount, orderMoney);
// 模拟异常,测试回滚
if (orderCount == 2) {
throw new RuntimeException("create order failed");
}
}
private int calculate(String commodityCode, int orderCount) {
return 100 * orderCount;
}
@TCC(confirmMethod = "confirmOrder", cancelMethod = "cancelOrder") // TCC操作
public void order(String userId, String commodityCode, int orderCount, int orderMoney) {
// Try操作:预留订单资源
jdbcTemplate.update("insert into order_tbl(user_id, commodity_code, count, money) values (?, ?, ?, ?)",
new Object[]{userId, commodityCode, orderCount, orderMoney});
}
public void confirmOrder(String userId, String commodityCode, int orderCount, int orderMoney) {
// Confirm操作:无需操作
System.out.println("confirm order");
}
public void cancelOrder(String userId, String commodityCode, int orderCount, int orderMoney) {
// Cancel操作:取消订单资源
jdbcTemplate.update("delete from order_tbl where user_id = ? and commodity_code = ?",
new Object[]{userId, commodityCode});
}
}
// 库存服务
@Service
public class InventoryService {
@Autowired
private JdbcTemplate jdbcTemplate;
@TCC(confirmMethod = "confirmDeduct", cancelMethod = "cancelDeduct") // TCC操作
public void deduct(String commodityCode, int count) {
// Try操作:预留库存资源
jdbcTemplate.update("update storage_tbl set count = count - ? where commodity_code = ?",
new Object[]{count, commodityCode});
}
public void confirmDeduct(String commodityCode, int count) {
// Confirm操作:无需操作
System.out.println("confirm deduct");
}
public void cancelDeduct(String commodityCode, int count) {
// Cancel操作:恢复库存资源
jdbcTemplate.update("update storage_tbl set count = count + ? where commodity_code = ?",
new Object[]{count, commodityCode});
}
}
下载Seata服务端的压缩包,解压后修改conf目录下的file.conf和registry.conf文件,配置好数据库和注册中心(如Nacos)等信息,然后运行bin目录下的seata-server.bat或者seata-server.sh文件,启动Seata服务端。
在两个微服务的项目目录下,运行mvn spring-boot:run命令,启动Seata客户端。
使用Postman或者curl等工具,向订单服务发送请求,创建订单:
curl -X POST http://localhost:8081/order/create?userId=U100001&commodityCode=C00321&orderCount=2
观察控制台输出和数据库变化,可以发现当orderCount为2时,会触发异常,并回滚订单服务和库存服务的操作,保证数据一致性。当orderCount为其他值时,会正常执行,并提交订单服务和库存服务的操作。
SAGA 模式是一种长事务模式,它将一个长时间运行的事务拆分为多个子事务,每个子事务都可以独立地提交或回滚,从而避免长时间占用资源。SAGA 模式的核心思想是:通过状态机来编排子事务的执行顺序和逻辑,每个子事务都有正向操作和反向操作,正向操作用于执行业务逻辑,反向操作用于补偿业务逻辑。SAGA 模式的工作流程如下:
SAGA 模式可以实现分布式事务的最终一致性,但是也有以下缺点:
Seata实现了SAGA模式,它将Seata服务端作为全局事务协调器,将Seata客户端作为分支事务执行器。Seata客户端通过状态机引擎来编排子事务的执行顺序和逻辑,并通过注解方式或者编程方式来标注业务服务的正向操作和反向操作。Seata客户端还通过注册中心(如Nacos)与Seata服务端进行通信,汇报分支事务的状态,并接收全局事务的命令。
下面是一个使用Seata SAGA 模式实现分布式事务的简单示例,假设有两个微服务:订单服务和库存服务,订单服务负责创建订单,库存服务负责扣减库存。当用户下单时,需要同时调用两个服务,并保证数据一致性。如果其中一个服务失败了,需要回滚另一个服务的操作。
在两个微服务的pom.xml文件中,添加Seata依赖:
<dependency>
<groupId>io.seatagroupId>
<artifactId>seata-spring-boot-starterartifactId>
<version>1.4.2version>
dependency>
在两个微服务的application.properties文件中,添加Seata相关配置:
# Seata 服务端地址
seata.service.vgroup-mapping.my_test_tx_group=default
seata.service.grouplist.default=127.0.0.1:8091
# Seata 应用ID
seata.application.id=order-service # 或者 inventory-service
# Seata 事务组ID
seata.tx-service-group=my_test_tx_group
# Seata 数据源配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/order_db?useUnicode=true&characterEncoding=utf8&useSSL=false # 或者 inventory_db
spring.datasource.username=root
spring.datasource.password=root
在两个微服务对应的数据库中,创建业务表:
-- 订单表
CREATE TABLE `order_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT '0',
`money` int(11) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `unique_user_commodity` (`user_id`,`commodity_code`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- 库存表
CREATE TABLE `storage_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `unique_commodity_code` (`commodity_code`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
在两个微服务中,分别编写订单服务和库存服务的业务代码,使用@GlobalTransactional注解标注分布式事务的入口方法,使用@Compensable注解标注业务服务的正向操作和反向操作,使用JdbcTemplate或者Mybatis等方式操作数据库:
// 订单服务
@Service
public class OrderService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private InventoryService inventoryService;
@GlobalTransactional // 分布式事务入口
public void createOrder(String userId, String commodityCode, int orderCount) {
// 扣减库存
inventoryService.deduct(commodityCode, orderCount);
// 创建订单
int orderMoney = calculate(commodityCode, orderCount);
order(userId, commodityCode, orderCount, orderMoney);
// 模拟异常,测试回滚
if (orderCount == 2) {
throw new RuntimeException("create order failed");
}
}
private int calculate(String commodityCode, int orderCount) {
return 100 * orderCount;
}
@Compensable(compensationMethod = "cancelOrder") // SAGA操作
public void order(String userId, String commodityCode, int orderCount, int orderMoney) {
// 正向操作:创建订单资源
jdbcTemplate.update("insert into order_tbl(user_id, commodity_code, count, money) values (?, ?, ?, ?)",
new Object[]{userId, commodityCode, orderCount, orderMoney});
}
public void cancelOrder(String userId, String commodityCode, int orderCount, int orderMoney) {
// 反向操作:删除订单资源
jdbcTemplate.update("delete from order_tbl where user_id = ? and commodity_code = ?",
new Object[]{userId, commodityCode});
}
}
// 库存服务
@Service
public class InventoryService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Compensable(compensationMethod = "cancelDeduct") // SAGA操作
public void deduct(String commodityCode, int count) {
// 正向操作:扣减库存资源
jdbcTemplate.update("update storage_tbl set count = count - ? where commodity_code = ?",
new Object[]{count, commodityCode});
}
public void cancelDeduct(String commodityCode, int count) {
// 反向操作:恢复库存资源
jdbcTemplate.update("update storage_tbl set count = count + ? where commodity_code = ?",
new Object[]{count, commodityCode});
}
}
下载Seata服务端的压缩包,解压后修改conf目录下的file.conf和registry.conf文件,配置好数据库和注册中心(如Nacos)等信息,然后运行bin目录下的seata-server.bat或者seata-server.sh文件,启动Seata服务端。
在两个微服务的项目目录下,运行mvn spring-boot:run命令,启动Seata客户端。
使用Postman或者curl等工具,向订单服务发送请求,创建订单:
curl -X POST http://localhost:8081/order/create?userId=U100001&commodityCode=C00321&orderCount=2
观察控制台输出和数据库变化,可以发现当orderCount为2时,会触发异常,并回滚订单服务和库存服务的操作,保证数据一致性。当orderCount为其他值时,会正常执行,并提交订单服务和库存服务的操作。
Seata是一款支持多种分布式事务模式的框架,它可以根据不同的业务场景和需求,选择合适的模式来实现数据的一致性。Seata的四种模式各有优缺点,需要根据具体情况进行权衡和选择。以下是一个简单的对比表:
模式 | 一致性 | 可用性 | 侵入性 | 复杂度 |
---|---|---|---|---|
XA | 强 | 低 | 低 | 高 |
AT | 最终 | 高 | 低 | 中 |
TCC | 最终 | 高 | 高 | 高 |
SAGA | 最终 | 高 | 高 | 中 |