Tavio's blog Tavio's blog
首页
  • JVM底层原理
  • 邪恶多线程
  • MyBatis底层原理
  • Spring底层原理
  • MySQL的优化之路
  • ClickHouse的高性能
  • Redis的快速查询
  • RabbitMQ的生产
  • Kafka的高吞吐量
  • ES的入门到入坑
  • MySQL自增ID主键空洞
  • 前端实现长整型排序
  • MySQL无感换表
  • Redis延时双删
  • 高并发秒杀优惠卷
  • AOP无侵入式告警
  • 长短链接跳转
  • 订单超时取消
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Tavio Zhang

努力学习的小码喽
首页
  • JVM底层原理
  • 邪恶多线程
  • MyBatis底层原理
  • Spring底层原理
  • MySQL的优化之路
  • ClickHouse的高性能
  • Redis的快速查询
  • RabbitMQ的生产
  • Kafka的高吞吐量
  • ES的入门到入坑
  • MySQL自增ID主键空洞
  • 前端实现长整型排序
  • MySQL无感换表
  • Redis延时双删
  • 高并发秒杀优惠卷
  • AOP无侵入式告警
  • 长短链接跳转
  • 订单超时取消
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Spring Boot 自动配置
  • Spring Boot Bean 生命周期
  • Spring事务传播机制
  • Spring Cloud Nacos 深度解析
  • Spring Cloud OpenFeign 深度解析
  • Spring Cloud Gateway 底层原理
  • Spring Cloud Seata 深度解析
    • 一、核心认知:Seata是什么?为什么选择它?
      • 1.1 分布式事务的核心痛点与传统方案局限
      • 1.2 Seata的定义与核心定位
      • 1.3 为什么选择Seata?(与主流方案对比)
    • 二、架构设计:Seata的核心组件与交互流程
      • 2.1 核心组件(三组件模型)
      • 2.2 核心交互流程(全局事务生命周期)
      • 2.3 核心数据模型
    • 三、核心功能深度解析:Seata四大事务模式
      • 3.1 AT模式:无侵入式最终一致性方案(核心推荐)
      • 3.1.1 核心原理:两阶段提交+回滚日志
      • 一阶段(本地事务提交)
      • 二阶段:根据全局事务状态执行提交或回滚
      • 情况1:二阶段提交(所有分支成功)
      • 情况2:二阶段回滚(存在分支失败)
      • 3.1.2 前置条件与约束
      • 3.1.3 实战示例:订单-库存-支付分布式事务
      • 步骤1:环境准备(数据库)
      • 步骤2:服务端配置(Seata Server)
      • 步骤3:客户端配置(微服务)
      • 1. 引入依赖(pom.xml)
      • 2. 配置文件(bootstrap.yaml)
      • 3. 数据源代理配置(关键:Seata需要代理数据源才能拦截SQL)
      • 4. 业务代码实现
      • 步骤4:测试验证
      • 3.1.4 优缺点总结
      • 3.2 TCC模式:侵入式高性能方案(复杂业务适配)
      • 3.2.1 核心原理:三阶段业务拆分
      • 3.2.2 执行流程
      • 3.2.3 实战示例:库存扣减TCC实现
      • 1. 库存表修改(添加冻结库存字段)
      • 2. TCC接口定义(使用@Tcc注解)
      • 3. 全局事务发起(TM)
      • 3.2.4 优缺点总结
      • 3.3 SAGA模式:长事务解决方案(状态机驱动)
      • 3.3.1 核心原理:正向事务+补偿事务
      • 3.3.2 实战示例:注解式SAGA实现
      • 3.3.3 优缺点总结
      • 3.4 XA模式:强一致性方案(兼容传统2PC)
      • 3.4.1 核心原理
      • 3.4.2 实战配置(客户端)
      • 3.4.3 优缺点总结
    • 四、底层原理:Seata核心机制深度解析
      • 4.1 AT模式核心:undo_log与数据回滚
      • 4.2 分布式锁机制
      • 4.3 事务分组与配置中心
    • 五、企业级部署:Seata集群配置
      • 5.1 集群部署准备
      • 5.2 集群配置步骤
    • 六、常见问题与解决方案
      • 6.1 问题1:AT模式回滚失败
      • 6.2 问题2:事务分组配置错误
      • 6.3 问题3:数据源代理未配置
      • 6.4 问题4:TCC模式幂等性问题
    • 七、总结:Seata最佳实践
      • 7.1 模式选择建议
      • 7.2 性能优化建议
  • Spring Cloud Sentinel 深度解析
  • 《Spring》笔记
Tavio
2024-09-12
目录

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定义了三个核心角色,共同完成分布式事务的生命周期管理:

  1. TC(Transaction Coordinator,事务协调者):
    • 核心职责:维护全局事务和分支事务的状态,协调全局事务的提交或回滚;
    • 部署形式:独立的Seata Server服务,支持集群部署,确保高可用;
    • 核心功能:全局事务ID生成、分支事务注册、事务状态协调、回滚指令下发。
  2. TM(Transaction Manager,事务管理器):
    • 核心职责:发起全局事务的开启、提交或回滚请求;
    • 部署形式:嵌入在业务微服务中(如订单服务),作为全局事务的发起者;
    • 核心功能:向TC申请全局事务ID、触发全局提交/回滚。
  3. RM(Resource Manager,资源管理器):
    • 核心职责:管理本地资源(如数据库连接),执行分支事务的提交或回滚,向TC汇报分支事务状态;
    • 部署形式:嵌入在各个业务微服务中(如订单服务、库存服务、支付服务);
    • 核心功能:注册分支事务到TC、执行本地事务、接收TC的回滚/提交指令并执行。

# 2.2 核心交互流程(全局事务生命周期)

无论哪种事务模式,Seata的全局事务生命周期都遵循以下核心流程:

  1. 全局事务开启:TM向TC申请开启全局事务,TC生成全局唯一的事务ID(XID),并返回给TM;
  2. 分支事务注册:TM在调用各个微服务时,将XID传递给RM;RM执行本地事务前,向TC注册分支事务,绑定XID与本地事务;
  3. 分支事务执行:RM执行本地事务(如扣减库存、创建订单),并将执行结果(成功/失败)汇报给TC;
  4. 全局事务协调:TC汇总所有分支事务的执行结果,若全部成功,则触发全局提交;若存在失败,则触发全局回滚;
  5. 分支事务提交/回滚:TC向所有RM下发提交或回滚指令,RM执行对应的操作(提交本地事务或回滚本地事务),并将结果汇报给TC;
  6. 全局事务结束:TC收到所有RM的操作结果后,标记全局事务状态为“完成”,全局事务结束。

核心交互流程:

TM(订单服务) → 申请 XID → TC(Seata Server) → 返回 XID
TM 携带 XID 调用 RM1(库存服务) → RM1 注册分支事务 → 执行本地事务 → 汇报结果给 TC
TM 携带 XID 调用 RM2(支付服务) → RM2 注册分支事务 → 执行本地事务 → 汇报结果给 TC
TC 汇总结果 → 下发提交 / 回滚指令 → RM1/RM2 执行 → 汇报结果 → TC 标记事务结束
1
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)”实现数据回滚。

# 一阶段(本地事务提交)
  1. 拦截SQL:RM通过数据源代理(DataSourceProxy)拦截业务SQL,解析SQL语义,获取操作的表、字段、条件等信息;
  2. 生成前置镜像:执行SQL前,查询数据的原始状态(如扣减库存前的库存数量),生成前置镜像(Before Image);
  3. 执行SQL:执行业务SQL(如扣减库存),更新本地数据;
  4. 生成后置镜像:执行SQL后,查询数据的新状态(如扣减后的库存数量),生成后置镜像(After Image);
  5. 写入回滚日志:将前置镜像、后置镜像、XID、Branch ID等信息写入undo_log表;
  6. 提交本地事务:将业务SQL的执行结果和undo_log的写入操作一同提交到本地数据库;
  7. 释放锁资源:一阶段提交后立即释放数据库锁,提升性能;
  8. 汇报状态:RM向TC汇报分支事务执行结果(成功/失败)。
# 二阶段:根据全局事务状态执行提交或回滚

二阶段的执行逻辑由TC根据所有分支事务的执行结果决定:若所有分支事务均成功,则执行“二阶段提交”;若存在分支事务失败,则执行“二阶段回滚”。

# 情况1:二阶段提交(所有分支成功)
  1. TC向所有RM下发“提交”指令;
  2. RM收到指令后,删除对应的undo_log记录(回滚日志已无需使用);
  3. RM向TC汇报提交结果;
  4. TC标记全局事务为“已提交”,事务结束。

核心特点:二阶段提交是轻量级操作,仅删除undo_log,无锁资源占用。

# 情况2:二阶段回滚(存在分支失败)
  1. TC向所有RM下发“回滚”指令;
  2. RM收到指令后,查询对应的undo_log记录,获取前置镜像;
  3. 根据前置镜像执行回滚SQL,将数据恢复到执行前的状态(如将库存数量恢复为扣减前的值);
  4. 删除undo_log记录;
  5. RM向TC汇报回滚结果;
  6. TC标记全局事务为“已回滚”,事务结束。

# 3.1.2 前置条件与约束

  • 数据库支持:需支持事务和行级锁(如MySQL、Oracle、PostgreSQL);
  • SQL支持:支持DML(INSERT/UPDATE/DELETE)语句,不支持DDL语句;
  • 隔离级别:数据库隔离级别需设置为READ COMMITTED(默认级别,避免脏读);
  • 主键约束:操作的表必须有主键(用于生成镜像时定位数据)。

# 3.1.3 实战示例:订单-库存-支付分布式事务

以“创建订单→扣减库存→扣减余额”为例,演示AT模式的使用。

# 步骤1:环境准备(数据库)
  1. 新建3个数据库(order_db、stock_db、account_db);
  2. 每个数据库创建对应的业务表和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;
1
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)
  1. 下载Seata Server:https://github.com/seata/seata/releases,选择稳定版本(如1.6.1);
  2. 修改配置文件(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
1
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
  1. 初始化Seata Server数据库:执行Seata提供的SQL脚本(conf/db_store.sql),创建global_table、branch_table、lock_table等表;
  2. 启动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>
1
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
1
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();
    }
}
1
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);
}
1
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);
    }
}
1
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);
    }
}
1
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 核心原理:三阶段业务拆分

  1. Try阶段:
    • 核心目标:检查业务资源是否充足,预留业务资源(如锁定库存、冻结余额);
    • 关键要求:幂等性(多次调用结果一致)、可补偿(预留的资源可释放)。
  2. Confirm阶段:
    • 核心目标:确认执行业务操作,释放Try阶段预留的资源(如将锁定的库存扣减、冻结的余额扣减);
    • 触发条件:所有分支事务的Try阶段均成功;
    • 关键要求:幂等性、非空补偿(必须执行成功)。
  3. Cancel阶段:
    • 核心目标:取消执行业务操作,回滚Try阶段预留的资源(如释放锁定的库存、解冻冻结的余额);
    • 触发条件:存在分支事务的Try阶段失败;
    • 关键要求:幂等性、可补偿(必须能回滚预留资源)。

# 3.2.2 执行流程

  1. TM向TC申请开启全局事务,获取XID;
  2. TM携带XID调用各RM的Try方法,执行资源检查与预留;
  3. 各RM执行Try方法,向TC汇报结果;
  4. TC汇总结果:
    • 所有Try成功:TC向各RM下发Confirm指令,执行确认操作;
    • 存在Try失败:TC向各RM下发Cancel指令,执行取消操作;
  5. 各RM执行Confirm/Cancel方法,向TC汇报结果;
  6. TC标记全局事务状态,事务结束。

# 3.2.3 实战示例:库存扣减TCC实现

以库存扣减为例,演示TCC模式的实现(需在库存表中添加“冻结库存”字段)。

# 1. 库存表修改(添加冻结库存字段)
ALTER TABLE `stock_tbl` ADD COLUMN `frozen_count` int(11) NOT NULL DEFAULT 0 COMMENT '冻结库存';
1
# 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;
    }
}
1
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);
    }
}
1
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 核心原理:正向事务+补偿事务

  1. 正向事务(Forward Transaction):将分布式事务拆分为多个本地事务(T1、T2、T3...Tn),依次执行,每个本地事务执行后立即提交;
  2. 补偿事务(Compensation Transaction):为每个正向事务定义对应的补偿事务(C1、C2、C3...Cn),用于回滚正向事务的执行结果;
  3. 执行逻辑:
    • 正常场景: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);
    }
}
1
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 核心原理

  1. 一阶段(准备阶段):RM执行本地事务,但不提交,仅将事务状态设置为“准备提交”,并持有数据库锁;
  2. 二阶段(提交/回滚阶段):
    • 所有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模式一致,无需修改
}
1
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:已删除)。

回滚逻辑:

  1. 反序列化rollback_info,获取前置镜像;
  2. 根据前置镜像生成回滚SQL(如UPDATE stock_tbl SET count = 100 WHERE commodity_code = 'C001' AND count = 99);
  3. 执行回滚SQL,恢复数据到原始状态;
  4. 删除undo_log记录。

# 4.2 分布式锁机制

Seata通过分布式锁保证并发场景下的数据一致性,核心逻辑:

  1. AT模式:一阶段提交时通过数据库行级锁保证并发安全,二阶段无锁;
  2. TCC模式:Try阶段通过业务逻辑锁定资源(如冻结库存),避免并发修改;
  3. XA模式:一阶段持有数据库锁,二阶段释放。

# 4.3 事务分组与配置中心

Seata的“事务分组(tx-service-group)”是核心配置,用于关联客户端与服务端:

  1. 客户端配置tx-service-group(如order_tx_group);
  2. 通过配置中心(如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
1
2
3

# 五、企业级部署:Seata集群配置

单机部署仅适用于开发测试环境,生产环境需采用集群部署,确保高可用。

# 5.1 集群部署准备

  1. 环境要求:3个及以上Seata Server节点,MySQL主从集群(存储事务数据);
  2. 数据库初始化:所有Seata Server节点连接同一个MySQL集群,执行db_store.sql脚本。

# 5.2 集群配置步骤

  1. 修改cluster.conf文件(conf/cluster.conf):
192.168.1.101:8091
192.168.1.102:8091
192.168.1.103:8091
1
2
3
  1. 修改application.yml:所有节点配置相同的数据库连接和Nacos注册信息;
  2. 启动集群:依次启动每个Seata Server节点;
  3. 客户端配置:将Seata Server地址配置为集群地址:
seata:
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848 # Nacos会自动发现Seata集群
      application: seata-server
1
2
3
4
5
6

# 六、常见问题与解决方案

# 6.1 问题1:AT模式回滚失败

现象:全局事务回滚时,数据未恢复到原始状态。 解决方案:

  1. 检查undo_log表是否存在,字段是否正确;
  2. 确保操作的表有主键;
  3. 检查数据库隔离级别是否为READ COMMITTED;
  4. 确认SQL为DML语句,无DDL语句。

# 6.2 问题2:事务分组配置错误

现象:客户端提示“no available server to connect”。 解决方案:

  1. 检查tx-service-group配置是否正确;
  2. 确认Nacos中已配置service.vgroupMapping.${tx-service-group};
  3. 检查Seata Server集群地址配置是否正确。

# 6.3 问题3:数据源代理未配置

现象:AT模式不生效,undo_log无记录。 解决方案:

  1. 确保配置了DataSourceProxy,替换了MyBatis的SqlSessionFactory;
  2. 检查数据源代理是否覆盖了所有业务数据源。

# 6.4 问题4:TCC模式幂等性问题

现象:Confirm/Cancel方法多次执行导致数据异常。 解决方案:

  1. 基于XID+Branch ID实现幂等性;
  2. 在业务表中添加“事务状态”字段,标记是否已执行Confirm/Cancel。

# 七、总结:Seata最佳实践

# 7.1 模式选择建议

  • 简单业务场景:优先选择AT模式(无侵入、高性能);
  • 复杂业务/非关系型数据库:选择TCC模式;
  • 长事务场景:选择SAGA模式;
  • 强一致性要求场景:选择XA模式(低并发场景)。

# 7.2 性能优化建议

  1. 尽量缩小事务范围,避免分布式事务包含过多分支;
  2. AT模式下,使用批量SQL减少undo_log写入次数;
  3. 生产环境使用数据库存储模式(db),避免文件存储的性能瓶颈;
  4. 集群部署Seata Server,避免单点故障。
编辑 (opens new window)
#Spring Cloud Seata 深度解析
上次更新: 2026/01/21, 19:29:14
Spring Cloud Gateway 底层原理
Spring Cloud Sentinel 深度解析

← Spring Cloud Gateway 底层原理 Spring Cloud Sentinel 深度解析→

最近更新
01
订单超时取消
01-21
02
双 Token 登录
01-21
03
长短链接跳转
01-21
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式