Spring Cloud Seata 深度解析
# Spring Cloud Seata深度解析:分布式事务核心原理、实战配置与企业级最佳实践
在微服务架构中,随着业务拆分粒度的细化,一个完整的业务流程往往需要跨多个服务调用(如订单创建→库存扣减→支付扣钱→物流创建)。此时,传统的本地事务(如MySQL的ACID)已无法保证跨服务数据的一致性,“分布式事务”问题成为微服务落地的核心痛点之一。
Seata(Simple Extensible Autonomous Transaction Architecture,简单可扩展自治事务框架)是Alibaba开源的分布式事务解决方案,致力于提供高性能、低侵入的分布式事务服务,支持AT、TCC、SAGA、XA等多种事务模式,完美适配Spring Cloud微服务生态。
# 一、核心认知:Seata是什么?为什么选择它?
# 1.1 分布式事务的核心痛点与传统方案局限
分布式事务的核心目标是保证“跨服务数据的一致性”,即多个服务的操作要么全部成功,要么全部失败。传统分布式事务方案存在诸多局限:
- 2PC(两阶段提交):强一致性,但锁资源占用时间长,性能差,协调者单点故障风险高;
- TCC(Try-Confirm-Cancel):性能好,但需业务代码侵入式开发,适配成本高;
- SAGA:适用于长事务,但补偿逻辑复杂,一致性保障弱;
- 本地消息表:低侵入,但需额外设计消息表和补偿逻辑,运维成本高。
Seata的出现正是为了解决这些问题,通过封装多种事务模式,实现“低侵入、高性能、多场景适配”的分布式事务解决方案。
# 1.2 Seata的定义与核心定位
Seata官方定义:一款开源的分布式事务解决方案,为微服务架构提供高性能和简单易用的分布式事务服务。其核心价值在于“简化分布式事务开发”,让开发者无需关注复杂的分布式事务细节,只需通过简单配置或少量注解,即可实现跨服务数据一致性保障。
Seata的核心特性可概括为“4个核心优势”:
- 多事务模式支持:内置AT、TCC、SAGA、XA四种事务模式,适配不同业务场景(如无侵入场景选AT,复杂业务选TCC,长事务选SAGA);
- 低侵入性:基于AOP实现事务增强,大部分场景(如AT模式)无需修改业务代码,仅需添加注解;
- 高性能:通过异步化、锁优化、日志优化等机制,降低分布式事务对性能的影响,支持高并发场景;
- 生态兼容性好:完美适配Spring Cloud、Dubbo、MyBatis、Sharding-JDBC等主流框架,支持MySQL、Oracle、PostgreSQL等多种数据库。
# 1.3 为什么选择Seata?(与主流方案对比)
下表通过对比清晰展现Seata的优势:
| 对比维度 | 传统2PC | TCC手动实现 | 本地消息表 | Seata |
|---|---|---|---|---|
| 侵入性 | 低(数据库层面) | 高(业务代码侵入) | 中(需额外消息表) | 低(AT模式无侵入,TCC模式半侵入) |
| 性能 | 差(锁资源占用久) | 好(无锁等待) | 中(异步补偿) | 好(AT模式接近本地事务,异步化优化) |
| 一致性保障 | 强一致性 | 最终一致性 | 最终一致性 | 支持强一致性(XA)和最终一致性(AT/TCC/SAGA) |
| 开发成本 | 中(需协调者) | 高(需实现Try/Confirm/Cancel) | 中(需设计补偿逻辑) | 低(框架封装,注解式开发) |
| 运维成本 | 高(协调者集群部署) | 中(需监控补偿逻辑) | 高(消息表运维、补偿监控) | 低(提供可视化控制台,集群部署简单) |
| 适用场景 | 强一致性要求高、低并发 | 高并发、复杂业务 | 异步通知、非核心业务 | 全场景(高并发/低并发、强一致性/最终一致性) |
总结:Seata的核心优势在于“全场景适配”“低开发成本”“高性能”,既能满足简单场景的无侵入式开发(AT模式),也能适配复杂业务的定制化需求(TCC模式),是微服务架构下分布式事务的首选方案。
# 二、架构设计:Seata的核心组件与交互流程
Seata采用“三组件架构”(TC、TM、RM),通过统一的事务协调机制,实现跨服务的事务管理。其架构设计遵循“解耦、可扩展”原则,核心分为客户端和服务端两部分。
# 2.1 核心组件(三组件模型)
Seata定义了三个核心角色,共同完成分布式事务的生命周期管理:
- TC(Transaction Coordinator,事务协调者):
- 核心职责:维护全局事务和分支事务的状态,协调全局事务的提交或回滚;
- 部署形式:独立的Seata Server服务,支持集群部署,确保高可用;
- 核心功能:全局事务ID生成、分支事务注册、事务状态协调、回滚指令下发。
- TM(Transaction Manager,事务管理器):
- 核心职责:发起全局事务的开启、提交或回滚请求;
- 部署形式:嵌入在业务微服务中(如订单服务),作为全局事务的发起者;
- 核心功能:向TC申请全局事务ID、触发全局提交/回滚。
- RM(Resource Manager,资源管理器):
- 核心职责:管理本地资源(如数据库连接),执行分支事务的提交或回滚,向TC汇报分支事务状态;
- 部署形式:嵌入在各个业务微服务中(如订单服务、库存服务、支付服务);
- 核心功能:注册分支事务到TC、执行本地事务、接收TC的回滚/提交指令并执行。
# 2.2 核心交互流程(全局事务生命周期)
无论哪种事务模式,Seata的全局事务生命周期都遵循以下核心流程:
- 全局事务开启:TM向TC申请开启全局事务,TC生成全局唯一的事务ID(XID),并返回给TM;
- 分支事务注册:TM在调用各个微服务时,将XID传递给RM;RM执行本地事务前,向TC注册分支事务,绑定XID与本地事务;
- 分支事务执行:RM执行本地事务(如扣减库存、创建订单),并将执行结果(成功/失败)汇报给TC;
- 全局事务协调:TC汇总所有分支事务的执行结果,若全部成功,则触发全局提交;若存在失败,则触发全局回滚;
- 分支事务提交/回滚:TC向所有RM下发提交或回滚指令,RM执行对应的操作(提交本地事务或回滚本地事务),并将结果汇报给TC;
- 全局事务结束:TC收到所有RM的操作结果后,标记全局事务状态为“完成”,全局事务结束。
核心交互流程:
TM(订单服务) → 申请 XID → TC(Seata Server) → 返回 XID
TM 携带 XID 调用 RM1(库存服务) → RM1 注册分支事务 → 执行本地事务 → 汇报结果给 TC
TM 携带 XID 调用 RM2(支付服务) → RM2 注册分支事务 → 执行本地事务 → 汇报结果给 TC
TC 汇总结果 → 下发提交 / 回滚指令 → RM1/RM2 执行 → 汇报结果 → TC 标记事务结束
2
3
4
# 2.3 核心数据模型
Seata通过以下核心数据模型维护事务状态:
- 全局事务(GlobalTransaction):由XID唯一标识,包含事务状态(开始、提交中、回滚中、已提交、已回滚)、超时时间等信息;
- 分支事务(BranchTransaction):与全局事务绑定(通过XID),由分支事务ID(Branch ID)唯一标识,包含资源ID(如数据库连接池标识)、分支事务状态等信息;
- 资源(Resource):指需要被事务管理的本地资源,如数据库、消息队列等,通过资源ID唯一标识;
- undo_log(回滚日志):AT模式的核心数据,用于记录本地事务执行前的数据快照,供回滚时恢复数据(后续详细讲解)。
# 三、核心功能深度解析:Seata四大事务模式
Seata支持AT、TCC、SAGA、XA四种事务模式,每种模式的实现原理、适用场景各不相同。本节将逐一深度解析每种模式的核心逻辑、执行流程、优缺点及实战示例。
# 3.1 AT模式:无侵入式最终一致性方案(核心推荐)
AT模式是Seata的默认模式,也是最常用的模式,其核心目标是“无侵入式实现分布式事务”,即开发者无需修改业务代码,仅需添加@GlobalTransactional注解即可。AT模式基于“两阶段提交”思想,但通过“回滚日志+异步化”优化,解决了传统2PC的性能问题。
# 3.1.1 核心原理:两阶段提交+回滚日志
AT模式将分布式事务拆分为“一阶段提交”和“二阶段提交/回滚”两个阶段,核心依赖“回滚日志(undo_log)”实现数据回滚。
# 一阶段(本地事务提交)
- 拦截SQL:RM通过数据源代理(DataSourceProxy)拦截业务SQL,解析SQL语义,获取操作的表、字段、条件等信息;
- 生成前置镜像:执行SQL前,查询数据的原始状态(如扣减库存前的库存数量),生成前置镜像(Before Image);
- 执行SQL:执行业务SQL(如扣减库存),更新本地数据;
- 生成后置镜像:执行SQL后,查询数据的新状态(如扣减后的库存数量),生成后置镜像(After Image);
- 写入回滚日志:将前置镜像、后置镜像、XID、Branch ID等信息写入undo_log表;
- 提交本地事务:将业务SQL的执行结果和undo_log的写入操作一同提交到本地数据库;
- 释放锁资源:一阶段提交后立即释放数据库锁,提升性能;
- 汇报状态:RM向TC汇报分支事务执行结果(成功/失败)。
# 二阶段:根据全局事务状态执行提交或回滚
二阶段的执行逻辑由TC根据所有分支事务的执行结果决定:若所有分支事务均成功,则执行“二阶段提交”;若存在分支事务失败,则执行“二阶段回滚”。
# 情况1:二阶段提交(所有分支成功)
- TC向所有RM下发“提交”指令;
- RM收到指令后,删除对应的undo_log记录(回滚日志已无需使用);
- RM向TC汇报提交结果;
- TC标记全局事务为“已提交”,事务结束。
核心特点:二阶段提交是轻量级操作,仅删除undo_log,无锁资源占用。
# 情况2:二阶段回滚(存在分支失败)
- TC向所有RM下发“回滚”指令;
- RM收到指令后,查询对应的undo_log记录,获取前置镜像;
- 根据前置镜像执行回滚SQL,将数据恢复到执行前的状态(如将库存数量恢复为扣减前的值);
- 删除undo_log记录;
- RM向TC汇报回滚结果;
- TC标记全局事务为“已回滚”,事务结束。
# 3.1.2 前置条件与约束
- 数据库支持:需支持事务和行级锁(如MySQL、Oracle、PostgreSQL);
- SQL支持:支持DML(INSERT/UPDATE/DELETE)语句,不支持DDL语句;
- 隔离级别:数据库隔离级别需设置为READ COMMITTED(默认级别,避免脏读);
- 主键约束:操作的表必须有主键(用于生成镜像时定位数据)。
# 3.1.3 实战示例:订单-库存-支付分布式事务
以“创建订单→扣减库存→扣减余额”为例,演示AT模式的使用。
# 步骤1:环境准备(数据库)
- 新建3个数据库(order_db、stock_db、account_db);
- 每个数据库创建对应的业务表和undo_log表(undo_log表是AT模式必需的):
-- 1. order_db:订单表
CREATE TABLE `order_tbl` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` varchar(50) NOT NULL,
`commodity_code` varchar(50) NOT NULL,
`count` int(11) NOT NULL DEFAULT 0,
`money` decimal(10,0) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 2. stock_db:库存表
CREATE TABLE `stock_tbl` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(50) NOT NULL,
`count` int(11) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_commodity_code` (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 3. account_db:账户表
CREATE TABLE `account_tbl` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` varchar(50) NOT NULL,
`money` decimal(10,0) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 4. 每个数据库都需创建undo_log表(Seata AT模式必需)
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 DEFAULT CHARSET=utf8;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 步骤2:服务端配置(Seata Server)
- 下载Seata Server:https://github.com/seata/seata/releases,选择稳定版本(如1.6.1);
- 修改配置文件(conf/application.yml):
server:
port: 8091 # Seata Server端口
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
seata:
config:
type: nacos # 配置中心类型(使用Nacos存储配置)
nacos:
server-addr: 127.0.0.1:8848 # Nacos地址
namespace: dev # 命名空间
group: SEATA_GROUP # 配置分组
data-id: seata-server.yaml # 配置Data ID
registry:
type: nacos # 注册中心类型(使用Nacos注册Seata Server)
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
namespace: dev
group: SEATA_GROUP
store:
mode: db # 存储模式(db:数据库存储,file:文件存储,生产环境推荐db)
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: 100
global-table: global_table # 全局事务表
branch-table: branch_table # 分支事务表
lock-table: lock_table # 锁表
distributed-lock-table: distributed_lock # 分布式锁表
query-limit: 100
security:
secretKey: SeataSecretKey0123456789
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
- 初始化Seata Server数据库:执行Seata提供的SQL脚本(conf/db_store.sql),创建global_table、branch_table、lock_table等表;
- 启动Seata Server:执行bin/startup.sh(Linux)或bin/startup.cmd(Windows)。
# 步骤3:客户端配置(微服务)
创建3个微服务:order-service(订单服务)、stock-service(库存服务)、account-service(账户服务),均引入Seata依赖。
# 1. 引入依赖(pom.xml)
### 父工程依赖(统一版本管理)
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2022.0.0.0-RC2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
### 各微服务依赖
<dependencies>
<!-- Spring Cloud Alibaba Nacos Discovery(服务注册发现) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Spring Cloud Alibaba Seata(分布式事务) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<!-- MyBatis-Plus(ORM框架) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 2. 配置文件(bootstrap.yaml)
spring:
application:
name: order-service # 服务名(stock-service/account-service同理修改)
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # Nacos地址
namespace: dev
group: SEATA_GROUP
alibaba:
seata:
tx-service-group: order_tx_group # 事务分组(所有参与分布式事务的服务需一致)
# 数据源配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/order_db?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# Seata配置(通过Nacos获取,也可本地配置)
seata:
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: dev
group: SEATA_GROUP
application: seata-server
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: dev
group: SEATA_GROUP
data-id: seata-client.yaml
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 3. 数据源代理配置(关键:Seata需要代理数据源才能拦截SQL)
@Configuration
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return new DruidDataSource(); // 或其他数据源(如HikariCP)
}
// 配置Seata数据源代理
@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
// 替换MyBatis的SqlSessionFactory,使用Seata的数据源代理
@Bean
public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
// 其他MyBatis配置(如mapperLocations)
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/*.xml"));
return sqlSessionFactoryBean.getObject();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 4. 业务代码实现
(1)订单服务(TM,全局事务发起者):
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockFeignClient stockFeignClient; // 调用库存服务的Feign客户端
@Autowired
private AccountFeignClient accountFeignClient; // 调用账户服务的Feign客户端
// 全局事务注解(TM发起全局事务)
@GlobalTransactional(rollbackFor = Exception.class)
public void createOrder(String userId, String commodityCode, int count) {
// 1. 计算订单金额(假设商品单价为10元)
BigDecimal money = new BigDecimal(count).multiply(new BigDecimal(10));
// 2. 创建订单(本地事务)
Order order = new Order();
order.setUserId(userId);
order.setCommodityCode(commodityCode);
order.setCount(count);
order.setMoney(money);
orderMapper.insert(order);
// 3. 调用库存服务扣减库存(分支事务1)
stockFeignClient.deductStock(commodityCode, count);
// 4. 调用账户服务扣减余额(分支事务2)
accountFeignClient.deductMoney(userId, money);
// 模拟异常(测试回滚)
// throw new RuntimeException("订单创建失败,触发回滚");
}
}
// Feign客户端
@FeignClient(name = "stock-service")
public interface StockFeignClient {
@PostMapping("/stock/deduct")
String deductStock(@RequestParam("commodityCode") String commodityCode, @RequestParam("count") int count);
}
@FeignClient(name = "account-service")
public interface AccountFeignClient {
@PostMapping("/account/deduct")
String deductMoney(@RequestParam("userId") String userId, @RequestParam("money") BigDecimal money);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
(2)库存服务(RM,分支事务参与者):
@Service
public class StockService {
@Autowired
private StockMapper stockMapper;
// 分支事务(无需额外注解,Seata自动识别)
public void deductStock(String commodityCode, int count) {
Stock stock = stockMapper.selectOne(new QueryWrapper<Stock>().eq("commodity_code", commodityCode));
if (stock == null || stock.getCount() < count) {
throw new RuntimeException("库存不足");
}
// 扣减库存
stock.setCount(stock.getCount() - count);
stockMapper.updateById(stock);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(3)账户服务(RM,分支事务参与者):
@Service
public class AccountService {
@Autowired
private AccountMapper accountMapper;
// 分支事务(无需额外注解,Seata自动识别)
public void deductMoney(String userId, BigDecimal money) {
Account account = accountMapper.selectOne(new QueryWrapper<Account>().eq("user_id", userId));
if (account == null || account.getMoney().compareTo(money) < 0) {
throw new RuntimeException("余额不足");
}
// 扣减余额
account.setMoney(account.getMoney().subtract(money));
accountMapper.updateById(account);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 步骤4:测试验证
- 正常场景:调用订单服务的createOrder方法,订单创建、库存扣减、余额扣减均成功,undo_log记录被删除;
- 异常场景:在createOrder方法中手动抛出异常,全局事务回滚,库存和余额恢复到原始状态,undo_log记录被删除。
# 3.1.4 优缺点总结
- 优点:无侵入式开发,无需修改业务代码;性能优异,一阶段提交后释放锁;最终一致性保障;
- 缺点:依赖数据库支持;不支持DDL语句;仅支持关系型数据库。
# 3.2 TCC模式:侵入式高性能方案(复杂业务适配)
TCC模式(Try-Confirm-Cancel)是一种侵入式的分布式事务方案,核心思想是“将业务逻辑拆分为Try、Confirm、Cancel三个阶段”,由开发者手动实现这三个阶段的逻辑,适用于AT模式无法覆盖的复杂业务场景(如非关系型数据库、无主键表、复杂业务逻辑)。
# 3.2.1 核心原理:三阶段业务拆分
- Try阶段:
- 核心目标:检查业务资源是否充足,预留业务资源(如锁定库存、冻结余额);
- 关键要求:幂等性(多次调用结果一致)、可补偿(预留的资源可释放)。
- Confirm阶段:
- 核心目标:确认执行业务操作,释放Try阶段预留的资源(如将锁定的库存扣减、冻结的余额扣减);
- 触发条件:所有分支事务的Try阶段均成功;
- 关键要求:幂等性、非空补偿(必须执行成功)。
- Cancel阶段:
- 核心目标:取消执行业务操作,回滚Try阶段预留的资源(如释放锁定的库存、解冻冻结的余额);
- 触发条件:存在分支事务的Try阶段失败;
- 关键要求:幂等性、可补偿(必须能回滚预留资源)。
# 3.2.2 执行流程
- TM向TC申请开启全局事务,获取XID;
- TM携带XID调用各RM的Try方法,执行资源检查与预留;
- 各RM执行Try方法,向TC汇报结果;
- TC汇总结果:
- 所有Try成功:TC向各RM下发Confirm指令,执行确认操作;
- 存在Try失败:TC向各RM下发Cancel指令,执行取消操作;
- 各RM执行Confirm/Cancel方法,向TC汇报结果;
- TC标记全局事务状态,事务结束。
# 3.2.3 实战示例:库存扣减TCC实现
以库存扣减为例,演示TCC模式的实现(需在库存表中添加“冻结库存”字段)。
# 1. 库存表修改(添加冻结库存字段)
ALTER TABLE `stock_tbl` ADD COLUMN `frozen_count` int(11) NOT NULL DEFAULT 0 COMMENT '冻结库存';
# 2. TCC接口定义(使用@Tcc注解)
@Service
public class StockTccService {
@Autowired
private StockMapper stockMapper;
// Try阶段:锁定库存(预留资源)
@Tcc(confirmMethod = "confirmDeductStock", cancelMethod = "cancelDeductStock")
public boolean tryDeductStock(String commodityCode, int count, BusinessActionContext context) {
// 1. 获取库存信息
Stock stock = stockMapper.selectOne(new QueryWrapper<Stock>().eq("commodity_code", commodityCode));
if (stock == null || stock.getCount() < count) {
throw new RuntimeException("库存不足,Try阶段失败");
}
// 2. 冻结库存(预留资源)
stock.setFrozenCount(stock.getFrozenCount() + count);
stockMapper.updateById(stock);
return true;
}
// Confirm阶段:确认扣减库存(释放预留资源)
public boolean confirmDeductStock(BusinessActionContext context) {
// 获取参数
String commodityCode = (String) context.getActionContext("commodityCode");
int count = (int) context.getActionContext("count");
// 扣减实际库存,释放冻结库存
Stock stock = stockMapper.selectOne(new QueryWrapper<Stock>().eq("commodity_code", commodityCode));
stock.setCount(stock.getCount() - count);
stock.setFrozenCount(stock.getFrozenCount() - count);
stockMapper.updateById(stock);
return true;
}
// Cancel阶段:取消扣减库存(回滚预留资源)
public boolean cancelDeductStock(BusinessActionContext context) {
// 获取参数
String commodityCode = (String) context.getActionContext("commodityCode");
int count = (int) context.getActionContext("count");
// 释放冻结库存
Stock stock = stockMapper.selectOne(new QueryWrapper<Stock>().eq("commodity_code", commodityCode));
stock.setFrozenCount(stock.getFrozenCount() - count);
stockMapper.updateById(stock);
return true;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 3. 全局事务发起(TM)
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockTccService stockTccService;
@Autowired
private AccountTccService accountTccService;
@GlobalTransactional(rollbackFor = Exception.class)
public void createOrder(String userId, String commodityCode, int count) {
// 1. 创建订单(本地事务)
BigDecimal money = new BigDecimal(count).multiply(new BigDecimal(10));
Order order = new Order();
order.setUserId(userId);
order.setCommodityCode(commodityCode);
order.setCount(count);
order.setMoney(money);
orderMapper.insert(order);
// 2. 调用库存TCC的Try方法
BusinessActionContext stockContext = new BusinessActionContext();
stockContext.setActionContext("commodityCode", commodityCode);
stockContext.setActionContext("count", count);
stockTccService.tryDeductStock(commodityCode, count, stockContext);
// 3. 调用账户TCC的Try方法(同理实现)
BusinessActionContext accountContext = new BusinessActionContext();
accountContext.setActionContext("userId", userId);
accountContext.setActionContext("money", money);
accountTccService.tryDeductMoney(userId, money, accountContext);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 3.2.4 优缺点总结
- 优点:高性能(无锁等待,Try阶段仅预留资源);适配复杂业务场景(非关系型数据库、无主键表);一致性保障强;
- 缺点:侵入式开发,需手动实现Try/Confirm/Cancel;开发成本高;需处理幂等性和补偿逻辑。
# 3.3 SAGA模式:长事务解决方案(状态机驱动)
SAGA模式适用于“长事务”场景(如跨多个服务的复杂流程,执行时间长),核心思想是“将分布式事务拆分为多个本地事务,每个本地事务执行后提交,若后续事务失败,则通过补偿事务回滚前面的本地事务”。Seata的SAGA模式支持两种实现方式:注解式(简单场景)和状态机式(复杂场景)。
# 3.3.1 核心原理:正向事务+补偿事务
- 正向事务(Forward Transaction):将分布式事务拆分为多个本地事务(T1、T2、T3...Tn),依次执行,每个本地事务执行后立即提交;
- 补偿事务(Compensation Transaction):为每个正向事务定义对应的补偿事务(C1、C2、C3...Cn),用于回滚正向事务的执行结果;
- 执行逻辑:
- 正常场景:T1→T2→T3→...→Tn,所有正向事务执行成功,全局事务结束;
- 异常场景:若Tk执行失败,则触发补偿流程:Ck-1→Ck-2→...→C1,回滚前面所有已执行的正向事务。
# 3.3.2 实战示例:注解式SAGA实现
@Service
public class OrderSagaService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockFeignClient stockFeignClient;
@Autowired
private AccountFeignClient accountFeignClient;
// 全局事务注解(指定SAGA模式)
@GlobalTransactional(rollbackFor = Exception.class, mode = GlobalTransactionMode.SAGA)
public void createOrder(String userId, String commodityCode, int count) {
// 1. 正向事务T1:创建订单
BigDecimal money = new BigDecimal(count).multiply(new BigDecimal(10));
Order order = new Order();
order.setUserId(userId);
order.setCommodityCode(commodityCode);
order.setCount(count);
order.setMoney(money);
orderMapper.insert(order);
// 2. 正向事务T2:扣减库存(指定补偿方法)
try {
stockFeignClient.deductStock(commodityCode, count);
} catch (Exception e) {
// 触发补偿事务C1:删除订单
orderMapper.deleteById(order.getId());
throw new RuntimeException("库存扣减失败,触发SAGA补偿");
}
// 3. 正向事务T3:扣减余额(指定补偿方法)
try {
accountFeignClient.deductMoney(userId, money);
} catch (Exception e) {
// 触发补偿事务C2(回滚库存)和C1(删除订单)
stockFeignClient.compensateStock(commodityCode, count);
orderMapper.deleteById(order.getId());
throw new RuntimeException("余额扣减失败,触发SAGA补偿");
}
}
}
// 库存服务:补偿方法
@Service
public class StockService {
@Autowired
private StockMapper stockMapper;
// 正向方法:扣减库存
public void deductStock(String commodityCode, int count) {
Stock stock = stockMapper.selectOne(new QueryWrapper<Stock>().eq("commodity_code", commodityCode));
if (stock == null || stock.getCount() < count) {
throw new RuntimeException("库存不足");
}
stock.setCount(stock.getCount() - count);
stockMapper.updateById(stock);
}
// 补偿方法:恢复库存
public void compensateStock(String commodityCode, int count) {
Stock stock = stockMapper.selectOne(new QueryWrapper<Stock>().eq("commodity_code", commodityCode));
stock.setCount(stock.getCount() + count);
stockMapper.updateById(stock);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# 3.3.3 优缺点总结
- 优点:适用于长事务场景;无锁资源占用;支持非关系型数据库;
- 缺点:一致性保障弱(最终一致性);补偿逻辑复杂;需处理幂等性和并发问题。
# 3.4 XA模式:强一致性方案(兼容传统2PC)
XA模式是基于传统2PC(两阶段提交)的分布式事务方案,核心依赖数据库的XA协议(如MySQL的XA事务),实现强一致性。Seata的XA模式封装了传统2PC的细节,降低了开发成本。
# 3.4.1 核心原理
- 一阶段(准备阶段):RM执行本地事务,但不提交,仅将事务状态设置为“准备提交”,并持有数据库锁;
- 二阶段(提交/回滚阶段):
- 所有RM准备成功:TC下发提交指令,所有RM提交本地事务,释放锁;
- 存在RM准备失败:TC下发回滚指令,所有RM回滚本地事务,释放锁。
# 3.4.2 实战配置(客户端)
只需在AT模式基础上修改事务模式为XA:
@GlobalTransactional(rollbackFor = Exception.class, mode = GlobalTransactionMode.XA)
public void createOrder(String userId, String commodityCode, int count) {
// 业务逻辑与AT模式一致,无需修改
}
2
3
4
# 3.4.3 优缺点总结
- 优点:强一致性保障;兼容传统2PC;无需回滚日志;
- 缺点:性能差(锁资源占用久);依赖数据库XA协议支持;高并发场景下不适用。
# 四、底层原理:Seata核心机制深度解析
# 4.1 AT模式核心:undo_log与数据回滚
AT模式的核心是undo_log表,其存储结构包含以下关键字段:
- xid:全局事务ID;
- branch_id:分支事务ID;
- rollback_info:回滚信息(序列化的前置镜像、后置镜像、SQL类型等);
- log_status:日志状态(0:正常,1:已删除)。
回滚逻辑:
- 反序列化rollback_info,获取前置镜像;
- 根据前置镜像生成回滚SQL(如UPDATE stock_tbl SET count = 100 WHERE commodity_code = 'C001' AND count = 99);
- 执行回滚SQL,恢复数据到原始状态;
- 删除undo_log记录。
# 4.2 分布式锁机制
Seata通过分布式锁保证并发场景下的数据一致性,核心逻辑:
- AT模式:一阶段提交时通过数据库行级锁保证并发安全,二阶段无锁;
- TCC模式:Try阶段通过业务逻辑锁定资源(如冻结库存),避免并发修改;
- XA模式:一阶段持有数据库锁,二阶段释放。
# 4.3 事务分组与配置中心
Seata的“事务分组(tx-service-group)”是核心配置,用于关联客户端与服务端:
- 客户端配置tx-service-group(如order_tx_group);
- 通过配置中心(如Nacos)配置分组与Seata Server集群的映射关系:
# Nacos中配置(data-id: service.vgroupMapping.order_tx_group)
service.vgroupMapping.order_tx_group=default
service.default.grouplist=127.0.0.1:8091
2
3
# 五、企业级部署:Seata集群配置
单机部署仅适用于开发测试环境,生产环境需采用集群部署,确保高可用。
# 5.1 集群部署准备
- 环境要求:3个及以上Seata Server节点,MySQL主从集群(存储事务数据);
- 数据库初始化:所有Seata Server节点连接同一个MySQL集群,执行db_store.sql脚本。
# 5.2 集群配置步骤
- 修改cluster.conf文件(conf/cluster.conf):
192.168.1.101:8091
192.168.1.102:8091
192.168.1.103:8091
2
3
- 修改application.yml:所有节点配置相同的数据库连接和Nacos注册信息;
- 启动集群:依次启动每个Seata Server节点;
- 客户端配置:将Seata Server地址配置为集群地址:
seata:
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848 # Nacos会自动发现Seata集群
application: seata-server
2
3
4
5
6
# 六、常见问题与解决方案
# 6.1 问题1:AT模式回滚失败
现象:全局事务回滚时,数据未恢复到原始状态。 解决方案:
- 检查undo_log表是否存在,字段是否正确;
- 确保操作的表有主键;
- 检查数据库隔离级别是否为READ COMMITTED;
- 确认SQL为DML语句,无DDL语句。
# 6.2 问题2:事务分组配置错误
现象:客户端提示“no available server to connect”。 解决方案:
- 检查tx-service-group配置是否正确;
- 确认Nacos中已配置service.vgroupMapping.${tx-service-group};
- 检查Seata Server集群地址配置是否正确。
# 6.3 问题3:数据源代理未配置
现象:AT模式不生效,undo_log无记录。 解决方案:
- 确保配置了DataSourceProxy,替换了MyBatis的SqlSessionFactory;
- 检查数据源代理是否覆盖了所有业务数据源。
# 6.4 问题4:TCC模式幂等性问题
现象:Confirm/Cancel方法多次执行导致数据异常。 解决方案:
- 基于XID+Branch ID实现幂等性;
- 在业务表中添加“事务状态”字段,标记是否已执行Confirm/Cancel。
# 七、总结:Seata最佳实践
# 7.1 模式选择建议
- 简单业务场景:优先选择AT模式(无侵入、高性能);
- 复杂业务/非关系型数据库:选择TCC模式;
- 长事务场景:选择SAGA模式;
- 强一致性要求场景:选择XA模式(低并发场景)。
# 7.2 性能优化建议
- 尽量缩小事务范围,避免分布式事务包含过多分支;
- AT模式下,使用批量SQL减少undo_log写入次数;
- 生产环境使用数据库存储模式(db),避免文件存储的性能瓶颈;
- 集群部署Seata Server,避免单点故障。