news 2026/10/13 23:05:12

Nacos 1.2.0整合Seata 1.2.0:分布式事务配置实战与踩坑记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Nacos 1.2.0整合Seata 1.2.0:分布式事务配置实战与踩坑记录

最近在一个微服务拆分改造项目里,我撞上了分布式事务这道坎。用户下单这个操作,牵扯订单服务、库存服务、余额服务,任何一个成功而另一个失败,账就对不上。考虑团队当时已经在用 Nacos 1.2.0 做注册中心和配置中心,顺势把 Seata 1.2.0 也接进来,让注册发现、配置管理、分布式事务三件事全部走同一套基础设施,是成本最低的路。折腾了几天,从服务端启动到客户端事务生效,中间踩了无数个配置坑,这篇就把 Nacos 1.2.0 整合 Seata 1.2.0 的完整过程和爬坑实录写出来,给正在搞 Seata 的同行做个参照。

1. 整合前必须想清楚的事

1.1 为什么要用 Seata,而不是自己写事务

微服务架构下,每个服务各管一个数据库,本地事务天然无法跨库。你不可能靠删库重跑来弥补数据不一致。Seata 做的事并不神秘:它把一次业务操作拆成两阶段提交,第一阶段各分支服务先执行业务 SQL 并且记录回滚日志,第二阶段由 Seata Server 统一决定提交或回滚。业务代码几乎无侵入,只需要在入口方法上加一个 @GlobalTransactional 注解,这对已经写完的接口来说改动量很小。

1.2.0 版本是 Seata 比较稳定的一代,文档齐全,社区讨论多,问题排查资料也容易搜到。Nacos 1.2.0 则是一个经典版本,很多公司的 Spring Cloud Alibaba 体系都停在它上面。两个版本配套使用的例子很多,网上踩坑记录也多,真出问题不会孤立无援。

1.2 先分清 Seata 里的三个角色

很多人在配置阶段就把 TM、RM、TC 搞混了,后面排查问题会非常痛苦。

  • TC(Transaction Coordinator):事务协调者,也就是 Seata Server,负责全局事务的开启、提交和回滚。
  • TM(Transaction Manager):事务管理器,业务服务里加了 @GlobalTransactional 注解的就是 TM,它告诉 TC 我开了个全局事务。
  • RM(Resource Manager):资源管理器,业务服务里被代理的数据源就是 RM,它执行本地事务并且向 TC 注册分支事务。

整个调用链上的每个业务服务,既是 TM 的调用方,也可能同时是 RM。Seata Server 本身不参与业务,它只负责接收 TM 的开始、RM 的分支注册和最后的决议通知。理解这个关系之后,你就知道配置时哪些东西要给服务端、哪些要给客户端。

1.3 版本兼容性和依赖风险

在动手之前,我建议先把你当前项目的 Spring Boot、Spring Cloud、Spring Cloud Alibaba 版本列出来。Seata 1.2.0 的 seata-spring-boot-starter 官方适配 Boot 2.2.x 到 2.3.x 这个区间,如果你项目在 Boot 2.4 以上,自动装配很可能出问题。我见过有人在 Boot 2.6 里硬上 Seata 1.2.0,结果配置项绑定不上,服务起不来。

另外 Seata 1.2.0 依赖库比较老,netty、kotlin、protobuf、guava 都和现在主流版本有偏差。引入之前,建议先跑一下 mvn dependency:tree 看清楚依赖冲突,不要等问题出现了再去补排除。

2. 服务端配置实操

2.1 先把 Nacos 1.2.0 准备到位

如果你的 Nacos 还是默认安装、默认配置,那基本可以直接长这个样子启动:

cd nacos/bin sh startup.sh -m standalone

Nacos 1.2.0 默认会把配置数据存在内嵌的 Derby 里,单机演示没问题,但团队开发时重启一次配置就丢,非常坑。所以我强烈建议把 Nacos 的存储切到 MySQL,改 conf/application.properties:

spring.datasource.platform=mysql db.num=1 db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=3000&socketTimeout=3000&autoReconnect=true&useUnicode=true db.user=root db.password=root

改完之后,先把 conf 目录下自带的 nacos-mysql.sql 导入到 nacos_config 这个库里,然后再启动 Nacos。这一步很多人忽略,结果 Nacos 控制台打开配置列表是空的,还以为刷出来了,实际数据写到了别处。

2.2 Seata Server 的 registry.conf 怎么填

Seata Server 解压之后,conf 目录下有两个核心文件:registry.conf 和 file.conf。先说 registry.conf,它管的是 Seata Server 自己如何注册到 Nacos、以及从 Nacos 拉配置。完整内容填成:

registry { type = "nacos" nacos { application = "seata-server" serverAddr = "localhost:8848" namespace = "" cluster = "default" username = "" password = "" } } config { type = "nacos" nacos { serverAddr = "localhost:8848" namespace = "" group = "SEATA_GROUP" username = "" password = "" } }

注意几个关键点:

  • application 这个值默认是 seata-server,客户端发现服务时会找服务名为 seata-server 的实例,别乱改。
  • cluster 默认是 default,客户端事务分组映射最终也会指向 default,两者必须对上。
  • namespace 留空代表使用 Nacos 的 public 命名空间。如果你项目用了自定义命名空间,这里必须填和客户端一致的 namespace ID,否则两边各认各的,服务能被 Nacos 看到但客户端就是连不上。

2.3 事务存储改成 db 模式

Seata Server 默认用 file 模式保存全局事务会话,简单但不利于生产。既然有 Nacos,建议直接用 db 模式,在 file.conf 中把 store 配置改成:

store { mode = "db" db { datasource = "druid" dbType = "mysql" driverClassName = "com.mysql.cj.jdbc.Driver" url = "jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai" user = "root" password = "root" minConn = 5 maxConn = 30 globalTable = "global_table" branchTable = "branch_table" lockTable = "lock_table" queryLimit = 100 maxWait = 5000 } }

同时你得先建好 seata 库,并执行 Seata 发行包 conf 目录里的 db_store.sql 脚本,它会创建 global_table、branch_table、lock_table 三张表。我建议自己把这三张表手动建一遍,不要依赖脚本报错之后才看,毕竟 MySQL 版本和 sql_mode 差异可能导致脚本执行一半就中断。

提示:db 模式下,Seata Server 一旦在 Nacos 配置中心读到了 seata.properties,会优先使用配置中心里的 store 配置,本地的 file.conf 反而成为兜底。

2.4 在 Nacos 上发布 seata.properties

Seata 1.2.0 合并了配置项,会把所有相关配置都放到同一个 dataId 里。建议在 Nacos 控制台的 public 命名空间下,创建一个 dataId 为 seata.properties、分组为 SEATA_GROUP 的配置。内容参考:

service.vgroupMapping.my_test_tx_group=default store.mode=db store.db.datasource=druid store.db.dbType=mysql store.db.driverClassName=com.mysql.cj.jdbc.Driver store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai store.db.user=root store.db.password=root store.db.minConn=5 store.db.maxConn=20 store.db.globalTable=global_table store.db.branchTable=branch_table store.db.lockTable=lock_table store.db.queryLimit=100 store.db.maxWait=5000

这里就出现第一个坑:本地 file.conf 里事务组映射用的是service.vgroup_mapping.my_test_tx_group,带下划线;Nacos 配置中心用的是service.vgroupMapping.my_test_tx_group,驼峰。大小写和下划线不一致是 Seata 1.2.0 最容易踩的雷。我建议以配置中心这份为准,本地 file.conf 保持原样作为兜底,但不要两边配置不同值。

2.5 启动 Seata Server 并验证注册状态

配置都改好之后,直接执行启动脚本:

cd seata/bin sh seata-server.sh -p 8091 -m db

-m db 参数最好显式带一下,避免某些版本按 file 模式启动。启动完成后去 Nacos 控制台的服务列表里看,应该能看到一个名为 seata-server 的服务,下面有一个集群名为 DEFAULT 的实例。能看见这一步,服务端就算通了。

如果看不到实例,优先看 seata-server 的日志,常见原因包括 Nacos 地址写错、namespace 填了不存在的 ID、registry.conf 配置没生效。我有一次就是 namespace 填了自定义空间 ID,Nacos 服务列表里切到该空间能看到 seata-server,但客户端在 public 空间找,自然找不到。

3. 客户端接入配置

3.1 依赖引入与冲突排除

客户端业务服务需要引入 Seata 的 Spring Boot Starter。以 Maven 为例:

<dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>1.2.0</version> <exclusions> <exclusion> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> </exclusion> <exclusion> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> </exclusion> </exclusions> </dependency>

为什么排除 netty-all?因为 Spring Cloud 全家桶里各个组件用的 netty 版本经常不一致,Seata 1.2.0 依赖的 netty 版本比较老,很容易把项目原有的 netty 版本覆盖掉,导致运行时出现 ChannelOption、HashedWheelTimer 这类 NoClassDefFoundError。排除之后,用项目现有的 netty 版本即可。kotlin-stdlib 也是类似情况,Seata 内部用了 kotlin 运行时的功能,如果项目主版本太低,就会 NoSuchMethodError。

3.2 不需要客户端再放 registry.conf

Seata 1.2.0 的 Spring Boot Starter 支持直接在 application.yml 里写客户端配置,比在业务服务里塞一个 registry.conf 更干净。我推荐这种方式,避免多个微服务各自复制文件、改漏一个就出了问题。参考配置如下:

spring: application: name: order-service cloud: nacos: discovery: server-addr: localhost:8848 seata: enabled: true application-id: ${spring.application.name} tx-service-group: my_test_tx_group enable-auto-data-source-proxy: true registry: type: nacos nacos: server-addr: localhost:8848 namespace: "" cluster: default config: type: nacos nacos: server-addr: localhost:8848 namespace: "" group: SEATA_GROUP service: vgroup-mapping: my_test_tx_group: default disable-global-transaction: false

这里 tx-service-group 和 service.vgroup-mapping 里的 key 必须一致。它的作用机制是:业务服务这个 TM 告诉 TC,我属于 my_test_tx_group 这个事务分组;TC 根据 vgroupMapping 找到对应的后端集群名 default;然后去 Nacos 注册中心里找 cluster 为 default 的 seata-server 实例。这个链条一环不匹配,就会报找不到服务。

3.3 业务代码接入事务

客户端配置完之后,业务代码改动非常小。在需要进行全局事务的入口方法上加上注解:

@GlobalTransactional public void createOrder(OrderDTO orderDTO) { orderMapper.insert(orderDTO); stockClient.deductStock(orderDTO.getProductId()); balanceClient.deductBalance(orderDTO.getUserId()); }

注意这个入口方法的调用方必须是另一个 Bean 或者外部接口,不能是同一个类内部方法调用。因为 @GlobalTransactional 是基于 Spring AOP 代理的,类内部 this 调用不会经过代理,事务自然不会生效。我在实际项目中遇到过一次,把 createOrder 放在了 OrderService 里,由 OrderController 调用没问题;后来重构的时候在同一个类里新增了一个方法,内部去调 createOrder,事务就完全没生效,排查了很久才反应过来。

3.4 客户端需要准备 undo_log 表

AT 模式的回滚依赖业务数据库里的 undo_log 表。每个参与全局事务的业务库都要建,不能只建在某个库里。建表 SQL 建议用 Seata 发行包里的 db_undo_log.sql,这里也贴一个标准版本:

CREATE TABLE IF NOT EXISTS `undo_log` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `branch_id` BIGINT NOT NULL, `xid` VARCHAR(100) NOT NULL, `context` VARCHAR(128) NOT NULL, `rollback_info` LONGBLOB NOT NULL, `log_status` INT NOT NULL, `log_created` DATETIME NOT NULL, `log_modified` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`) ) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4;

如果业务库没有 undo_log 表,事务执行阶段是正常的,但一旦需要回滚,Seata 会尝试往 undo_log 表写入回滚记录,直接报 Table 'xxx.undo_log' doesn't exist。这个错误在测试环境回滚场景下才会暴露,很隐蔽。

4. 爬坑过程实录

4.1 服务端能注册,客户端连不上

我把 Seata Server 启动后,Nacos 服务列表里已经能看到 seata-server 实例了,但业务服务一启动就报can not connect to services-server。

排查过程:

  • 先确认业务服务所在的机器能不能 telnet 到 Seata Server 的 8091 端口,网络不通就别往后查了。
  • 再确认 Seata Server 的 registry.conf 里 namespace 和 cluster 是否和客户端一致。这是高频坑,服务端注册到了 namespace A,客户端配置里 namespace 留空,两边各看各的。
  • 最后看 Seata Server 日志,确认客户端请求有没有到达服务端,如果连日志都没有,肯定是网络或注册发现问题。

注意:Seata Server 的 registry.conf 里 nacos.application 不要随便改成业务名,客户端从 Nacos 拿到的服务名必须是 seata-server,改了名字客户端就找不到了。

4.2 提示 No available service in cluster default

这个报错在客户端日志里出现时,说明客户端已经找到了注册中心,但没找到符合条件的 Seata Server 实例。

常见原因两种:

  • Seata Server 注册时 cluster 值不是 default。检查服务端 registry.conf,和客户端 service.vgroup-mapping 里映射的值是否一致。
  • Seata Server 注册到了别的 namespace,客户端却用 public 空间去查。这种问题在 Nacos 控制台切到对应 namespace 一眼就能看出来。

我在测试环境里遇到过一次,因为服务端 registry.conf 里 cluster 被之前的同事改成了 "SEATA_GROUP",而客户端 vgroupMapping 指向 default,结果两边互相找不到。记住,cluster 是集群名,和配置中心的分组 SEATA_GROUP 完全是两个东西,不要混在一起。

4.3 配置中心读不到 seata.properties

客户端配置 config.type 指向 Nacos 后,它会去拿 dataId 为 seata.properties、group 为 SEATA_GROUP 的配置。如果你只改了 application.yml 里的 seata.config.nacos.group,但 Nacos 上其实没创建这个配置,客户端会退回到本地 file.conf 读取。这时候最容易出现的情况是:本地 file.conf 里的 store 配置和服务端不一致,客户端以为自己在 file 模式下工作。

建议的做法是:在 Nacos 控制台明确创建 seata.properties,然后重启 Seata Server 和业务服务,再看 registry 模式是否真正切换成 Nacos。如果不想用配置中心,也可以把 config.type 改成 file,让业务服务只在本地 file.conf 读取,哪种方式都不推荐混着来。

4.4 本地 file.conf 和配置中心命名差异

Seata 1.2.0 这个版本在配置项命名上有点历史遗留:本地 file.conf 的配置 key 是下划线风格:

service.vgroup_mapping.my_test_tx_group = "default"

而 Nacos 配置中心里,同一个含义的 key 是驼峰:

service.vgroupMapping.my_test_tx_group=default

如果你在本地 file.conf 和配置中心都配置了事务分组,但值不一样,客户端会以配置中心为准,本地配置被覆盖。反过来说,如果配置中心没配置,客户端读本地 file.conf 的 vgroup_mapping。我在一个项目里看到有人把本地 file.conf 里改成驼峰,启动时直接报非法配置项,这个别折腾,文件里该用什么格式就用什么格式。

4.5 依赖冲突导致服务启动失败

Seata 1.2.0 引入后,我的某个服务启动时直接抛出了NoSuchMethodError: kotlin.collections.ArraysKt.copyInto。查下来是 Seata 内部依赖 kotlin-stdlib 版本比较高,而项目主 pom 里锁了一个老版本。把 kotlin-stdlib 排除掉,让 Spring Boot 依赖管理统一版本之后就好了。

还有一个常见的是 netty 冲突,报错类型五花八门。最直接的办法是在引入 Seata 时先排除 netty-all 和 kotlin-stdlib,让项目主导版本。如果项目里用了同版本的 netty,业务代码不受影响;如果使用的是 Spring Cloud Gateway 这类重度依赖 netty 的组件,尤其要注意。

4.6 事务名称过长或参数类型问题

在数据库里如果看到事务回滚失败,或者 Seata 日志里出现Could not found any open transaction,不一定配置的问题,还可能是表字段和数据类型不匹配。比如 MySQL 8 的 JDBC 驱动要求 url 里带 serverTimezone,否则时间字段解析会报错,连带 undo_log 的写入失败。我踩过最恶心的一次是业务表主键用了 varchar 但存的是数字字符串,Seata 的全局锁解析主键时类型转换出错。遇到这类问题,先看 Seata 所在服务端的完整异常堆栈,别盯着客户端日志猜。

4.7 多数据源场景下的事务边界

如果业务服务里配置了多个数据源,Seata 默认只会代理主数据源,其他数据源参与的操作不会被纳入全局事务。项目里有一个服务同时连主库、订单库、报表库,结果报表库的操作回滚不了。

解决办法是用 Seata 手动包装数据源:

@Bean @Primary public DataSource seataDataSource(DataSource dataSource) { return new DataSourceProxy(dataSource); }

对于不需要纳入全局事务的数据源,保持原样即可。这么一改,需要事务的库走 Seata 代理,不需要的库不参与,边界清晰很多。

4.8 全局锁等待超时

高并发测试的时候,全局事务之间会抢同一行数据的锁,如果前一个事务还没提交,后一个事务会一直重试拿锁。默认锁重试间隔和次数在压测时不够,会出现Lock wait timeout的异常。我在 application.yml 里调大了参数:

seata: client: rm: lock: retry-interval: 10 retry-times: 30

retry-interval 单位是毫秒,调大到 10 毫秒,重试 30 次,基本能满足测试环境的并发要求。生产环境要根据实际响应耗时来压测调整,不要盲目加太大,否则请求排队会拖垮接口。

4.9 Nacos 重启后 Seata 连接不上

Nacos 1.2.0 本身是单机能力有限的,如果 Nacos 重启期间 Seata Server 和业务服务都还活着,它们和 Nacos 的长连接会断开。Nacos 恢复后,Seata Server 大概率能自己重新注册,但客户端不一定能及时感知。最省心的处理办法:Nacos 恢复后,按顺序重启 Seata Server,再重启业务服务。不要在生产环境随意重启 Nacos,至少要把这个操作排进灰度发布流程里。

5. 常见问题速查表

现象大概率原因处理方式
服务端不能注册到 Nacosregistry.conf 地址、namespace 错误核对 serverAddr、namespace,看 seata-server 日志
客户端报 can not connect网络不通或服务端未启动检查 8091 端口连通性
客户端报 No available servicecluster 或 namespace 不一致对齐服务端 cluster 和客户端 vgroupMapping
配置中心读不到配置dataId/group 写错或未创建在 Nacos 上创建 seata.properties
事务没回滚@GlobalTransactional 没生效检查注解位置、同类内部调用、数据源代理
回滚时报 undo_log 表不存在业务库没建 undo_log每个业务库执行 undo_log 建表脚本
启动报 NoSuchMethodErrorkotlin/netty 等依赖冲突排除冲突依赖,统一版本
全局锁超时并发下重试参数不足调大 lock.retry-interval 和 retry-times
MySQL 时间字段报错JDBC url 缺 serverTimezone增加 serverTimezone=Asia/Shanghai

6. 最后的实操心得

这套 Nacos 1.2.0 和 Seata 1.2.0 的组合,到现在为止我维护过好几套环境,整体稳定性没让我失望,但配置环节确实容易反复踩坑。个人体会是:配置这个东西,每改一个变量就要重启验证一次,不要一次改一堆,不然出了问题根本不知道是哪一项影响的。我习惯的顺序是 Nacos 先起来,Nacos 控制台能看到配置;再起 Seata Server,Nacos 能看到 seata-server 实例;最后起业务服务,日志里出现 Global Transaction 相关的注册信息,再开始测分布式事务。

另外,如果你是第一次接 Seata,别急着直接改造真实业务。搭一个最小闭环:订单服务调库存服务,库存服务里故意抛异常,验证全局回滚是否生效。这个 Demo 环境我保留了很久,每次换个新环境、升个版本,都先用它跑一遍,确认没问题再放业务进去。分布式事务这种功能,上线后出问题的代价远大于前期准备的时间,值得多花半小时把链路完全打通。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 23:04:36

企业微信外部群API推送开发实战:从人工搬运到系统投递

做外部群运营的团队&#xff0c;多多少少都会遇到一个尴尬阶段&#xff1a;群建了不少&#xff0c;活跃度也还行&#xff0c;但每次发通知、做活动、发福利&#xff0c;都得靠运营同事手动复制粘贴到一群群里。群少的时候看不出问题&#xff0c;群一多&#xff0c;错发、漏发、…

作者头像 李华
网站建设 2026/10/13 23:04:08

编程知识骨架:可嵌入工程实践的技术手册体系

简介&#xff1a;本资源是C语言中文网出品的13本技术电子书合集&#xff0c;面向编程初学者与中级开发者&#xff0c;覆盖Java基础与Swing界面开发、23种经典设计模式解析、Python入门、Go语言快速上手等核心方向&#xff0c;同时延伸至CSS3、GCC编译、Linux vi、Qt5、TensorFl…

作者头像 李华
网站建设 2026/10/13 23:03:55

高职仓储分拣优化中的数据分析实践

1. 高职仓储分拣优化中的数据分析价值在物流仓储领域&#xff0c;分拣环节的效率直接影响着整体运营成本。我曾参与过多个高职院校物流实训基地的仓储优化项目&#xff0c;发现许多管理者仍依赖经验决策&#xff0c;而忽视了数据分析带来的精准洞察。以某高职院校实训仓库为例&…

作者头像 李华
网站建设 2026/10/13 23:03:54

流水灯程序与Proteus仿真:从原理到避坑的完整指南

简介&#xff1a;这是一份面向嵌入式初学者与单片机课程学习者的流水灯教学资源&#xff0c;将C语言程序与Proteus电路仿真结合&#xff0c;帮助读者在无需搭建实物硬件的情况下理解微控制器I/O端口控制、循环结构与延时函数的实现思路。压缩包共13个文件&#xff0c;约24KB&am…

作者头像 李华
网站建设 2026/10/13 23:01:38

图像分析数据结构详解:从RAG到四叉树与金字塔实战

读图像处理类教材&#xff0c;很多人习惯直接翻到滤波、边缘检测、形态学这些“看起来很能出效果”的章节&#xff0c;而把“图像分析的数据结构”这一章当理论过一遍就算。坦白讲&#xff0c;第一遍读它时我也是这个心态&#xff0c;直到后来在分割项目里被区域合并算法卡住&a…

作者头像 李华
网站建设 2026/10/13 22:59:13

Servlet核心机制与生命周期详解:从HTTP请求到Java方法的完整链路

Servlet这东西&#xff0c;说起来有点“老”&#xff0c;但如果你想搞明白Java Web到底是怎么跑起来的&#xff0c;它还真躲不开。我带团队和面试新人的时候发现一个现象&#xff1a;不少人上来就学Spring Boot&#xff0c;写接口已经非常熟练&#xff0c;但你把问题往回问一层…

作者头像 李华