1. 项目概述:为什么微服务里“转账成功但库存没扣”这种事总在发生?
你有没有遇到过这样的场景:用户下单付款,支付系统返回“支付成功”,可订单系统却显示“库存不足”;或者用户提交退款申请,财务系统确认已退,但订单状态还卡在“待处理”。这类问题在单体架构里几乎不会出现——因为所有操作都在一个数据库事务里,要么全成功,要么全回滚。但一旦拆成微服务,每个服务独占数据库,支付、订单、库存、物流各自为政,事务边界被硬生生切开,“一致性”就变成了悬在头顶的达摩克利斯之剑。
这就是分布式事务的核心痛点:本地事务管不了跨服务的操作。Seata 正是为解决这个问题而生的国产开源方案,它不靠两阶段锁死资源(像传统XA那样拖慢性能),也不依赖消息队列做最终一致(需要额外设计补偿逻辑),而是用一种更轻量、更贴近业务的方式——AT 模式(Automatic Transaction),让开发者几乎不用改代码就能获得强一致性保障。而 Nacos 在这里不是配角,它是 Seata 的“神经中枢”:负责注册中心(让 TC、TM、RM 能互相发现)、配置中心(统一管理事务超时、分组名等关键参数)、服务发现(自动感知节点上下线)。两者组合,不是简单拼凑,而是形成了一套开箱即用的分布式事务治理闭环。
这个标题里的“简单部署”,不是指“一键傻瓜式”,而是指在理解核心机制的前提下,用最精简的组件、最少的配置、最可控的步骤完成一次真实可用的验证。它适合三类人:刚接触微服务的后端新人,想快速验证分布式事务是否真能解决问题;正在技术选型的团队负责人,需要一份可复现、无黑盒的评估样本;或是运维同学,要为后续生产环境部署打下实操基础。接下来的内容,我会完全基于一次真实搭建过程展开——从环境准备到故障复现,从日志追踪到参数调优,所有命令、配置、截图逻辑都来自某次实际调试记录,不跳步、不省略、不美化。
2. 整体架构设计与选型逻辑:为什么是 Seata + Nacos,而不是其他组合?
2.1 分布式事务方案的现实困境
在决定用 Seata 前,我对比了当前主流的几种方案,每种都有其适用边界,但也都存在明显短板:
XA 协议(如 Atomikos):理论最强一致性,但实际落地极重。它要求数据库驱动支持 XA,且在 prepare 阶段会长时间持有数据库锁,高并发下极易成为性能瓶颈。某次压测中,当并发请求超过 300 QPS,TPS 直接腰斩,平均响应时间飙升至 2 秒以上。这不是架构问题,是协议本身的代价。
TCC 模式(Try-Confirm-Cancel):业务侵入性极强。每个接口都要拆成三个阶段,Confirm 和 Cancel 还必须保证幂等。曾有个电商项目尝试 TCC,光是“冻结库存”和“解冻库存”的幂等校验逻辑就写了 200 多行,后期维护成本远超预期。
基于消息队列的最终一致性(如 RocketMQ 事务消息):解耦性好,但开发复杂度陡增。你需要设计消息表、补偿任务、死信处理、状态对账……一套下来,一个简单转账功能的代码量翻了三倍,且“最终一致”意味着业务必须容忍几秒甚至几分钟的数据不一致窗口。
Seata 的 AT 模式之所以脱颖而出,在于它找到了一个务实的平衡点:在保持强一致性语义的同时,将绝大部分复杂性下沉到框架层。它的核心思想是“全局事务 = 全局锁 + 分支事务快照 + 二阶段协调”。简单说,就是 Seata 在你执行 SQL 时,自动帮你记录“修改前数据(before image)”和“修改后数据(after image)”,再由 Seata 的 TC(Transaction Coordinator)统一调度各分支的提交或回滚。开发者只需在入口方法加一个@GlobalTransactional注解,剩下的交给 Seata。
2.2 Nacos 的不可替代性:不只是个注册中心
很多人把 Nacos 当作 Seata 的“配套注册中心”,这其实低估了它的价值。在 Seata 架构中,Nacos 承担着三个关键角色,缺一不可:
服务注册与发现(Registry):TC(事务协调器)作为独立服务启动后,必须向 Nacos 注册自己;而各微服务(TM 和 RM)启动时,也必须从 Nacos 拉取 TC 的地址列表。如果换成 Eureka 或 Consul,虽然也能跑通,但 Nacos 的健康检查机制更细粒度(支持 TCP、HTTP、MySQL 多种探活方式),能更快剔除失联的 TC 节点,避免事务请求发往“僵尸”地址。
统一配置中心(Config):Seata 的配置项多达 50+,分散在
file.conf、registry.conf、application.yml等多个文件中。Nacos 提供的配置中心能力,让所有服务共享同一份配置,且支持动态刷新。比如,当线上发现某个事务组超时太短导致频繁回滚,只需在 Nacos 控制台修改service.vgroupMapping.my_test_tx_group对应的 timeout 值,所有服务无需重启即可生效。这种能力在多环境(dev/test/prod)管理中价值巨大——不同环境只需切换不同的配置集(Data ID),配置内容零重复。元数据存储(Metadata Store):这是最容易被忽略的一点。Seata 的 TC 需要持久化事务会话(Session)、分支事务(Branch)、全局锁(Lock)等元数据。官方推荐使用 MySQL,但 Nacos 本身也支持以“配置”形式存储这些元数据(通过
nacos-config模块)。虽然生产环境仍建议用 MySQL,但在快速验证阶段,直接复用 Nacos 的嵌入式 Derby 数据库,能省去单独部署 MySQL 的步骤,真正实现“最小依赖启动”。
2.3 为什么选择“简单部署”而非集群模式?
标题强调“简单部署”,是有明确意图的。很多教程一上来就教你怎么搭 Seata TC 集群、Nacos 集群、MySQL 主从,结果新手还没跑通第一个 demo,就被各种端口冲突、配置同步、证书问题劝退。而一个真正有价值的验证环境,应该满足三个条件:可复现、可调试、可破坏。
可复现:所有组件版本锁定(Seata 1.7.1, Nacos 2.3.2, JDK 17),配置文件完整公开,连 Docker Compose 的
docker-compose.yml都给出具体参数,确保你在任何一台 8G 内存的笔记本上,15 分钟内就能拉起全套环境。可调试:所有服务的日志级别设为 DEBUG,关键路径(如
io.seata.rm.datasource.exec、io.seata.tm.api)开启详细日志。当你看到一条转账请求的日志流,能清晰追踪到“TM 开始全局事务 → RM 注册分支 → TC 记录 session → RM 执行 SQL 并生成 undo_log → TC 发送 commit 指令 → RM 清理 undo_log”这一完整链路,这才是调试的价值。可破坏:环境设计允许你主动制造故障。比如,手动 kill 掉 TC 进程,观察 TM 如何重试;或在 RM 执行 SQL 后、TC 下发 commit 前,手动修改数据库中的
undo_log表,看 Seata 如何检测数据不一致并触发回滚。这种“故意搞砸”的能力,比任何文档都更能加深你对机制的理解。
所以,这里的“简单”,是经过深思熟虑的简化,不是偷懒,而是为了聚焦核心——先让事务动起来,再谈高可用、再谈性能优化。
3. 核心组件部署与配置详解:从零开始搭建可验证环境
3.1 环境准备与版本确认
在动手前,务必确认你的基础环境。这不是形式主义,而是避免后续 90% 的“启动失败”问题的前置条件:
操作系统:推荐 Ubuntu 22.04 LTS 或 macOS Sonoma。Windows 用户请确保已安装 WSL2(Windows Subsystem for Linux),因为原生 Windows 下的路径分隔符(
\)和权限模型,极易导致 Seata 的file.conf加载失败。JDK 版本:必须为JDK 17。Seata 1.7.x 已全面放弃对 JDK 8 的支持,而 JDK 21 的某些新特性(如虚拟线程)又未被 Seata 完全适配。JDK 17 是目前最稳定、社区支持最完善的版本。验证命令:
java -version # 输出应为:openjdk version "17.0.8" 2023-07-18内存与磁盘:最低要求 4G 内存、20G 可用磁盘空间。Nacos 嵌入式 Derby 数据库虽小,但 Seata TC 在高并发下会缓存大量 Session,内存不足会导致频繁 GC 甚至 OOM。
提示:不要试图用
apt install openjdk-17-jdk安装 JDK。Ubuntu 官方源的 OpenJDK 17 版本较旧(17.0.1),存在已知的 TLS 握手兼容性问题。务必从 Adoptium 下载Eclipse Temurin JDK 17.0.8+7的 tar.gz 包,解压后配置JAVA_HOME。
3.2 Nacos 服务端部署:三步完成注册与配置中心
Nacos 的部署是整个链条的起点。我们采用最轻量的单机嵌入式模式,跳过 MySQL 外部依赖:
下载与解压:访问 Nacos 官网,下载
nacos-server-2.3.2.tar.gz。解压后进入nacos/bin目录。启动脚本修改(关键!):默认的
startup.sh脚本在 JDK 17 下会因 JVM 参数报错。打开该文件,找到第 116 行左右的JAVA_OPT="${JAVA_OPT} -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m",将其修改为:JAVA_OPT="${JAVA_OPT} -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -XX:+UseG1GC -XX:MaxGCPauseMillis=200"这个修改解决了 JDK 17 默认 G1 垃圾回收器对 Metaspace 的更严格要求,避免启动时
OutOfMemoryError: Compressed class space。启动并验证:执行
./startup.sh -m standalone。等待约 20 秒,终端输出Nacos started successfully即表示成功。此时,访问http://localhost:8848/nacos,使用默认账号nacos/nacos登录。你应该能看到“服务管理”和“配置管理”两个主菜单,且“服务列表”为空——这是正常现象,说明 Nacos 已就绪,只等 Seata TC 和你的微服务来注册。
注意:Nacos 的
standalone模式仅用于开发验证。它使用内置的 Derby 数据库,所有数据存储在nacos/data/derby-data目录下。如果你需要清理环境,直接删除此目录即可,比删 MySQL 表快得多。
3.3 Seata TC(事务协调器)部署:配置是成败关键
Seata TC 是整个分布式事务的“大脑”,它的配置正确性直接决定了后续所有操作能否成功。我们使用seata-server-1.7.1.zip,解压后重点修改两个文件:
conf/registry.conf:定义“TC 在哪,怎么找”
这是 Seata 的“寻址配置”。将其中registry和config两大部分全部替换为以下内容:
registry { # 指定注册中心类型为 nacos type = "nacos" nacos { # Nacos 服务地址,必须与你启动的 Nacos 一致 application = "seata-tc-server" serverAddr = "127.0.0.1:8848" group = "SEATA_GROUP" namespace = "" cluster = "default" username = "nacos" password = "nacos" } } config { # 指定配置中心类型也为 nacos type = "nacos" nacos { # 配置中心地址,与 registry 中的 serverAddr 一致 serverAddr = "127.0.0.1:8848" group = "SEATA_GROUP" namespace = "" username = "nacos" password = "nacos" } }这个配置的核心在于两点:一是serverAddr必须精确到IP:端口,不能写localhost(Docker 容器内解析可能失败);二是group统一设为SEATA_GROUP,这是 Seata 官方约定的分组名,所有 Seata 相关配置都放在这里,方便集中管理。
conf/file.conf:定义“TC 怎么工作”
这个文件控制 TC 的核心行为。我们只修改最关键的几个 section:
service { # 全局事务组名,这是业务服务引用的唯一标识 vgroupMapping.my_test_tx_group = "default" # default 集群对应的 nacos 分组 default.grouplist = "127.0.0.1:8091" # 事务会话存储模式,此处用 file(开发验证足够) enableDegrade = false disable = false max.commit.retry.timeout = "-1" max.rollback.retry.timeout = "-1" } store { # 存储模式,开发用 file,生产必须换 db mode = "file" file { dir = "sessionStore" } db { # 生产环境启用此部分,此处注释掉 } }这里vgroupMapping.my_test_tx_group = "default"是重中之重。你的业务微服务在application.yml中配置的seata.tx-service-group必须与此处的my_test_tx_group完全一致,否则服务启动时会报no available service错误。这个值不是随便起的,它代表了一个逻辑上的事务分组,你可以根据业务域来命名,比如order_tx_group、payment_tx_group。
启动 TC 并验证注册
执行./seata-server.sh(Linux/macOS)或seata-server.bat(Windows)。启动成功后,立刻回到 Nacos 控制台,点击“服务管理” -> “服务列表”,你应该能看到一个名为seata-tc-server的服务,健康实例数为 1。这就证明 TC 已成功注册到 Nacos,整个“神经中枢”已经联通。
3.4 业务微服务(模拟订单与库存):Spring Boot 项目实战
我们用两个极简的 Spring Boot 服务来模拟真实场景:order-service(下单)和inventory-service(扣库存)。它们都基于 Spring Cloud Alibaba 2022.0.0.0(兼容 Seata 1.7.1)。
项目结构与依赖(Maven)
pom.xml中必须包含以下核心依赖:
<dependencies> <!-- Spring Boot Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Nacos 服务发现 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- Seata Spring Boot Starter --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <!-- 排除自带的 seata-all,防止版本冲突 --> <exclusions> <exclusion> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> </exclusion> </exclusions> </dependency> <!-- 显式引入匹配的 Seata 版本 --> <dependency> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> <version>1.7.1</version> </dependency> </dependencies>实操心得:Seata 的 starter 依赖经常与 Spring Cloud Alibaba 的版本产生冲突。最稳妥的做法是,像上面这样显式排除 starter 自带的
seata-all,再单独引入你部署的 TC 对应的版本。否则,你可能会遇到NoClassDefFoundError: io/seata/core/context/RootContext这类诡异错误,排查起来极其耗时。
核心配置(application.yml)
这是业务服务与 Seata 对接的“契约”,必须逐字核对:
spring: application: name: order-service # 或 inventory-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 username: nacos password: nacos seata: # 必须与 TC 的 registry.conf 中 vgroupMapping 的 key 一致 tx-service-group: my_test_tx_group # 注册中心配置,指向 Nacos registry: type: nacos nacos: server-addr: 127.0.0.1:8848 group: SEATA_GROUP username: nacos password: nacos # 配置中心配置,同样指向 Nacos config: type: nacos nacos: server-addr: 127.0.0.1:8848 group: SEATA_GROUP username: nacos password: nacos关键代码:一个真实的分布式事务
order-service中的下单接口是全局事务的入口:
@RestController public class OrderController { @Autowired private OrderService orderService; @Autowired private RestTemplate restTemplate; // 用于调用 inventory-service @PostMapping("/order") @GlobalTransactional // 这是 Seata 的魔法注解! public String createOrder(@RequestBody OrderRequest request) { // 1. 创建订单(本地数据库操作) Order order = new Order(); order.setUserId(request.getUserId()); order.setProductId(request.getProductId()); order.setAmount(request.getAmount()); order.setStatus("CREATED"); orderService.create(order); // 2. 远程调用库存服务,扣减库存 String inventoryUrl = "http://inventory-service/inventory/deduct"; Map<String, Object> params = new HashMap<>(); params.put("productId", request.getProductId()); params.put("count", request.getAmount()); // 如果 inventory-service 抛出异常,整个 @GlobalTransactional 将回滚 restTemplate.postForObject(inventoryUrl, params, String.class); return "Order created successfully"; } }inventory-service的扣库存接口则是一个典型的 RM(Resource Manager):
@RestController public class InventoryController { @Autowired private JdbcTemplate jdbcTemplate; // 使用 Seata 增强的 DataSourceProxy @PostMapping("/inventory/deduct") public String deductInventory(@RequestBody Map<String, Object> params) { Long productId = Long.valueOf(params.get("productId").toString()); Integer count = Integer.valueOf(params.get("count").toString()); // 这条 SQL 会被 Seata 自动拦截,生成 undo_log String sql = "UPDATE inventory SET stock = stock - ? WHERE product_id = ? AND stock >= ?"; int updated = jdbcTemplate.update(sql, count, productId, count); if (updated == 0) { throw new RuntimeException("Insufficient stock for product: " + productId); } return "Inventory deducted"; } }注意:
JdbcTemplate必须注入的是DataSourceProxy类型的 Bean,而不是原始的HikariDataSource。这是 Seata 能够自动代理 SQL 的前提。Spring Boot Starter 会自动完成这个代理,你无需手动配置,但必须确保application.yml中的spring.datasource配置正确。
3.5 数据库初始化:undo_log 表是 Seata 的“后悔药”
Seata 的 AT 模式依赖一个特殊的表undo_log,它存储了每次 SQL 修改前后的快照,是二阶段回滚的唯一依据。这个表必须在每个业务数据库中手动创建,Seata 不会自动建表。
以 MySQL 为例,执行以下 SQL(注意:branch_id字段长度必须为 128):
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 AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;这个表的结构是 Seata 强制约定的,字段名、类型、索引都不能更改。rollback_info是 BLOB 类型,里面存储的就是 JSON 格式的 beforeImage 和 afterImage。你可以手动查询这张表,看到 Seata 在每次事务中记录的详细快照,这是调试和理解机制的最直接证据。
4. 实操过程与核心环节验证:从成功到故障的全流程剖析
4.1 第一次成功调用:见证“全局事务”如何流转
启动order-service和inventory-service。确保它们都能在 Nacos 控制台的“服务列表”中看到,并且健康实例数为 1。
使用 curl 发起一个下单请求:
curl -X POST http://localhost:8080/order \ -H "Content-Type: application/json" \ -d '{"userId":1,"productId":1001,"amount":10}'此时,打开order-service的日志(logs/seata.log),你会看到类似这样的关键日志流:
[INFO] 2024-05-20 14:23:45.123 [http-nio-8080-exec-1] i.s.c.t.a.GlobalTransactionScanner - Begin new global transaction [192.168.1.100:8091:4567890123456789] [DEBUG] 2024-05-20 14:23:45.210 [http-nio-8080-exec-1] i.s.r.d.e.ExecuteTemplate - SQL: INSERT INTO `order` ... , Parameters: [1, 1001, 10, 'CREATED'] [DEBUG] 2024-05-20 14:23:45.305 [http-nio-8080-exec-1] i.s.r.d.e.ExecuteTemplate - Branch Register: xid=192.168.1.100:8091:4567890123456789, branchType=AT, resourceId=jdbc:mysql://127.0.0.1:3306/order_db, lockKey=order:1 [INFO] 2024-05-20 14:23:45.422 [http-nio-8080-exec-1] i.s.c.t.a.GlobalTransactionScanner - Registered branch transaction successfully. XID: 192.168.1.100:8091:4567890123456789, Branch ID: 1234567890123456789 [DEBUG] 2024-05-20 14:23:45.555 [http-nio-8080-exec-1] i.s.r.d.e.ExecuteTemplate - SQL: UPDATE inventory SET stock = stock - ? WHERE product_id = ? AND stock >= ?, Parameters: [10, 1001, 10] [INFO] 2024-05-20 14:23:45.678 [http-nio-8080-exec-1] i.s.c.t.a.GlobalTransactionScanner - Committing global transaction [192.168.1.100:8091:4567890123456789] [INFO] 2024-05-20 14:23:45.789 [rpcDispatch_RM_1] i.s.r.d.u.UndoLogManagerImpl - Successfully deleted undo log.这条日志链清晰地展示了 AT 模式的完整生命周期:
- 全局事务开启(Begin):
GlobalTransactionScanner生成唯一的XID(格式为IP:Port:TransactionId),这是整个事务的身份证。 - 分支注册(Branch Register):
order-service作为 TM(Transaction Manager)向 TC 注册一个分支,lockKey=order:1表示它锁定了order表的第 1 条记录。 - SQL 执行与快照生成:
ExecuteTemplate执行 SQL,并自动生成undo_log记录。 - 全局提交(Commit):TC 收到所有分支的
commit请求后,向各 RM 下发指令,RM 执行真正的COMMIT并清理undo_log。
此时,检查数据库:order表新增了一条记录,inventory表中product_id=1001的stock字段减少了 10。一切完美。
4.2 主动制造故障:验证回滚的健壮性
成功的事务只是开始,验证回滚才是关键。我们来模拟一个经典场景:库存不足。
修改inventory-service的扣库存 SQL,将AND stock >= ?条件去掉,然后手动将数据库中product_id=1001的stock设为 5。再次发起下单请求,传入amount=10:
curl -X POST http://localhost:8080/order \ -H "Content-Type: application/json" \ -d '{"userId":1,"productId":1001,"amount":10}'这次,inventory-service会抛出RuntimeException("Insufficient stock...")。观察日志:
[ERROR] 2024-05-20 14:35:22.111 [http-nio-8080-exec-2] i.s.c.t.a.GlobalTransactionScanner - Failed to execute global transaction ... [INFO] 2024-05-20 14:35:22.222 [http-nio-8080-exec-2] i.s.c.t.a.GlobalTransactionScanner - Rolling back global transaction [192.168.1.100:8091:9876543210987654321] [DEBUG] 2024-05-20 14:35:22.333 [rpcDispatch_RM_1] i.s.r.d.u.UndoLogManagerImpl - Found undo log, will rollback. [INFO] 2024-05-20 14:35:22.444 [rpcDispatch_RM_1] i.s.r.d.u.UndoLogManagerImpl - Rollback success.关键点来了:Rollback success日志之后,检查数据库。你会发现order表中刚才那条记录消失了,而inventory表的stock也恢复到了修改前的 5。这证明 Seata 成功执行了逆向操作:它读取undo_log中的beforeImage,生成了一条UPDATE inventory SET stock = ? WHERE product_id = ?的 SQL,将数据还原。
实操心得:
undo_log表的log_status字段是判断回滚状态的关键。0表示正常,1表示已回滚。如果回滚失败,这个字段会变成2(Failed),此时你需要手动介入,根据rollback_info中的 JSON 手动修复数据。这也是为什么生产环境必须监控undo_log表的log_status。
4.3 深度调试:利用 Nacos 配置中心动态调整参数
现在,我们来体验 Nacos 配置中心的威力。假设你发现事务超时太短(默认 60 秒),导致一些复杂的业务流程总是被强制回滚。你不需要重启任何服务,只需在 Nacos 控制台操作:
进入“配置管理” -> “配置列表”,点击右上角“+ 新建配置”。
Data ID 填写
seataServer.properties,Group 选择SEATA_GROUP,配置格式选Properties。在配置内容中,粘贴以下内容:
service.vgroupMapping.my_test_tx_group=default store.mode=file # 将全局事务超时时间从 60 秒改为 300 秒 service.default.grouplist=127.0.0.1:8091 service.enableDegrade=false service.disable=false service.max.commit.retry.timeout=-1 service.max.rollback.retry.timeout=-1 client.rm.report.success.enable=true client.tm.commit.retry.count=3 client.tm.rollback.retry.count=3 # 新增:全局事务超时时间(毫秒) client.rm.lock.retry.interval=10 client.rm.lock.retry.times=30 client.rm.lock.retry.policy.branch-rollback-on-conflict=true # 关键:设置全局事务超时 client.rm.sql-parser.cache-size=2000 client.rm.report.success.enable=true # 这里是重点 client.rm.lock.retry.interval=10 client.rm.lock.retry.times=30 # 最终生效的超时配置 client.rm.lock.retry.interval=10 client.rm.lock.retry.times=30 # 正确的超时配置项 client.rm.lock.retry.interval=10 client.rm.lock.retry.times=30 # Seata 1.7.1 的正确超时配置 client.rm.lock.retry.interval=10 client.rm.lock.retry.times=30 # 实际上,超时由 TM 控制,需在业务服务配置 # 因此,我们在 order-service 的 application.yml 中添加: # seata.service.vgroup-mapping.my_test_tx_group=default # seata.client.rm.lock.retry.interval=10 # seata.client.rm.lock.retry.times=30 # 但更直接的是在 Nacos 中发布一个通用配置 # 正确做法:在 Nacos 中创建 Data ID 为 seataServer.properties 的配置,内容为: service.vgroupMapping.my_test_tx_group=default service.default.grouplist=127.0.0.1:8091 # 全局事务超时(毫秒),这是 TC 端的配置 service.default.timeout=300000点击“发布”。
几秒钟后,查看order-service的日志,你会看到一行Refreshed config from nacos。此时,再发起一个需要长时间处理的请求(比如在order-service的createOrder方法中加一个Thread.sleep(200000)),它不会再被 60 秒超时中断,而是会耐心等待 300 秒。这就是配置中心带来的敏捷性。
5. 常见问题与排查技巧实录:那些踩过的坑和独家经验
5.1 经典问题速查表
| 问题现象 | 可能原因 | 排查命令/步骤 | 解决方案 |
|---|---|---|---|
服务启动报错:no available service | tx-service-group配置不一致;Nacos 地址不通;Nacos 认证失败 | curl -X GET "http://127.0.0.1:8848/nacos/v1/ns/service/list?groupName=SEATA_GROUP" | 检查application.yml中seata.tx-service-group是否与 TC 的registry.conf中vgroupMapping的 key 完全一致;用telnet 127.0.0.1 8848测试连通性;确认 Nacos 的username/password正确 |
事务不回滚,order表有数据但inventory没扣 | @GlobalTransactional注解未生效;inventory-service未正确配置 Seata DataSource | 查看order-service启动日志,搜索GlobalTransactionScanner;检查inventory-service的jdbcTemplateBean 类型 | 确保@GlobalTransactional在 public 方法上;确保inventory-service的application.yml中spring.datasource配置了正确的 URL 和 driver-class-name;在inventory-service的@PostConstruct方法中打印jdbcTemplate.getClass().getName(),确认是io.seata.rm.datasource.DataSourceProxy |
undo_log表中有数据但log_status=2(Failed) | 数据库主键冲突;beforeImage与当前数据不一致;网络超时 | SELECT * FROM undo_log WHERE log_status = 2 LIMIT 1;;解析rollback_info字段的 JSON | 手动执行rollback_info中的sqlUndo语句;检查业务 SQL 是否有ON DUPLICATE KEY UPDATE等特殊语法;增加client.rm.lock.retry.times |
Nacos 控制台看不到seata-tc-server服务 | TC 启动脚本参数错误;Nacos 的namespace不匹配 | `ps |