Seata 实战部署 + 核心模式代码示例(AT模式为主)
我会以最常用的AT模式为例,完整讲解Seata的「环境部署」+「代码实战」,包含从环境搭建到微服务跨库事务的完整流程,新手也能直接落地。
一、Seata 环境部署(生产级,基于1.7.0版本)
1. 部署架构说明
本次部署采用「TC集群 + Nacos注册中心 + MySQL存储」(生产环境标准配置),涉及组件:
- Seata Server(TC):2台(集群),端口8091/8092
- Nacos:注册中心/配置中心(端口8848)
- MySQL:TC存储库 + 业务库(微服务A/微服务B)
- JDK:1.8+
2. 步骤1:环境准备
(1)创建TC存储库
在MySQL中创建Seata TC的数据库(如seata_db),执行官方SQL脚本(表结构用于存储全局事务状态):
-- 从Seata官网获取最新脚本:https://github.com/seata/seata/blob/1.7.0/server/src/main/resources/db/mysql.sql
CREATE DATABASE IF NOT EXISTS seata_db DEFAULT CHARACTER SET utf8mb4;
USE seata_db;
-- 全局事务表
CREATE TABLE IF NOT EXISTS `global_table` (
`xid` varchar(128) NOT NULL,
`transaction_id` bigint DEFAULT NULL,
`status` tinyint NOT NULL,
`application_id` varchar(32) DEFAULT NULL,
`transaction_service_group` varchar(32) DEFAULT NULL,
`transaction_name` varchar(128) DEFAULT NULL,
`timeout` int DEFAULT NULL,
`begin_time` bigint DEFAULT NULL,
`application_data` varchar(2000) DEFAULT NULL,
`gmt_create` datetime DEFAULT NULL,
`gmt_modified` datetime DEFAULT NULL,
PRIMARY KEY (`xid`),
KEY `idx_status_gmt_modified` (`status`,`gmt_modified`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 分支事务表
CREATE TABLE IF NOT EXISTS `branch_table` (
`branch_id` bigint NOT NULL,
`xid` varchar(128) NOT NULL,
`transaction_id` bigint DEFAULT NULL,
`resource_group_id` varchar(32) DEFAULT NULL,
`resource_id` varchar(256) DEFAULT NULL,
`branch_type` varchar(8) DEFAULT NULL,
`status` tinyint DEFAULT NULL,
`client_id` varchar(64) DEFAULT NULL,
`application_data` varchar(2000) DEFAULT NULL,
`gmt_create` datetime DEFAULT NULL,
`gmt_modified` datetime DEFAULT NULL,
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 锁表
CREATE TABLE IF NOT EXISTS `lock_table` (
`row_key` varchar(128) NOT NULL,
`xid` varchar(128) DEFAULT NULL,
`transaction_id` bigint DEFAULT NULL,
`branch_id` bigint DEFAULT NULL,
`resource_id` varchar(256) DEFAULT NULL,
`table_name` varchar(32) DEFAULT NULL,
`pk` varchar(36) DEFAULT NULL,
`status` tinyint DEFAULT NULL COMMENT '0:locked,1:rollbacking',
`gmt_create` datetime DEFAULT NULL,
`gmt_modified` datetime DEFAULT NULL,
PRIMARY KEY (`row_key`),
KEY `idx_status` (`status`),
KEY `idx_branch_id` (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
(2)下载并配置Seata Server
- 下载Seata Server:https://github.com/seata/seata/releases/tag/v1.7.0
- 解压后修改
conf/application.yml(核心配置):
server:
port: 8091 # 集群第二台改为8092
spring:
application:
name: seata-server
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # Nacos地址
namespace: public # 命名空间
group: SEATA_GROUP
config:
server-addr: 127.0.0.1:8848
namespace: public
group: SEATA_GROUP
file-extension: yml
seata:
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: public
group: SEATA_GROUP
data-id: seataServer.properties
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
namespace: public
group: SEATA_GROUP
store:
mode: db # 存储模式:db(生产)/file(测试)
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/seata_db?useUnicode=true&rewriteBatchedStatements=true
user: root
password: 123456
min-conn: 5
max-conn: 30
global-table: global_table
branch-table: branch_table
lock-table: lock_table
server:
service-port: 8091 # 与server.port一致
- 在Nacos中添加配置
seataServer.properties(分组SEATA_GROUP):
# 事务分组配置(核心:微服务的tx-service-group要匹配)
service.vgroupMapping.my_test_tx_group=default
service.default.grouplist=127.0.0.1:8091,127.0.0.1:8092
service.enableDegrade=false
service.disableGlobalTransaction=false
# 客户端配置
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=true
client.rm.sqlParserType=druid
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
(3)启动Seata Server
# 进入bin目录,启动第一台
sh seata-server.sh -p 8091 -h 127.0.0.1 -m db
# 启动第二台(集群)
sh seata-server.sh -p 8092 -h 127.0.0.1 -m db
3. 步骤2:业务库准备(AT模式必备)
在每个微服务的业务库中创建undo_log表(AT模式用于存储镜像数据):
CREATE TABLE IF NOT EXISTS `undo_log` (
`branch_id` bigint NOT NULL COMMENT '分支事务ID',
`xid` varchar(128) NOT NULL COMMENT '全局事务ID',
`context` varchar(128) NOT NULL COMMENT '上下文',
`rollback_info` longblob NOT NULL COMMENT '回滚信息',
`log_status` int NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` datetime NOT NULL COMMENT '创建时间',
`log_modified` datetime NOT NULL COMMENT '修改时间',
PRIMARY KEY (`branch_id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AT模式回滚日志表';
二、AT模式代码实战(Spring Cloud微服务)
1. 场景说明
模拟「订单服务(order-service)」调用「库存服务(stock-service)」的分布式事务:
- 订单服务:创建订单(写order库)
- 库存服务:扣减库存(写stock库)
- 目标:要么都成功,要么都回滚(比如库存不足时,订单也回滚)
2. 步骤1:微服务依赖配置
(1)Pom依赖(两个微服务都需引入)
<!-- Spring Cloud Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2022.0.0.0-RC2</version>
</dependency>
<!-- Seata Starter -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2022.0.0.0-RC2</version>
<!-- 排除自带的低版本Seata,使用1.7.0 -->
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.7.0</version>
</dependency>
<!-- MyBatis + MySQL -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- OpenFeign(服务调用) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
(2)application.yml配置(订单服务为例)
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/order_db?useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: 123456
# Seata配置(核心)
seata:
tx-service-group: my_test_tx_group # 与Nacos中配置的事务分组一致
service:
vgroup-mapping:
my_test_tx_group: default # 与Nacos中service.vgroupMapping一致
grouplist:
default: 127.0.0.1:8091,127.0.0.1:8092
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: public
group: SEATA_GROUP
application: seata-server
mybatis:
mapper-locations: classpath:mapper/*.xml
configuration:
map-underscore-to-camel-case: true
库存服务的配置仅需修改spring.application.name=stock-service和spring.datasource.url=jdbc:mysql://127.0.0.1:3306/stock_db,其余与订单服务一致。
3. 步骤2:业务代码实现
(1)库存服务(stock-service)
① 库存实体与Mapper
// Stock实体
public class Stock {
private Long id;
private String skuCode; // 商品编码
private Integer stock; // 库存数量
// getter/setter
}
// StockMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.stock.mapper.StockMapper">
<update id="deductStock">
UPDATE stock SET stock = stock - #{num} WHERE sku_code = #{skuCode}
</update>
</mapper>
// StockMapper接口
@Mapper
public interface StockMapper {
int deductStock(@Param("skuCode") String skuCode, @Param("num") Integer num);
}
② 库存Service与Controller
// StockService
@Service
public class StockService {
@Autowired
private StockMapper stockMapper;
// 扣减库存(分支事务)
public void deduct(String skuCode, Integer num) {
// 模拟库存不足场景:num>10时抛出异常
if (num > 10) {
throw new RuntimeException("库存不足,扣减失败");
}
stockMapper.deductStock(skuCode, num);
}
}
// StockController
@RestController
@RequestMapping("/stock")
public class StockController {
@Autowired
private StockService stockService;
@PostMapping("/deduct")
public String deduct(@RequestParam String skuCode, @RequestParam Integer num) {
stockService.deduct(skuCode, num);
return "库存扣减成功";
}
}
(2)订单服务(order-service)
① 订单实体与Mapper
// Order实体
public class Order {
private Long id;
private String orderNo; // 订单编号
private String skuCode; // 商品编码
private Integer num; // 购买数量
// getter/setter
}
// OrderMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.order.mapper.OrderMapper">
<insert id="createOrder" useGeneratedKeys="true" keyProperty="id">
INSERT INTO order (order_no, sku_code, num) VALUES (#{orderNo}, #{skuCode}, #{num})
</insert>
</mapper>
// OrderMapper接口
@Mapper
public interface OrderMapper {
int createOrder(Order order);
}
② Feign客户端(调用库存服务)
@FeignClient(name = "stock-service")
public interface StockFeignClient {
@PostMapping("/stock/deduct")
String deduct(@RequestParam("skuCode") String skuCode, @RequestParam("num") Integer num);
}
③ 订单Service(全局事务发起方)
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockFeignClient stockFeignClient;
// 全局事务入口:@GlobalTransactional标记
@GlobalTransactional(rollbackFor = Exception.class) // AT模式核心注解
public void createOrder(String skuCode, Integer num) {
// 1. 创建订单(本地分支事务)
Order order = new Order();
order.setOrderNo(UUID.randomUUID().toString());
order.setSkuCode(skuCode);
order.setNum(num);
orderMapper.createOrder(order);
// 2. 调用库存服务扣减库存(远程分支事务)
stockFeignClient.deduct(skuCode, num);
}
}
④ 订单Controller
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/create")
public String createOrder(@RequestParam String skuCode, @RequestParam Integer num) {
try {
orderService.createOrder(skuCode, num);
return "订单创建成功";
} catch (Exception e) {
return "订单创建失败:" + e.getMessage();
}
}
}
4. 测试验证
(1)正常场景(num=5,库存充足)
调用接口:POST http://localhost:8080/order/create?skuCode=iphone15&num=5
- 结果:订单库新增订单记录,库存库扣减5个库存,事务提交成功。
(2)异常场景(num=20,库存不足)
调用接口:POST http://localhost:8080/order/create?skuCode=iphone15&num=20
- 库存服务抛出“库存不足”异常;
- 订单服务的@GlobalTransactional触发全局回滚;
- 结果:订单库无新增记录,库存库数据无变化,实现分布式事务回滚。
三、TCC模式代码示例(补充)
如果需要TCC模式,核心差异在业务代码层(需手动实现Try/Confirm/Cancel),以下是关键代码:
1. 库存服务的TCC接口
@Service
public class StockTccService {
// Try阶段:预占库存
@TccAction(name = "deductStockTcc", commitMethod = "confirm", rollbackMethod = "cancel")
public boolean prepareDeductStock(String skuCode, Integer num) {
// 1. 预占库存(冻结库存,如stock_freeze表)
// 2. 记录TCC事务状态
return true;
}
// Confirm阶段:确认扣减(正式扣减库存)
public boolean confirm(String skuCode, Integer num) {
// 执行正式扣减
return true;
}
// Cancel阶段:回滚预占(释放冻结库存)
public boolean cancel(String skuCode, Integer num) {
// 释放冻结的库存
return true;
}
}
2. 订单服务调用(全局事务仍用@GlobalTransactional)
@GlobalTransactional
public void createOrder(String skuCode, Integer num) {
// 创建订单
orderMapper.createOrder(order);
// 调用TCC的prepare方法
stockFeignClient.prepareDeductStock(skuCode, num);
}
四、总结
- Seata部署核心:TC集群化(Nacos注册)+ MySQL存储(生产必选)+ 业务库undo_log表(AT模式必备)。
- AT模式关键:通过
@GlobalTransactional标记全局事务入口,业务代码0侵入,底层依赖undo_log的镜像数据自动补偿。 - 测试验证:异常场景下,Seata会自动触发全局回滚,保证跨库事务的最终一致性;TCC模式需手动实现Try/Confirm/Cancel接口,适配高并发场景。
百流积聚,江河是也;文若化风,可以砾石。

浙公网安备 33010602011771号