Appearance
Hello Seata
1. Seata 架构
1.1. 领域模型
Seata 事务管理中有三个重要的角色:
TC(Transaction Coordinator)— 事务协调者
负责维护全局事务与分支事务的元数据(如 XID、分支状态)、驱动全局提交或回滚、管理全局锁等,是分布式事务的 “大脑”。TC 接收分支注册、状态上报,并向 RM 下发分支提交/回滚指令。
TM(Transaction Manager)— 事务管理器
由业务应用(或框架)侧调用,负责:开启/提交/回滚全局事务,在本地调用链中传播 XID(使下游分支成为全局事务的一部分)。TM 不直接操作资源的持久化细节,只定义全局事务的边界并协调提交/回滚。
RM(Resource Manager)— 资源管理器
代表具体资源(如某个微服务的数据库、消息队列等)。当本地(分支)事务执行时,RM 向 TC 注册分支(携带 XID),执行本地操作并可向 TC 上报分支结果;在 TC 发出指令时,RM 驱动本地分支 Commit 或 Rollback。
1.2. Seata 元数据
在 Seata 中,核心的事务元数据包括:
| 元数据 | 含义 | 谁负责生成/维护 |
|---|---|---|
| XID(Transaction Id) | 全局唯一事务 Id,用于标识一个分布式事务。格式一般为:ip:port:transactionId | TC 生成,在 TM 端调用 begin() 时返回 |
| Branch Id | 分支事务 Id,用于标识隶属于某个 XID 的具体资源分支 | TC 生成,在 RM 注册分支时返回 |
| Branch Type | 分支模式(AT、TCC、XA、SAGA) | RM 注册分支时指定 |
| Status / Phase | 全局或分支事务状态(Begin、Committing、Rollbacking 等) | TC 维护,TM/RM 上报更新 |
| Global Lock Key | 全局锁标识(一般为表名 + 主键) | RM 在分支注册时申请,TC 维护锁状态 |
1.3. XID 传播机制
1.3.1. RPC/HTTP Header
Seata 在各语言客户端(如 Java、Go、Python 等 SDK)中,都内置了 RootContext(或等效上下文)机制,用来存储当前线程的 XID。
当业务代码调用下游服务时,Seata 拦截 RPC 调用(如 Dubbo、Feign、Spring Cloud、gRPC 等),在请求头中自动携带 XID。
下游服务的 Seata 拦截器在收到请求时,从头中解析出 XID 并绑定到自己的线程上下文。
1.3.2. 线程上下文
Seata 的客户端 SDK 内部维护了一个 RootContext,其实就是封装了一个 ThreadLocal<String>。
当 TM 开启全局事务时,会调用 RootContext.bind(xid) 把当前全局事务 Id 绑定到当前线程。
在调用下游服务或访问资源时(比如 JDBC),Seata 的代理层(如 DataSourceProxy)会自动从 RootContext 里拿出 XID 向 TC 注册分支事务。
这保证了在同一线程上下文中数据库操作都会自动参与全局事务。
1.3.3. 跨线程/异步场景
当业务使用异步线程、消息队列、事件驱动架构时,XID 无法自动通过 ThreadLocal 传播,因此需要手动处理。手动传递示例:
Java
String xid = RootContext.getXID();
// 异步线程中手动绑定
CompletableFuture.runAsync(() -> {
RootContext.bind(xid);
try {
// 执行数据库操作(会自动注册分支)
} finally {
RootContext.unbind();
}
});或者在消息中携带 XID 字段,消费者侧在处理时再绑定。
提示
异步分支注册需注意时序问题,若全局事务已进入第二阶段(Commit / Rollback),分支注册会失败(状态非 Begin)。Seata 不内置异步框架集成,用户需结合 API 自定义。
1.4. 事务分组
事务分组(Transaction Group,也称为 tx-service-group)是 Seata 架构中的一个核心概念,用于标识和隔离分布式事务的逻辑资源。它本质上类似命名空间,允许用户根据业务需求自定义分组名称(如 "my-tx-group"),每个分组对应一组相关的全局事务实例。
1.4.1. 核心作用
路由与发现:客户端(应用)通过事务分组名称,从配置中心(如 Nacos)查询映射关系(
vgroupMapping),获取对应的 TC 集群名称,然后从注册中心(如 Nacos、Eureka)拉取实际的 TC 服务列表(grouplist)。这确保了事务请求能够高效路由到正确的 Seata Server 集群;逻辑隔离:不同分组之间的分布式事务互不干扰。例如,在多项目环境中,可以为不同微服务项目分配独立分组,避免事务冲突;
高可用支持:支持 TC 集群的负载均衡和 failover,当某个 TC 节点故障时,分组可以快速切换到备用集群。
1.4.2. 配置示例
客户端
YAMLseata: tx-service-group: my-tx-group registry: type: nacos nacos: # 与 Seata Server 注册到 Nacos 注册中心的配置一致 server-addr: localhost:8848 username: YOUR_USERNAME password: YOUR_PASSWORD namespace: '' group: DEFAULT_GROUP application: seata-server # service: # vgroup-mapping: # my-tx-group: default config: # 从配置中心读取 vgroupMapping type: nacos nacos: server-addr: localhost:8848 username: YOUR_USERNAME password: YOUR_PASSWORD namespace: '' group: SEATA_GROUP data-id: seata-client.properties配置中心
Nacos 中的 dataId:
seata-client.properties,group:SEATA_GROUP。Propertiesservice.vgroupMapping.my-tx-group=default注册中心
Seata Server 注册时使用名称空间
"",集群名称"default"和服务名称seata-server。
1.4.3. 设计原因
主是为了解决高可用、隔离和扩展性问题。
分组作为资源的 “逻辑单位”,它允许将不同业务场景(如开发/生产环境、多租户、多项目)隔离到独立分组中,避免全局事务跨组干扰。
或当某个 TC 集群故障时,只需动态更新 vgroupMapping 配置(例如从 "default" 切换到 "backup"),即可实现快速 failover,将故障范围缩小到服务级别,而不影响其他分组。
1.5. 事务模式
| 模式 | 简介 | 事务控制方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|---|
| XA 模式 | 基于数据库 XA 协议的两阶段提交 | 数据库驱动层 | 强一致性 | 性能差、数据库锁定时间长 | 支持 XA 的数据库、小流量核心交易 |
| TCC 模式 | 业务级两阶段(Try / Confirm / Cancel) | 业务层接口控制 | 性能高、强一致 | 开发侵入高 | 高价值操作(支付、转账) |
| AT 模式(默认) | 自动代理本地事务 + Undo Log | 数据源代理层 | 无侵入、易用 | 仅适合关系型数据库 | 一般业务系统 |
| SAGA 模式 | 长事务 + 补偿(异步) | 状态机/流程控制 | 支持长流程、柔性事务 | 最终一致性,复杂性高 | 业务流程长、跨系统异步 |
1.5.1. XA 模式
使用数据库的原生 XA 两阶段提交协议。由 Seata TC 充当事务协调器,与各数据库协作完成两阶段提交。
实现步骤较为简单:
对每个参与事务的微服务,修改其
application.yml配置文件:YAMLseata: data-source-proxy-mode: XA配置完成后,Seata 的 Spring Boot Starter 会自动注入一个 XA 模式的数据源代理对象(
DataSourceProxyXA)。给发起全局事务入口的方法添加
@GlobalTransactional注解:Java@PostMapping("create") @GlobalTransactional public void create(Order order) { orderService.insert(order); remoteInventoryService.deduct(order.getProductId(), order.getCount()); remoteUserService.deduct(order.getUserId(), order.getMoney()); }1
2
3
4
5
6
7
1.5.2. AT 模式(默认)
1.5.2.1. 概述
AT 模式是 Seata 创新的一种非侵入式的分布式事务解决方案,Seata 在内部做了对数据库操作的代理层,我们使用 Seata AT 模式时,实际上用的是 Seata 自带的数据源代理 DataSourceProxy,Seata 在这层代理中加入了很多逻辑,比如插入回滚 undo_log 日志,检查全局锁等。
1.5.2.2. 写隔离
一阶段本地事务提交前,需要确保先拿到全局锁。拿不到全局锁,不能提交本地事务。拿全局锁的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。
以一个示例来说明:
两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。
tx1 先开始,开启本地事务,拿到本地锁,更新操作 tx2 后开始,开启本地事务,拿到本地锁,更新操作 tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待全局锁。
tx1 二阶段全局提交,释放全局锁。tx2 拿到全局锁提交本地事务。
如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。
此时,如果 tx2 仍在等待该数据的全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的全局锁等锁超时,放弃全局锁并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。
因为整个过程全局锁在 tx1 结束前一直是被 tx1 持有的,所以不会发生脏写的问题。
1.5.2.3. 读隔离
在数据库本地事务隔离级别读已提交(Read Committed)或以上的基础上,Seata(AT 模式)的默认全局隔离级别是读未提交(Read Uncommitted)。
如果应用在特定场景下,必需要求全局的读已提交,目前 Seata 的方式是通过 SELECT ... FOR UPDATE 语句的代理。
SELECT ... FOR UPDATE 语句的执行会申请全局锁,如果全局锁被其他事务持有,则释放本地锁(回滚 SELECT ... FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 Block 住的,直到全局锁拿到,即读取的相关数据是已提交的,才返回。
出于总体性能上的考虑,Seata 目前的方案并没有对所有 SELECT 语句都进行代理,仅针对 FOR UPDATE 的 SELECT 语句。
1.5.2.4. 配置步骤
对每个参与事务的微服务,修改其
application.yml配置文件:YAMLseata: data-source-proxy-mode: AT配置完成后,Seata 的 Spring Boot Starter 会自动注入一个 AT 模式的数据源代理对象(
DataSourceProxy)。给发起全局事务入口的方法添加
@GlobalTransactional注解:Java@PostMapping("create") @GlobalTransactional public void create(Order order) { orderService.insert(order); remoteInventoryService.deduct(order.getProductId(), order.getCount()); remoteUserService.deduct(order.getUserId(), order.getMoney()); }1
2
3
4
5
6
7
1.5.3. TCC 模式
1.5.3.1. TCC 事务模型
TCC 要求每个分支事务实现以下三个阶段:
- Try:检查业务条件并预留资源;
- Confirm:正式提交业务操作,要求 Try 成功 Confirm 一定要能成功;
- Cancel:回滚并释放预留资源。
1.5.3.2. 常见问题
由于网络抖动、超时、重试等不确定因素,TCC 易引发两大典型问题:
空回滚(Empty Rollback)
定义:Try 阶段尚未执行或未完成时,Cancel 已先行触发。例如:Try 因网络延迟未到达,事务管理器(TM)因全局事务超时提前发起 Cancel;
影响:未扣减资源却执行释放逻辑,可能导致资源数据异常(如余额被错误加回);
解决方案:引入 TCC 控制表。Cancel 执行前先查询控制表,若无对应的控制表记录,则插入一条状态为 Canceled 的记录到控制表,并直接返回成功(记录空回滚,后续将用于业务悬挂判断)。
业务悬挂(Business Hanging)
定义:空回滚发生后,延迟的 Try 请求最终到达并执行。此时全局事务已回滚,但资源被预留,无法再进入 Confirm 或 Cancel,导致资源 “悬挂”;
影响:资源长期冻结(如账户资金无法解冻),影响业务可用性;
解决方案:Try 执行前先查询 TCC 控制表,若记录已存在(状态应为 Canceled)则直接返回。
1.5.3.3. 幂等性控制
为防止重复调用引发状态错乱,需确保接口幂等:
| 接口 | 幂等策略 |
|---|---|
| Confirm | 天生幂等,执行成功后可删除控制表记录。 |
| Cancel | 1. 执行前查控制表,若已存在 Cancelled 记录则直接返回成功; 2. 更新 TCC 记录状态后,需判断更新行数是否为 1,否则回滚事务(防并发)。 |
1.5.3.4. 优缺点总结
优点:
- 高性能:Try 阶段即提交本地事务(事物短),无需全局锁或快照(优于 AT 模式);
- 灵活性强:不依赖数据库事务,支持跨库、跨服务、非事务资源操作。
缺点:
- 最终一致性:资源在 Try ~ Confirm/Cancel 间为软状态;
- 侵入性强:需手动实现三阶段逻辑;
- 实现复杂:必须处理幂等、空回滚、悬挂等边缘场景。
1.5.3.5. 示例代码
TCC 控制表
SQLCREATE TABLE tcc_tbl ( xid VARCHAR(128) NOT NULL PRIMARY KEY, res LONGTEXT NULL COMMENT 'res 为 NULL 且 state = 2 的数据,表示空回滚', state INT NOT NULL DEFAULT 0 COMMENT '事务状态;0:try,1(预留):confirmed,2:canceled' );Domain Objects
Java@Data public class TccDetail { private String res; private Integer state; public static abstract class State { public static final Integer TRY = 0; public static final Integer CONFIRMED = 1; public static final Integer CANCELED = 2; } }Mapper Class
Java@Mapper public interface TccMapper { @Select("SELECT res, state FROM tcc_tbl WHERE xid = #{xid};") TccDetail getDetailById(String xid); @Insert("INSERT tcc_tbl(xid, res) VALUES (#{xid}, #{res});") void prepare(String xid, String res); @Delete("DELETE FROM tcc_tbl WHERE xid = #{xid} AND state = 0;") int commit(String xid); @Update("UPDATE tcc_tbl SET state = 2 WHERE xid = #{xid} AND state = 0;") int rollback(String xid); @Insert("INSERT tcc_tbl(xid, state) VALUES (#{xid}, 2);") void emptyRollback(String xid); }Inventory TCC Service
Java@Service @LocalTCC @RequiredArgsConstructor public class DeductInventoryTccService { private final InventoryMapper inventoryMapper; private final TccMapper tccMapper; private final SneakyObjectMapper jackson; @TwoPhaseBusinessAction(name = "deduct", commitMethod = "commit", rollbackMethod = "rollback") @Transactional public void deduct(String productName, int number) { String xid = RootContext.getXID(); // 5. 防业务悬挂 TccDetail tccDetail = tccMapper.getDetailById(xid); if (tccDetail != null) return; // 1.1. 扣减库存 int deducted = inventoryMapper.deduct(productName, number); if (deducted == 0) throw new RuntimeException("insufficient stock"); // 1.2. 冻结资源到 TCC 控制表 // // 6.1. deduct 与 rollback 并发执行时,有可能会同时通过 tccDetail 的 null 校验, // 但只有一个 xid 会插入成功,另一个方法则会引发主键(xid)冲突异常,从而 // 引发事务回滚。 // // - 如果是 deduct 回滚,则避免了业务悬挂; // - 如果是 rollback 回滚,则 TC 会发起重试,释放资源。 tccMapper.prepare(xid, jackson.writeValueAsString(new InventoryDeducting() .setProductName(productName) .setNumber(number))); } public boolean commit(BusinessActionContext context) { // 2. 正式提交(天然幂等) tccMapper.commit(context.getXid()); return true; } @Transactional public boolean rollback(BusinessActionContext context) { String xid = context.getXid(); TccDetail tccDetail = tccMapper.getDetailById(xid); if (tccDetail == null) { // 4. 空回滚(Try 尚未执行,Cancel 已先行触发) tccMapper.emptyRollback(xid); return true; } else if (Objects.equals(tccDetail.getState(), TccDetail.State.CANCELED)) { // 6.2. 确保幂等性(并发 Cancel) return true; } InventoryDeducting deducting = jackson.readValue(tccDetail.getRes(), InventoryDeducting.class); // 3.1. 恢复库存 int refunded = inventoryMapper.refund(deducting.getProductName(), deducting.getNumber()); if (refunded == 0) throw new RuntimeException("dirty data"); // 3.2. 释放资源 // // 6.3. 追加判断 rolled == 0,确保幂等性(并发 Cancel) int rolled = tccMapper.rollback(xid); if (rolled == 0) throw new RuntimeException("concurrency conflict"); 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67@LocalTCC注解到 TCC Service 实现类上;@TwoPhaseBusinessAction(name = "deduct", commitMethod = "commit", rollbackMethod = "rollback")注解到 Try 方法上,name、commitMethod、rollbackMethod分别对应 Try、Confirm、Cancel 方法名;可以通过
RootContext.getXID()静态方法,或BusinessActionContext实例的getXid方法获取 XID;可以在 Try 方法参数前添加
@BusinessActionContextParameter注解,被注解的参数将来可以在 Confirm、Cancel 方法中通过BusinessActionContext获取。
1.5.4. SAGA 模式
Saga 模式是一种长事务解决方案,它的核心理念是:把一个大事务拆分成多个本地事务,每个本地事务都有对应的补偿操作(回滚操作):
Text
正常流程:T1 → T2 → T3 → ... → Tn
补偿流程:C1 ← C2 ← C3 ← ... ← Cn- 每个 Ti 是一个本地事务;
- 每个 Ci 是对应的补偿事务;
- 如果某个环节失败,执行已完成事务的补偿操作。
优点:
- 事务参与者可以基于事件驱动实现异步调用,吞吐高;
- 一阶段直接提交事务,无锁,性能好;
- 比 TCC 简单,只需要实现正向操作和补偿操作。
缺点:
- 软状态持续时间不确定,时效性差;
- 隔离性差,中间状态对外可见,出现脏写后可能导致补偿失败。
2. 适配整合
2.1. Java
2.1.1. 引入依赖
seata-all提供 Seata 的所有功能,但不包含任何框架自动配置,需要手动集成(如手动创建
DataSourceProxy)。XML<dependency> <groupId>org.apache.seata</groupId> <artifactId>seata-all</artifactId> <version>${seata.version}</version> </dependency>seata-spring-boot-starter(内部依赖seata-all):提供基本的 Spring Boot 自动配置(如数据源代理),支持 YAML/Properties 配置。无内置 XID 传播机制,需要手动处理微服务间事务上下文。
可禁用自动代理,在
application.yml中配置seata.enableAutoDataSourceProxy=false。XML<dependency> <groupId>org.apache.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>${seata.version}</version> </dependency>spring-cloud-starter-alibaba-seata(内部依赖seata-spring-boot-starter):自动集成 Spring Cloud 组件(如 Nacos、Feign、RestTemplate),实现 XID(全局事务 Id)在微服务调用中的自动传播。
需要引入 Spring Cloud Alibaba 的 BOM
spring-cloud-alibaba-dependencies,它已声明了spring-cloud-starter-alibaba-seata的版本。不过,由于该 Starter 内部依赖的seata-spring-boot-starter版本较旧,因此还需手动指定其具体版本。依赖管理配置如下:
XML<properties> <spring-boot.version>2.7.6</spring-boot.version> <spring-cloud.version>2021.0.8</spring-cloud.version> <spring-alibaba-cloud.version>2021.0.6.0</spring-alibaba-cloud.version> <seata.version>1.8.0</seata.version> </properties> <dependencyManagement> <dependencies> <!-- Spring Boot Dependencies --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- Spring Cloud Dependencies --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- Spring Cloud Alibaba Dependencies --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring-alibaba-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- Seata Spring Boot Starter --> <dependency> <groupId>org.apache.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>${seata.version}</version> </dependency> </dependencies> </dependencyManagement>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项目 POM 中引入依赖:
XML<!-- Spring Cloud Alibaba Seata --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> </dependency>
信息
Seata(Apache Seata)在版本 2.0 及之前使用 groupId 为 io.seata,而在 2.0 之后(包括 2.0.x 及更高版本,如 2.5.0)统一改为 org.apache.seata。这一变更主要是由于 Seata 从阿里巴巴开源项目正式进入 Apache 基金会孵化器(incubator),以符合 Apache 项目命名规范和包结构要求。该变更影响了所有核心 artifact,包括 seata-all 和 seata-spring-boot-starter,但不影响 Spring Cloud Alibaba 的 starter(其 groupId 始终为 com.alibaba.cloud)。
2.1.2. 配置 application.yml
参见配置示例。