Seata
本文范围
先看一个最常见的下单场景。
order-service 创建订单
-> storage-service 扣库存
-> account-service 扣余额如果三个服务各自提交自己的本地事务,就会出现一个很尴尬的问题:订单已经创建了,库存也扣了,最后账户余额不足,扣款失败了。这个时候如果没有统一协调,数据就可能变成“有订单、少库存、没扣钱”。
Seata 要解决的就是这类问题:一次业务操作跨了多个服务、多个数据库,每个服务都有自己的本地事务,但是最后要么一起成功,要么一起回滚。
这里先不要把 Seata 想成一个神奇开关。它更像一个事务协调员:
- 入口服务告诉它:“我要开一个全局事务。”
- 下游服务每做一次本地事务,就来登记:“我是这个全局事务下面的一个分支。”
- 入口服务成功了,它通知大家提交。
- 入口服务失败了,它通知大家回滚。
Seata 里有三个角色,后面看日志时经常会遇到:
| 角色 | 全称 | 放在哪里 | 你可以怎么理解 |
|---|---|---|---|
| TC | Transaction Coordinator | 独立部署的 Seata Server | 总协调员,记录全局事务、分支事务和全局锁 |
| TM | Transaction Manager | 业务服务里 | 全局事务发起者,负责开始、提交、回滚 |
| RM | Resource Manager | 业务服务里 | 资源管理者,负责本地分支事务和回滚 |
一般情况下,Seata Server 就是 TC;业务入口方法上加了 @GlobalTransactional,这个服务就承担 TM;每个参与数据库操作的服务,通过 Seata 数据源代理成为 RM。
Seata 全局事务角色与回滚链路图
下面这张图适合在接入评审时讲清楚“谁发起事务、谁登记分支、数据写到哪里、失败时靠什么回滚”。尤其是 AT 模式,不能只说“自动回滚”,必须把 XID、分支注册、全局锁、undo_log 和 TC 存储表串起来看。
设计评审时可以用这张图问四个问题:tx-service-group 能不能通过 Nacos 映射到正确 cluster;TC 的 store.mode 是否是生产可用模式;每个参与 AT 的业务库有没有匹配版本的 undo_log;失败回滚时能不能通过 global_table、branch_table、lock_table 和业务库 undo_log 定位到同一个 XID。
这篇只讲 Seata 的部署、配置、接入和排障。不展开 Spring Cloud Alibaba 全家桶,不把分布式事务理论写成大论文,也不讲本地消息表、事务消息、人工补偿平台这种完整业务一致性方案。它们都很重要,但不是这篇的主角。
当前写作时间是 2026-07-11。Apache Seata 官方下载页列出的 Java 最新版本是 2.6.0,下面命令用 2.6.0 做示例。生产环境不要直接复制版本号到几年后还用,上线前要重新核对 Seata Server、Seata Client、Spring Cloud Alibaba、Nacos、JDK 和数据库驱动版本。
还要单独看 JDK 边界:Seata Server 2.6.0 官方下载页对二进制包运行环境写的是 JDK 25 或更高;业务侧 Seata Client 取决于你引入的客户端版本、Spring Cloud Alibaba 版本和业务应用 JDK 基线,不要把 Server 的 JDK 要求机械套到所有业务服务。Docker 镜像则要以镜像内置运行时和镜像 tag 说明为准,验证时用 docker run --rm apache/seata-server:2.6.0 java -version 或进入容器查看,不要只看宿主机 JDK。
前置条件
接入 Seata 前,先确认这些条件:
- 已选定事务模式。本文以 AT 为主讲部署和排障,但 TCC、Saga、XA 的业务边界要先评估,不能所有场景都默认 AT。
- Seata Server、Seata Client、Spring Cloud Alibaba、Nacos、JDK、数据库驱动版本已经放进兼容矩阵,并在测试环境验证过;Server 二进制包、业务客户端和 Docker 镜像的 JDK 口径分开记录。
- Registry 和 config center 已确定,Nacos namespace、group、cluster、
tx-service-group、service.vgroupMapping命名规则明确。 - Seata Server 存储后端已规划,生产环境不要把 file 模式当成可靠存储;DB 表结构使用对应版本官方脚本初始化。
- 每个参与 AT 的业务库都已经创建
undo_log,并确认字符集、索引、权限和清理策略。 - 入口服务、下游服务、数据库、Nacos、Seata Server 网络互通,端口、鉴权、控制台入口和日志目录已确认。
- 已准备至少一条成功用例、一条失败回滚用例、一条全局锁冲突用例和一条回滚失败处理演练。
- 数据修复、人工补偿、对账和幂等策略已经有负责人。Seata 能协调事务,但不能替业务兜住所有一致性问题。
先选事务模式,不要一上来就 AT
很多项目一提 Seata 就默认 AT 模式,因为 AT 对业务代码侵入最小。但这里要注意,侵入小不等于成本低。AT 会拦截 SQL,生成 before image、after image,写 undo_log,再通过全局锁保证隔离。
可以先按下面这个判断:
| 模式 | 怎么工作 | 适合什么 | 现场最容易出什么问题 |
|---|---|---|---|
| AT | 自动代理 JDBC SQL,写 undo_log,失败时自动反向补偿 | 常规数据库型微服务,SQL 不复杂,冲突不高 | undo_log 缺失、SQL 不支持、全局锁冲突 |
| TCC | 业务自己写 Try、Confirm、Cancel | 高并发核心链路,需要明确资源预留 | 空回滚、悬挂、幂等没做好 |
| Saga | 用状态机编排多个本地事务和补偿动作 | 长流程、跨系统、人工介入多 | 状态机复杂,补偿动作漏写 |
| XA | 使用数据库 XA 协议做两阶段提交 | 强一致要求高,事务规模可控 | 锁持有时间长,吞吐下降 |
如果只是常规下单、扣库存、扣余额,先用 AT 跑通是可以的。但如果是秒杀库存、账户核心余额、热点行非常集中,不能只图接入快。全局锁冲突一上来,接口就会变慢,甚至频繁回滚。
后面我们以 AT 模式为主,因为它最适合讲清部署、undo_log、全局锁和常见排障。
中国大陆、内网和离线环境先准备
Seata 还没装之前,先把资源准备好。生产现场最常见的问题不是命令不会写,而是机器不能访问外网,临时下载不了包,Docker 镜像拉不下来,Maven 依赖也解析失败。
1、下载 Seata 安装包
公网下载机上执行:
VERSION=2.6.0
curl -fL -o apache-seata-${VERSION}-bin.tar.gz \
"https://dlcdn.apache.org/incubator/seata/${VERSION}/apache-seata-${VERSION}-bin.tar.gz"
curl -fL -o apache-seata-${VERSION}-bin.tar.gz.sha512 \
"https://downloads.apache.org/incubator/seata/${VERSION}/apache-seata-${VERSION}-bin.tar.gz.sha512"
sha512sum -c apache-seata-${VERSION}-bin.tar.gz.sha512这一步解决的是安装包来源可信的问题。最后一条命令输出 OK,说明下载包和官方校验值一致。如果校验失败,不要继续安装,先重新下载,或者换 Apache 官方镜像节点。
这里要注意,不要把某个第三方下载地址写进生产脚本里长期使用。正确做法是公网机器下载好安装包、校验文件和 KEYS,再放到企业内部制品库或文件服务器。
2、准备 Docker 镜像
如果你用容器部署,公网机器先拉取指定版本镜像:
docker pull apache/seata-server:2.6.0
docker image inspect apache/seata-server:2.6.0 --format '{{.Id}}'第一条命令下载镜像,第二条命令看镜像 ID。能看到 sha256: 开头的 ID,说明镜像已经在本机。
离线环境一般这样导出:
docker save apache/seata-server:2.6.0 -o seata-server-2.6.0.tar
sha256sum seata-server-2.6.0.tar > seata-server-2.6.0.tar.sha256到了内网机器后:
sha256sum -c seata-server-2.6.0.tar.sha256
docker load -i seata-server-2.6.0.tar
docker images | grep seata-server如果 sha256sum -c 失败,说明镜像包传输过程中可能损坏了,不要继续 docker load。如果 docker images 看不到 apache/seata-server,说明导入没有成功,先看 docker load 的输出。
3、准备 Maven 依赖
业务服务要引入 Seata client。企业内网不要让每台开发机和构建机直接访问公网 Maven 仓库,一般需要 Nexus 或 Artifactory 做代理缓存。
构建机可以先执行:
mvn -U dependency:tree | grep -i seata这条命令用来确认项目最终解析到了哪些 Seata 依赖。看到多个不同版本的 seata-* 包时要小心,尤其是一个服务用了新版本 starter,另一个服务还带着旧版本 client,后面排障会很痛苦。
准备部署目录
下面开始部署 Seata Server。为了让目录清楚一点,先创建配置目录和日志目录:
mkdir -p /data/seata/conf
mkdir -p /data/seata/logs这里的 /data/seata/conf 放配置文件,/data/seata/logs 放运行日志。生产环境不要把日志放到容器可写层里,也不要放到系统根目录。磁盘满的时候,Seata 本身不一定是根因,但它会跟着一起出问题。
如果没有这些目录就新建,有就直接复用:
ls -lah /data/seata正常应该能看到 conf 和 logs 两个目录。
准备 Seata Server 数据库
生产环境建议从 DB 模式开始。原因很简单:出了问题能查表,能备份,团队也容易理解。
先创建数据库和用户:
CREATE DATABASE seata DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'seata'@'%' IDENTIFIED BY 'change_me';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, INDEX, ALTER ON seata.* TO 'seata'@'%';
FLUSH PRIVILEGES;这里要注意,生产上密码不要写 change_me,也不要直接用 root 用户。Seata Server 只需要操作自己的事务状态表,权限不要给太大。
然后导入当前 Seata 版本官方脚本。DB 模式常见表有:
| 表 | 作用 |
|---|---|
global_table | 记录全局事务 |
branch_table | 记录分支事务 |
lock_table | 记录全局锁 |
distributed_lock | 记录服务端内部锁 |
导入后执行:
USE seata;
SHOW TABLES;
DESC global_table;
DESC branch_table;
DESC lock_table;
DESC distributed_lock;正常情况下,SHOW TABLES 应该能看到上面几张表。如果缺 distributed_lock,不要先怀疑 MySQL,先确认你是不是用了旧版本 SQL 脚本。Seata 版本和 SQL 脚本一定要匹配。
写 Seata Server 配置
现在写 /data/seata/conf/application.yml。如果你用的是二进制部署,也可以把同样的配置放到 Seata 的配置目录里。这里用 Docker 举例。
server:
port: 7091
spring:
application:
name: seata-server
seata:
registry:
type: nacos
nacos:
application: seata-server
server-addr: nacos-1.example.com:8848,nacos-2.example.com:8848,nacos-3.example.com:8848
namespace: prod-namespace-id
group: SEATA_GROUP
cluster: trade
username: nacos
password: ${NACOS_PASSWORD}
config:
type: nacos
nacos:
server-addr: nacos-1.example.com:8848,nacos-2.example.com:8848,nacos-3.example.com:8848
namespace: prod-namespace-id
group: SEATA_GROUP
data-id: seataServer.properties
username: nacos
password: ${NACOS_PASSWORD}这份配置做了两件事:
registry.type=nacos:Seata Server 启动后会注册到 Nacos,业务服务通过 Nacos 找它。config.type=nacos:Seata Server 会从 Nacos 读取事务分组、存储模式、数据库连接等公共配置。
这里要注意 namespace。Nacos 控制台里显示的是命名空间名称,但配置里一般要填 namespace ID。public 命名空间通常保持空,不要随手写字符串 public。
再准备 Nacos 里的 seataServer.properties:
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
service.vgroupMapping.trade_tx_group=trade
service.enableDegrade=false
service.disableGlobalTransaction=false
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://mysql.example.com:3306/seata?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
store.db.user=seata
store.db.password=change_me
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.lockTable=lock_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.maxWait=5000最关键的是这一行:
service.vgroupMapping.trade_tx_group=trade它的意思是:业务服务里配置的事务分组 trade_tx_group,要去找 Seata Server 的 trade 集群。
后面如果客户端报 no available service found in cluster,十有八九就从这条线开始查:
业务服务 tx-service-group = trade_tx_group
Nacos 配置 service.vgroupMapping.trade_tx_group = trade
Seata Server registry.nacos.cluster = trade
Seata Server 和客户端的 namespace/group 一致旧文章和旧项目里可能会看到 service.vgroup_mapping.* 这种写法,新版本要以官方参数为准。升级时不要只复制旧配置。
启动 Seata Server
配置准备好后,启动容器:
docker run -d \
--name seata-server \
--restart unless-stopped \
-p 8091:8091 \
-p 7091:7091 \
-e SEATA_IP=192.168.10.21 \
-e SEATA_PORT=8091 \
-e NACOS_PASSWORD='nacos_password' \
-v /data/seata/conf/application.yml:/seata-server/resources/application.yml \
-v /data/seata/logs:/root/logs/seata \
apache/seata-server:2.6.0参数说明一下:
-p 8091:8091:事务通信端口,业务服务连接 TC 用。-p 7091:7091:控制台或管理端口,具体以当前版本配置为准。SEATA_IP:告诉 Seata Server 注册到 Nacos 时使用哪个 IP。SEATA_PORT:告诉客户端连接哪个事务端口。-v application.yml:挂载配置文件,避免把配置写死在镜像里。-v logs:把日志落到宿主机,便于排障和采集。
这里最容易错的是 SEATA_IP。如果不写,容器可能把自己的容器内 IP 注册到 Nacos。业务服务在容器外面,当然连不上。
启动后先看容器:
docker ps --filter name=seata-server如果 STATUS 是 Up,说明容器进程起来了。但这还不代表 Seata 可用,还要看日志:
docker logs seata-server --tail=200正常应该能看到服务启动、Nacos 注册、配置加载成功之类的信息。如果看到 Nacos 认证失败、namespace 找不到、数据库连接失败,就先不要接业务服务,先把 Server 修好。
再看端口:
ss -lntp | grep -E '8091|7091'能看到监听,说明端口已经起来。如果端口没有监听,但容器还在运行,通常要看容器日志;如果容器已经退出,执行:
docker ps -a --filter name=seata-server
docker logs seata-server --tail=300最后去 Nacos 控制台看服务列表。访问后应该能看到 seata-server,cluster 是 trade,group 是 SEATA_GROUP。如果看不到,先查 Nacos 地址、账号密码、namespace、group 和容器网络。
二进制方式怎么启动
如果不用 Docker,也可以直接解压二进制包启动:
mkdir -p /opt/seata
tar -xf apache-seata-2.6.0-bin.tar.gz -C /opt/seata --strip-components=1
cd /opt/seata把配置写好以后启动:
bin/seata-server.sh -p 8091 -h 192.168.10.21 -m db这里:
-p 8091是事务服务端口。-h 192.168.10.21是注册到 Nacos 的地址。-m db表示使用 DB 存储模式。
验证:
ps -ef | grep seata-server | grep -v grep
ss -lntp | grep 8091
tail -f logs/start.out如果是 systemd 托管,还要检查:
systemctl status seata-server
journalctl -u seata-server -n 200 --no-pager二进制部署的好处是简单直观,坏处是后续升级、回滚、日志目录、进程托管都要自己处理。团队如果已经容器化,优先用容器方式统一交付。
给业务库建 undo_log
Seata Server 的表建好了还不够。AT 模式下,每个参与全局事务的业务库都要建 undo_log。
假设有三个库:
order_db
storage_db
account_db那这三个库都要建 undo_log。不要只建在订单库里。
进入每个业务库检查:
SHOW TABLES LIKE 'undo_log';如果没有,就用当前 Seata 版本官方脚本创建。创建后检查字段:
DESC undo_log;正常会看到 xid、branch_id、rollback_info、log_status、log_created、log_modified 等字段。
undo_log 是干什么的?简单说,AT 模式执行写 SQL 时,会先记录修改前的数据,再记录修改后的数据。以后如果全局事务失败,Seata 就靠这些记录生成反向 SQL,把数据恢复回去。
这里要注意两件事:
undo_log表结构要和 Seata client 版本匹配。- 不要在不了解事务状态时手动清空
undo_log。
如果 undo_log 增长很快,一般要查二阶段清理有没有失败:
SELECT COUNT(*) FROM undo_log;
SELECT xid, branch_id, log_status, log_created, log_modified
FROM undo_log
ORDER BY log_modified DESC
LIMIT 20;正常提交后的 undo_log 不应该无限堆积。一直堆积,说明 RM、TC、数据库权限或二阶段异步删除有问题。
业务服务引入 Seata
服务接入前先统一版本。最怕的是 order-service 用一套 Seata client,storage-service 又因为 starter 间接带了另一套版本。
直接使用 Seata starter 可以这样写:
<dependency>
<groupId>org.apache.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.6.0</version>
</dependency>Spring Cloud Alibaba 项目一般用 BOM 管理:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>然后引入:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>引完依赖后先看依赖树:
mvn dependency:tree | grep -i seata这条命令不是走形式。它能帮你确认最终项目里到底用了哪个 Seata 版本。如果出现多个版本,先处理依赖冲突,再启动服务。
配置业务服务
业务服务配置示例:
seata:
enabled: true
application-id: order-service
tx-service-group: trade_tx_group
enable-auto-data-source-proxy: true
registry:
type: nacos
nacos:
application: seata-server
server-addr: nacos-1.example.com:8848,nacos-2.example.com:8848,nacos-3.example.com:8848
namespace: prod-namespace-id
group: SEATA_GROUP
cluster: trade
username: nacos
password: ${NACOS_PASSWORD}
config:
type: nacos
nacos:
server-addr: nacos-1.example.com:8848,nacos-2.example.com:8848,nacos-3.example.com:8848
namespace: prod-namespace-id
group: SEATA_GROUP
data-id: seataServer.properties
username: nacos
password: ${NACOS_PASSWORD}老项目里可能会看到:
spring:
cloud:
alibaba:
seata:
tx-service-group: trade_tx_group这不是一定错,但要看你当前 Spring Cloud Alibaba 版本支持哪种配置方式。不要把老配置和新 starter 混着写,然后靠猜。
启动服务后看日志:
grep -i "seata" order-service.log | tail -100
grep -i "register TM" order-service.log | tail -20
grep -i "register RM" order-service.log | tail -20
grep -i "trade_tx_group" order-service.log | tail -20正常应该能看到 TM/RM 注册成功,且事务分组是 trade_tx_group。如果日志里没有任何 Seata 信息,先查依赖是否生效、配置是否被读取、profile 是否加载错。
数据源代理要确认
AT 模式必须代理数据源,否则 Seata 根本拦截不到 SQL,也就不会写 undo_log。
新版本 starter 通常可以自动代理:
seata:
enable-auto-data-source-proxy: true如果是老项目或复杂多数据源项目,可能需要手动配置:
@Configuration
public class SeataDataSourceConfig {
@Bean
@Primary
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
}这里要注意,自动代理和手动代理不要重复。重复代理以后,问题会很绕,日志看起来像进了 Seata,但事务行为不稳定。
多数据源项目更要小心。不是只代理默认数据源,参与全局事务的业务数据源都要被代理。
验证方式很简单:
- 执行一次全局事务里的写操作。
- 查询业务库
undo_log是否短暂出现记录。 - 看业务日志里有没有 branch register、global lock、xid 等关键字。
SELECT xid, branch_id, log_status, log_created
FROM undo_log
ORDER BY log_created DESC
LIMIT 10;如果业务写入成功,但 undo_log 从来没有记录,优先查数据源代理。
写第一个全局事务
入口服务方法上加 @GlobalTransactional:
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public Long createOrder(CreateOrderCommand command) {
Long orderId = orderService.create(command);
storageClient.deduct(command.getSkuId(), command.getCount());
accountClient.deduct(command.getUserId(), command.getAmount());
return orderId;
}这里有几个细节:
@GlobalTransactional一般加在入口业务方法上,不要每个下游都加一遍。rollbackFor = Exception.class建议写上,避免检查型异常不触发回滚。- 下游异常不要吞掉。你 catch 住以后返回成功,TM 就会认为全局事务可以提交。
- 全局事务范围越小越好,不要把慢查询、文件上传、第三方接口轮询放进去。
想确认当前线程有没有 XID,可以打日志:
String xid = RootContext.getXID();
log.info("current seata xid={}", xid);入口服务有 XID,下游没有 XID,说明调用链路没有把 XID 传过去。常见位置是 Feign 拦截器、网关过滤器、异步线程、线程池和消息消费。
跑一条成功链路
先准备数据:
SELECT * FROM storage WHERE sku_id = 1001;
SELECT * FROM account WHERE user_id = 1;然后发起下单:
curl -X POST http://gateway.example.com/api/orders \
-H 'Content-Type: application/json' \
-d '{"userId":1,"skuId":1001,"count":1,"amount":100}'接口返回成功后检查:
SELECT * FROM orders ORDER BY id DESC LIMIT 1;
SELECT * FROM storage WHERE sku_id = 1001;
SELECT * FROM account WHERE user_id = 1;
SELECT COUNT(*) FROM undo_log;你应该看到:
- 订单创建成功。
- 库存减少。
- 余额减少。
undo_log可能短暂出现后被清理。
如果接口成功但库存或余额没变化,先查业务调用是否真的走到了下游。Seata 只能协调事务,不能替你调用服务。
故意跑一条失败链路
验证 Seata 是否生效,不能只测成功。一定要故意制造失败。
比如账户服务余额不足时抛异常:
if (command.getAmount().compareTo(balance) > 0) {
throw new IllegalArgumentException("balance not enough");
}然后发起一个金额很大的请求:
curl -X POST http://gateway.example.com/api/orders \
-H 'Content-Type: application/json' \
-d '{"userId":1,"skuId":1001,"count":1,"amount":999999}'接口应该返回失败。接着查数据:
SELECT * FROM orders ORDER BY id DESC LIMIT 5;
SELECT * FROM storage WHERE sku_id = 1001;
SELECT * FROM account WHERE user_id = 1;正常结果是:订单没有保留,库存没有扣,账户余额没有变。
再看日志:
grep -Ei 'xid|global|branch|rollback|seata' order-service.log | tail -200
grep -Ei 'xid|global|branch|rollback|seata' storage-service.log | tail -200
grep -Ei 'xid|global|branch|rollback|seata' account-service.log | tail -200如果接口失败,但订单或库存已经提交了,就说明全局事务没有覆盖到所有分支。先查 XID 是否传播,再查数据源代理,再查异常是不是被吞掉。
控制台怎么看
控制台不是装饰品,排障时很有用。启动后访问控制台地址,应该能看到事务、全局锁、Saga 状态机等页面。不同版本控制台端口可能不一样,先看配置和启动日志,不要只背一个端口。
grep -n "port:" /data/seata/conf/application.yml
docker logs seata-server --tail=100
ss -lntp | grep java生产上控制台要注意:
- 不要暴露公网。
- 修改默认账号密码。
- 修改 SecretKey。
- 加内网访问限制、VPN 或堡垒机。
- 异常事务处理前先记录 XID、业务主键和时间点。
控制台能看到异常事务,不代表可以随手删。删除之前要先确认业务数据、undo_log、global_table、branch_table 的状态,否则自动回滚可能变成人工修数。
集群怎么配
生产一般不会只跑一个 Seata Server。至少两个实例,注册到同一个 Nacos namespace 和 group,然后用 cluster 区分。
例如交易域用 trade 集群:
seata:
registry:
nacos:
cluster: tradeNacos 配置里写:
service.vgroupMapping.trade_tx_group=trade业务服务写:
seata:
tx-service-group: trade_tx_group这三处要连起来看。很多排障现场只看业务服务配置,忘了 Nacos 的 service.vgroupMapping,结果服务启动正常,一调用全局事务就找不到 TC。
如果你有多个业务域,可以这样规划:
service.vgroupMapping.trade_tx_group=trade
service.vgroupMapping.member_tx_group=member
service.vgroupMapping.settle_tx_group=settle这里要注意,不要所有系统都长期用 default_tx_group。前期省事,后期迁移时就很难拆。
把集群画到生产拓扑里,会更容易看清 Seata 的真实风险点:Seata Server 进程可以横向扩,但事务状态并不是无状态的。DB 模式要治理数据库容量、备份、慢 SQL 和主从切换;Raft 模式要治理节点数、日志、快照和多数派;业务库还要治理 undo_log 的增长和清理。
上线检查时按这张图逐项确认:Nacos 能解析到正确 cluster,8091 在业务网段可达,7091 控制台只在受控网段开放,DB 表或 Raft 数据目录有备份和容量告警,业务库 undo_log 有数量、清理延迟和异常记录监控。Seata Server “多副本运行”不等于事务状态可靠,真正可靠的是存储、配置、业务库和恢复演练都闭环。
日志和表怎么查
Seata 排障不要只看业务日志。至少要看四处:
- Seata Server 日志。
- 业务服务日志。
- Nacos 注册和配置。
- Seata Server 数据库表、业务库
undo_log。
看 Server 日志:
docker logs seata-server --tail=300 | grep -Ei 'error|rollback|lock|branch|global'看业务日志:
grep -Ei 'xid|seata|branch|rollback|global' order-service.log | tail -200看最近全局事务:
SELECT xid, status, application_id, transaction_service_group, begin_time
FROM seata.global_table
ORDER BY begin_time DESC
LIMIT 20;看分支事务:
SELECT xid, branch_id, resource_id, status, client_id
FROM seata.branch_table
ORDER BY branch_id DESC
LIMIT 20;看全局锁:
SELECT xid, branch_id, resource_id, table_name, pk, row_key
FROM seata.lock_table
ORDER BY row_key
LIMIT 50;看业务库 undo_log:
SELECT xid, branch_id, log_status, log_created, log_modified
FROM undo_log
ORDER BY log_modified DESC
LIMIT 20;这些 SQL 解决的是“事务到底走到哪一步”的问题。只看接口返回,很难判断是入口没开全局事务,还是下游没注册分支,还是二阶段回滚失败。
Seata 失败排查决策图
Seata 的故障不要只从业务接口返回值判断。下面这张图适合值班时先把“连不上 TC、分支没注册、锁冲突、undo_log 异常、事务卡住、Nacos 配置错误”拆成可验证分支。
现场用这张图时,先记录 XID、业务主键和时间点,再查表和日志。不要先删 global_table、branch_table 或 undo_log,这些记录本身就是判断自动重试、人工修数还是业务补偿的证据。
故障一:Seata Server 注册不上 Nacos
现象一般是:
Nacos 控制台看不到 seata-server
Seata Server 日志出现 registry failed
业务服务提示找不到 TC先测 Nacos:
curl -I http://nacos-1.example.com:8848/nacos/能返回 HTTP 状态,说明网络至少通了。然后看 Server 日志:
docker logs seata-server --tail=200再查配置:
grep -n "namespace" /data/seata/conf/application.yml
grep -n "group" /data/seata/conf/application.yml
grep -n "cluster" /data/seata/conf/application.yml一般需要重点看:
- Nacos 地址写错没有。
- 用户名密码是否正确。
- namespace 是 ID 还是名称。
- group 是否是
SEATA_GROUP。 - 容器注册出去的 IP 是否业务服务能访问。
修复后重启:
docker restart seata-server
docker logs seata-server --tail=100然后回 Nacos 控制台看服务实例。能看到 seata-server,再继续接业务。
故障二:客户端提示 no available service
这个错误很典型,常见信息类似:
no available service found in cluster
can not get cluster name in registry config
Failed to get available servers不要一上来重启服务。先按这条链路查:
客户端 tx-service-group
-> Nacos 配置 service.vgroupMapping.<tx-service-group>
-> 映射出来的 cluster
-> Seata Server 注册到 Nacos 的 cluster
-> namespace 和 group 是否一致例如客户端:
seata:
tx-service-group: trade_tx_groupNacos:
service.vgroupMapping.trade_tx_group=tradeServer:
seata:
registry:
nacos:
group: SEATA_GROUP
cluster: trade这三个地方只要一个不一致,客户端就可能找不到 TC。特别是 trade_tx_group 和 trade,一个是事务分组,一个是 Seata Server 集群名,不要混成一个概念。
故障三:接口失败了,但数据没有回滚
现象:
接口报错
订单却生成了
库存也扣了
账户没扣先查入口有没有 XID:
grep -Ei 'xid|global|GlobalTransactional' order-service.log | tail -100再查下游有没有 XID:
grep -Ei 'xid|branch|seata' storage-service.log | tail -100
grep -Ei 'xid|branch|seata' account-service.log | tail -100如果入口有 XID,下游没有,多半是调用链路没传 XID。查 Feign、Gateway、异步线程、线程池、消息消费。
如果下游也有 XID,再查 undo_log:
SELECT xid, branch_id, log_status, log_created
FROM undo_log
ORDER BY log_created DESC
LIMIT 10;如果完全没有 undo_log,优先查数据源代理。如果有 undo_log,再去 Seata Server 表里查事务状态:
SELECT xid, status, application_id
FROM seata.global_table
ORDER BY begin_time DESC
LIMIT 10;
SELECT xid, branch_id, status, resource_id
FROM seata.branch_table
ORDER BY branch_id DESC
LIMIT 10;这里要注意,业务代码里 catch 异常后返回成功,是最容易忽略的坑。TM 看到方法正常返回,就会提交全局事务。
故障四:undo_log 报错
常见报错:
Table 'xxx.undo_log' doesn't exist
Duplicate entry for key ux_undo_log
rollback info deserialize failed先确认表是否存在:
SHOW TABLES LIKE 'undo_log';
DESC undo_log;如果表不存在,就给这个业务库导入对应版本官方脚本。注意,是每个参与 AT 的业务库都要建。
如果字段不一致,先不要手动补几个字段凑合用。正确做法是用当前版本官方 SQL 对比迁移。Seata 的 rollback_info、序列化方式、字段长度这些都和版本有关。
如果反序列化失败,查配置:
client.undo.logSerialization
client.undo.logTable还要查服务版本:
mvn dependency:tree | grep -i seata版本混用时,undo_log 相关问题会非常隐蔽。
故障五:全局锁冲突
报错常见是:
get global lock fail
Global lock acquire failed
LockConflictException这不是单纯“Seata 不稳定”。它通常说明多个全局事务在抢同一行数据。
先查全局锁:
SELECT xid, branch_id, resource_id, table_name, pk, row_key
FROM seata.lock_table
ORDER BY row_key
LIMIT 50;再查业务库锁等待:
SHOW ENGINE INNODB STATUS\G
SELECT * FROM information_schema.innodb_trx\G常见处理方向:
- 缩短全局事务范围。
- 热点行不要轻易放进 AT。
- 批量更新拆小。
- 给更新条件补索引,避免锁范围扩大。
- 高并发扣减场景评估 TCC 或业务补偿。
这里的原理和实战是连着的:AT 一阶段虽然会释放本地数据库锁,但全局锁还要保证分布式层面的隔离。热点行越集中,全局锁冲突越明显。
故障六:回滚失败
回滚失败常见日志:
Branch rollback failed
dirty data
PhaseTwo_RollbackFailed_RetryableAT 回滚时会用 undo_log 里的 after image 和当前数据做比较。如果当前数据已经被别人改过,就会判断为脏数据,不会直接覆盖。
先查 undo_log:
SELECT xid, branch_id, log_status, log_created, log_modified
FROM undo_log
ORDER BY log_modified DESC
LIMIT 20;再查全局和分支:
SELECT xid, status
FROM seata.global_table
WHERE xid = '目标 XID';
SELECT xid, branch_id, status, resource_id
FROM seata.branch_table
WHERE xid = '目标 XID';处理时不要急着删记录。一般步骤是:
- 记录 XID、业务主键、接口、时间点。
- 查业务表当前值。
- 对比
undo_log里的镜像。 - 判断能否重试回滚。
- 不能自动回滚时,走人工修数或业务补偿。
- 修完以后再处理异常事务状态。
这类问题最后一定要复盘:为什么回滚期间数据被改了,是事务范围太长,还是人工修数没流程,还是业务接口缺少幂等和隔离。
生产上怎么治理
Seata 能跑起来只是第一步,能长期运行才是重点。
一般建议这样做:
- 版本统一:Server、client、starter、Spring Cloud Alibaba BOM 放到同一张版本表里。
- 配置统一:namespace、group、cluster、tx-service-group 有命名规范。
- 存储统一:生产优先 DB 或经过验证的 Raft,不用 file 拼集群。
- 日志统一:Seata Server、业务服务、Nacos、数据库日志都要能查。
- 监控统一:进程、端口、Nacos 实例、事务异常、全局锁、
undo_log增长都要告警。 - 权限统一:控制台不暴露公网,修改默认账号、密码和 SecretKey。
- 回滚统一:镜像、配置、SQL、Maven 依赖都要能回退。
发布前可以按下面清单过一遍:
[ ] Seata Server 版本已确认
[ ] Seata client 和 Spring Cloud Alibaba 版本已确认
[ ] Nacos namespace、group、cluster 已确认
[ ] service.vgroupMapping 和 tx-service-group 已确认
[ ] Server DB 表已使用对应版本官方脚本初始化
[ ] 每个参与 AT 的业务库都有 undo_log
[ ] 数据源代理已验证
[ ] XID 能从入口传到下游
[ ] 成功提交用例已通过
[ ] 失败回滚用例已通过
[ ] 全局锁冲突用例已验证
[ ] 控制台账号、密码、SecretKey 已修改
[ ] 日志目录、日志保留和告警已配置
[ ] 离线包、镜像、Maven 依赖、SQL 脚本已归档
[ ] 回滚镜像和配置已准备故障时也按清单来,不要靠感觉:
[ ] 记录 XID、接口、时间、服务、业务主键
[ ] 查入口是否进入 @GlobalTransactional
[ ] 查下游是否拿到 XID
[ ] 查 Seata Server global_table / branch_table
[ ] 查 lock_table 是否有锁冲突
[ ] 查业务库 undo_log
[ ] 查 Server 日志和业务日志
[ ] 判断是可重试、人工修数还是业务补偿
[ ] 修复后做数据一致性验证
[ ] 复盘事务范围、热点行、版本和配置问题最后再强调一句现场经验:不要让 Seata 替你掩盖业务边界问题。它能协调事务,但不能替你设计幂等、超时、重试、补偿、对账和人工修复流程。真正稳定的生产系统,一定是 Seata 加业务治理一起落地。
落地工程深水区
Seata 的深水区基本都在“事务协调之外”的工程治理里:
- 全局锁冲突要提前压测。热点库存、账户余额、状态机主表这类行锁冲突高的场景,AT 模式可能把接口拖慢,甚至频繁回滚。
undo_log不是垃圾表。它是回滚证据,要监控增长、清理延迟、异常记录和字段兼容,不能随手清空。- TC 存储要按生产级别治理。Seata Server 自身无状态不代表事务数据无状态,DB 存储的备份、容量、慢 SQL、主从切换都要有预案。
- Nacos 兼容性要单独验证。注册发现、配置推送、命名空间、鉴权、长连接和客户端版本都可能影响 Seata 发现 TC。
tx-service-group和 vgroupMapping 要纳入配置审计。一个环境写错 group,业务服务就可能找不到 TC,或者连到错误集群。- 回滚失败要有人工路径。脏写、空回滚、幂等缺失、SQL 不支持、人工修数,都可能导致自动回滚失败,必须能定位 XID、业务主键和补偿方案。
- SQL 支持边界要写清。AT 对复杂 SQL、存储过程、触发器、跨库语句、非标准驱动的支持要逐项验证,不要上线后才发现无法生成镜像。
- 监控要覆盖事务状态。全局事务数量、分支事务失败、锁冲突、回滚失败、TC 存储异常、Server 日志错误、Nacos 实例异常都要告警。
- 控制台和密钥要收口。默认账号、默认 SecretKey、控制台公网暴露、跨环境共用密钥,都会把事务系统变成高危入口。
- 组织边界要明确。Seata 归平台或运维维护,业务一致性归业务团队负责;没有对账和补偿流程,分布式事务只能解决一半问题。
常用命令速查
查看 Server 容器:
docker ps --filter name=seata-server
docker logs seata-server --tail=200查看端口:
ss -lntp | grep -E '8091|7091'查看业务服务 Seata 日志:
grep -Ei 'xid|seata|branch|rollback|global' order-service.log | tail -200查看最近全局事务:
SELECT xid, status, application_id, transaction_service_group, begin_time
FROM seata.global_table
ORDER BY begin_time DESC
LIMIT 20;查看分支事务:
SELECT xid, branch_id, resource_id, status, client_id
FROM seata.branch_table
ORDER BY branch_id DESC
LIMIT 20;查看全局锁:
SELECT xid, branch_id, resource_id, table_name, pk, row_key
FROM seata.lock_table
ORDER BY row_key
LIMIT 50;查看业务库 undo_log:
SELECT xid, branch_id, log_status, log_created, log_modified
FROM undo_log
ORDER BY log_modified DESC
LIMIT 20;查看依赖版本:
mvn dependency:tree | grep -i seata