恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Docker部署Nacos+Seata:从版本选型到分布式事务排错全指南
首页
资讯中心
/
Docker部署Nacos+Seata:从版本选型到分布式事务排错全指南
Docker部署Nacos+Seata:从版本选型到分布式事务排错全指南
发布时间:2026/10/20 14:41:44
有时候一个看起来只有几个容器的部署任务真正花时间的不是敲命令而是理清楚容器之间、组件之间的依赖关系。这不前段时间我在一个微服务项目里要搭分布式事务环境需求很明确docker安装nacosseata但是真正操作的时候才发现版本、网络、配置中心、事务分组每一层都有坑。这篇文章就把我从零到一把 Nacos 和 Seata 跑在 Docker 里的全过程记录下来包括命令、配置文件以及排错思路希望能帮你少走点弯路。1. 为什么这套组合值得用Docker部署两个组件分别解决什么问题先说个很多人没想明白的点Nacos 和 Seata 是两回事。Nacos 负责服务发现和配置管理可以理解成整个微服务架构的通讯录和设置中心Seata 负责分布式事务是保证多个服务之间数据一致性的协调员。它们经常同时出现是因为 Seata 需要一个地方来注册自己、拉取配置而 Nacos 正好提供了这个能力。很多人误以为装了 Seata 就必须装 Nacos其实 Seata 也支持别的注册中心但 Nacos 确实是最常见的搭配尤其是 Spring Cloud Alibaba 的生态里。在 Docker 环境下这两个组件的部署有几个直接的痛点第一Nacos 的配置项很多环境变量和数据库设置容易漏第二Seata 的配置分成注册和配置中心两块改错一个文件整个服务就起不来第三容器网络里注册的 IP 可能导致客户端连不上第四数据持久化问题不挂载目录的话容器一删配置全部丢失。这篇文章会围绕这几个痛点展开。这套环境适合谁来参考如果你是刚接触微服务、想在学习环境快速搭一套注册中心加分布式事务环境的人或者你在公司内网要部署一套测试环境但不想手动改一堆配置文件再或者你在排查为什么 Seata 客户端报了找不到事务分组这类问题——这篇文章都能给你一个完整的参考路径。2. 动手前先定案版本组合、网络规划与目录挂载我见过太多人上来就docker pull结果 Nacos 和 Seata 版本不兼容或者 Seata 客户端和服务端版本差太多导致分支事务类型不匹配。所以第一步不是拉镜像而是定版本。2.1 版本组合的兼容矩阵我实测下来比较稳的组合是Nacos 2.2.3或 2.3.2搭配 Seata Server 1.6.1。如果是生产环境想用更新的版本1.8.0 也是可选但客户端最好用 1.6.x 或 1.7.x 对应版本。Spring Cloud Alibaba 的版本也很关键旧版本客户端往新版本服务端注册时有时候会报invalid registry之类的错误。组件推荐版本说明Nacos Server2.2.3 / 2.3.22.x 才支持 gRPC 端口9848/9849Seata Server1.6.1 / 1.8.01.6.1 最经典1.8.0 对 AT 模式更友好Spring Boot2.5.x / 2.6.x与 Seata starter 兼容性较好Spring Cloud Alibaba2021.0.5.0 左右版本号与服务端版本需匹配MySQL5.7 / 8.0必须初始化 Nacos 和 Seata 的数据库这里给个忠告Nacos 的 1.x 版本和 2.x 版本在客户端兼容性上差别很大尽量别混用。我遇到过 Nacos Server 用 2.2.3客户端依赖还是 1.4.x 的情况服务注册能成功但配置订阅偶尔失效查起来非常费劲。2.2 网络规划docker network 比端口映射更好用为什么推荐先建一个 Docker 网络因为 Nacos 和 Seata 之间要互相访问用容器名作为 host 是最方便的方式。端口映射是给宿主机访问用的而容器间通信走的是 Docker 内部网络。如果你只做了端口映射却没让它们加入同一个网络那 Seata 里配置 Nacos 地址时就只能写宿主机 IP虽然也能跑但不够干净也容易在跨主机时出问题。docker network create micro-net这条命令创建一个桥接网络。后面启动的 Nacos 容器和 Seata 容器都加入这个网络互相之间直接用服务名访问即可。比如 Nacos 容器名为nacos-server那么 Seata 里配置localhost或者轮流 IP 都不需要了直接写nacos-server就行。2.3 目录挂载规划容器一旦重建配置就丢这是 Docker 最常见的坑。所以我建议在宿主机上先建好目录把 Nacos 的配置目录、日志目录还有 Seata 的配置目录、日志目录都挂载出来。mkdir -p /opt/docker-volume/nacos/{logs,conf} mkdir -p /opt/docker-volume/seata/{logs,conf}这样以后改配置不用进容器直接在宿主机改完重启容器即可。如果是测试环境挂载 logs 目录就够了conf 目录是否挂载可以看情况但生产环境建议全部挂载。3. 启动Nacos容器数据库初始化、环境变量与鉴权Nacos 的 Docker 启动其实不算难但很多教程故意省略了数据库初始化这一步。Nacos 默认会用内嵌数据库重启数据就没了而且集群模式下内嵌库根本不支持。所以我们要做的第一件正事是准备 MySQL。3.1 初始化 Nacos 的数据库和账号Nacos 官方提供了数据库初始化脚本在 GitHub 仓库的distribution/conf/nacos-mysql.sql里。下载这个脚本在 MySQL 中创建名为nacos_config的库执行脚本CREATE DATABASE nacos_config DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE USER nacos% IDENTIFIED BY nacos; GRANT ALL PRIVILEGES ON nacos_config.* TO nacos%; FLUSH PRIVILEGES;然后执行 sql 文件导入表结构。这一步完成了Nacos 就不会再依赖内嵌的 Derby 数据库控制台里的配置、用户、权限都会持久化到 MySQL。3.2 Nacos 容器的环境变量设置Nacos 官方镜像里的环境变量比较多我只挑关键的。MODEstandalone表示单机模式SPRING_DATASOURCE_PLATFORMmysql指定数据源为 MySQLMYSQL_SERVICE_HOST指 MySQL 的地址这里要注意如果 MySQL 也跑在 Docker 里要用同一个网络并写容器名MYSQL_SERVICE_DB_NAME是刚才创建的库名NACOS_AUTH_ENABLE用来开启鉴权。docker run -d \ --name nacos-server \ --network micro-net \ -p 8848:8848 \ -p 9848:9848 \ -p 9849:9849 \ -e MODEstandalone \ -e SPRING_DATASOURCE_PLATFORMmysql \ -e MYSQL_SERVICE_HOSTmysql-container \ -e MYSQL_SERVICE_PORT3306 \ -e MYSQL_SERVICE_DB_NAMEnacos_config \ -e MYSQL_SERVICE_USERnacos \ -e MYSQL_SERVICE_PASSWORDnacos \ -e NACOS_AUTH_ENABLEtrue \ -e NACOS_AUTH_TOKENVGhpc0lzTXlDdXN0b21TZWNyZXRLZXkwMDE \ -v /opt/docker-volume/nacos/logs:/home/nacos/logs \ nacos/nacos-server:v2.2.3很多人不知道 9848 和 9849 是干什么的。Nacos 2.x 版本的服务端和客户端之间不仅有 HTTP 通信还有 gRPC 通信。8848 是 HTTP 主端口9848 是客户端 gRPC 端口9849 是服务端之间同步的 gRPC 端口。如果只映射 8848客户端会一直报连接超时因为 gRPC 端口不通。这个坑非常隐蔽我第一次部署时漏了 9848服务注册总失败。3.3 启动后验证访问控制台和确认数据源启动完成后浏览器访问http://宿主机IP:8848/nacos默认账号密码都是nacos。如果能登录说明容器本身没问题。接下来去 Nacos 控制台的配置管理里随便建一条配置再重启 Nacos 容器看看配置是否还在——这一步能验证 MySQL 是否生效。如果重启后配置没了说明数据源配置没生效检查环境变量里的MYSQL_SERVICE_HOST是否写错。鉴权这块多说一句Nacos 2.2.3 之后默认鉴权是可以关闭的但生产环境必须要开。NACOS_AUTH_TOKEN这个值不能太短否则服务端不启动它会校验 Base64 编码后的长度。如果你不设这个值控制台登录还好说但客户端连的时候会报 403。测试环境图省事可以不开鉴权但后面 Seata 连接 Nacos 时也要同步关掉或带上 token否则 Seata 拿不到配置。4. 启动Seata容器注册配置、初始化脚本和挂载细节Seata 比 Nacos 复杂一个量级因为它的配置体系分两层注册中心配置和配置中心配置。很多人把registry.conf里的registry和config混为一谈导致 Seata Server 能启动但客户端连不上或者服务端拿到了错误的配置。4.1 Seata 的配置模型registry.conf 与 config.txtSeata Server 启动时需要两个关键信息它自己要注册到哪里去以及它的运行时配置从哪里读取。这两个信息都定义在registry.conf里。其中registry节点决定注册中心类型我们选nacosconfig节点决定配置中心类型我们也选nacos意思是 Seata Server 的配置项不是从本地文件读而是从 Nacos 配置中心拉取。既然配置放在 Nacos那 Seata 官方提供了一套配置文件模板叫config.txt里面是各种keyvalue形式的事务配置项。我们需要把这份config.txt的内容批量导入到 Nacos 中。导入之后Seata Server 启动时会从 Nacos 读取这些配置。这个思路很像把配置文件交给注册中心管理好处是多个 Seata 实例不用各自保存一份配置。我在实测中发现Seata 1.6.1 的config.txt里最关键的几个配置项是store.modedb store.db.datasourcedruid store.db.dbTypemysql store.db.driverClassNamecom.mysql.jdbc.Driver store.db.urljdbc:mysql://mysql-container:3306/seata?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai store.db.usernacos store.db.passwordnacos这段配置的意思是 Seata Server 要把全局事务会话、分支事务这些记录持久化到 MySQL 的seata库中。如果你不配置 store.modedb默认是 file 存储虽然也能跑但生产环境不推荐而且多实例会互相覆盖数据。4.2 初始化 Seata 的数据库脚本Seata 的数据库和 Nacos 不是同一个库。需要新建一个seata数据库然后执行官方脚本script/server/db/mysql.sql这个脚本会创建global_table、branch_table、lock_table、distributed_lock四张表。CREATE DATABASE seata DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;除此之外你的每个业务数据库里还要执行script/client/at/db/mysql.sql这个脚本创建一张undo_log表Seata 的 AT 模式靠它来做回滚日志。这个表经常被漏掉后果是全局事务提交的时候一切正常一旦回滚就会报undo_log表不存在。如果你手下有多个业务服务每个业务的库都要建这张表。4.3 把 config.txt 灌入 Nacos 配置中心这一步是整个部署中最容易被忽略的。官方仓库里提供了脚本目录进入scripts/config-center/nacos/运行nacos-config.sh脚本即可。脚本会读取上一级目录的config.txt然后把每条配置都写入 Nacos 配置中心。sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t 你的命名空间ID -u nacos -w nacos参数说明-h是 Nacos 地址-p是端口-g是配置分组-t是命名空间 ID。如果你没建命名空间默认 public 命名空间的 ID 是空字符串-t可以不传。导入完成后到 Nacos 控制台看配置管理-配置列表搜索SEATA_GROUP会看到上百条keyvalue配置。如果一条都没有那就是脚本没跑成功检查 Nacos 的鉴权是否已开启脚本不带 token 就会全失败。4.4 Seata 容器启动命令与目录挂载启动 Seata Server 时registry.conf是必须挂载的。我建议先在自己的目录里写好registry.conf再挂载进容器registry { type nacos nacos { application seata-server serverAddr nacos-server:8848 group SEATA_GROUP namespace username nacos password nacos } } config { type nacos nacos { serverAddr nacos-server:8848 namespace group SEATA_GROUP username nacos password nacos } }注意这里serverAddr写的是nacos-server:8848因为我们在同一个 Docker 网络里Nacos 容器的名称是nacos-server。写完之后启动容器docker run -d \ --name seata-server \ --network micro-net \ -p 8091:8091 \ -e SEATA_IP192.168.1.100 \ -e SEATA_PORT8091 \ -e SEATA_GROUPSEATA_GROUP \ -v /opt/docker-volume/seata/conf/registry.conf:/seata-server/resources/registry.conf \ -v /opt/docker-volume/seata/logs:/seata-server/logs \ seataio/seata-server:1.6.1SEATA_IP这个环境变量特别重要。如果容器运行在 Docker 默认的 bridge 网络Seata 会把自己的容器 IP 注册到 Nacos。客户端如果从宿主机网络访问能访问到宿主机 IP 但访问不到容器 IP就会一直报无法连接 TC。设置SEATA_IP为宿主机 IP注册到 Nacos 的地址就是宿主机 IP配合-p 8091:8091端口映射客户端就能正常连接了。这是个非常容易踩的坑很多人卡在这里日志显示 Nacos 里有 seata-server 服务但客户端就是连不上。4.5 查看 Seata Server 启动日志启动后通过docker logs -f seata-server看日志。正常情况下会出现Server started之类的字样。如果没有大概率是registry.conf里 Nacos 地址写错或者config中心的配置没导入成功。出现dataId not exist之类的报错时优先检查是不是已经用脚本导入过配置。5. 客户端与服务端握手事务分组是怎么连上的环境跑起来之后真正容易让人迷惑的不是容器而是 Seata 的事务分组机制。客户端配置一个tx-service-group服务端在注册中心里有一个service.vgroupMapping的映射关系两者必须对齐不然客户端会报could not find service group。5.1 事务分组与 vgroupMapping 的关系先放一个类比事务分组就像快递地址里的小区名而 Seata Server 集群就是快递站点。客户端只知道自己住在哪个小区但不知道哪个快递站负责这个小区所以它要去 Nacos 里查一张映射表。这张表就是vgroupMapping。在 Seata 1.6.x 里默认的映射关系是service.vgroupMapping.default default意思是tx-service-group为default的客户端对应服务端集群名为default。如果客户端配置的tx-service-group不是default比如是my_tx_group那你必须在 Nacos 的配置中心添加一条service.vgroupMapping.my_tx_group default这条配置的位置要看版本。旧版常在config.txt里新版可以通过 Nacos 配置中心动态添加。我在部署时就吃过这个亏客户端写了个看起来很合理的分组名payment_group但没有在 Nacos 里加映射关系服务一直报找不到组日志里全是no available service。5.2 服务端注册到 Nacos 的名字是什么上面说的服务端集群名default对应到 Nacos 里是 Seata Server 注册时使用的application名称。看registry.conf里的application seata-server不这里单独说清楚注册到 Nacos 的服务名是seata-server而应用名的映射关系是service.vgroupMapping。其实在 Nacos 控制台的服务列表里搜索seata-server能看到一个名为seata-server的服务下面有一个临时实例IP 是你在SEATA_IP里设置的值。客户端和这个 IP 建立连接后才算真正跟事务协调器建立会话。很多教程会直接告诉你客户端加几行配置就能用但没人说这几个名字之间是怎么绕的。你只要记住一条链客户端的tx-service-group根据vgroupMapping找到集群名再根据集群名找到 Nacos 里的服务实例。链路中任何一个名字对不上事务协调器就找不到。5.3 客户端的最小配置参考如果用的是 Spring Cloud Alibaba最简配置如下spring: cloud: nacos: discovery: server-addr: localhost:8848 config: server-addr: localhost:8848 file-extension: yaml seata: tx-service-group: my_tx_group service: vgroup-mapping: my_tx_group: default registry: type: nacos nacos: application: seata-server server-addr: localhost:8848 group: SEATA_GROUP namespace: username: nacos password: nacos config: type: nacos nacos: server-addr: localhost:8848 group: SEATA_GROUP namespace: username: nacos password: nacos这里要注意客户端的registry.nacos.application不一定等于服务端的容器名它应该是服务端注册到 Nacos 的application。上面registry.conf里写的是seata-server所以客户端这里也对应写一致。另外客户端的 group 要和服务端一致不然在 Nacos 的同一个命名空间里会隔离掉。6. 端到端跑通验证从日志看全局事务的完整生命周期环境都起来了配置也写了怎么确认真的能跑通我的建议是准备一个最简单的 Demo两个服务一个模拟订单创建一个模拟库存扣减中间用 OpenFeign 调用然后在订单服务的方法上加上GlobalTransactional注解故意让库存服务抛异常看订单会不会回滚。6.1 准备数据库表和 Demo 工程两个业务库各建一张表订单库有orders表库存库有stock表。两个库都要执行undo_log建表脚本。Demo 工程的结构不多说重点是引入依赖dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-seata/artifactId /dependency很多新手会在GlobalTransactional和Transactional之间犹豫。实际用法是GlobalTransactional加在全局事务发起方的入口方法上内部的远程调用方法的本地事务用Transactional保持正常。全局事务的入口方法里Seata 会拦截并开启全局事务分支事务通过 XID 串联起来。6.2 看日志begin 与 commit/rollback当订单服务调用库存服务时观察 Seata Server 容器的日志能看到类似这样的关键信息Begin new global transaction [192.168.1.100:8091:203123456789] Register branch transaction successfully Global transaction [192.168.1.100:8091:203123456789] commit/rollback如果一切正常库存扣减成功后日志里是 commit。如果你在库存服务里加一行throw new RuntimeException()订单服务和库存服务的数据都应该恢复原状。这就是 AT 模式的作用Seata 会在分支事务执行前记录镜像回滚时用镜像数据覆盖回去。6.3 回滚失败时的排查点回滚失败最常见的情况有两种一种是undo_log表没建另一种是全局锁等待超时。第一种报错直接看异常信息就能定位第二种通常在并发场景出现测试环境没那么明显。还有一个容易被忽略的点业务表的字段类型要和实体类里的一致否则回滚时类型转换异常会导致回滚失败。如果你看到UndoLogManager相关的错误先查表是否存在再查表结构是否和官方脚本一致。6.4 隐藏的时区问题我那次部署就是栽在时区上。MySQL 的 URL 里如果没有serverTimezoneAsia/ShanghaiSeata 记录分支事务时间时可能会错位导致全局事务的创建时间和分支事务时间不一致进而引发异常。建议在config.txt的store.db.url里显式加上serverTimezoneAsia/Shanghai同时在业务数据源连接串里也保持一致。这个配置不显眼但出了问题非常难查。7. 接入Spring Boot客户端时的高频坑与排查顺序最后把我在实际接入阶段遇到的几个高频率问题整理一下这些问题几乎每个初次部署的人都会碰到按照从下到上的排查顺序写。7.1 先看容器 IP再看注册地址客户端报连接超时先不要急着怀疑代码。第一步看 Nacos 控制台打开服务列表找到seata-server看看实例列表里的 IP 是什么。如果 IP 是172.17.0.x这种容器 IP而你的客户端跑在宿主机或者别的机器上那就是SEATA_IP没设置。重新设置SEATA_IP并重启 Seata Server 即可。这里的逻辑是Seata 只会把配置的地址告诉别人它自己并不知道你在外面做了端口映射。7.2 检查命名空间和 Group 是否一致Nacos 里的 namespace 和 group 是隔离维度。服务端registry.conf里如果namespace 那就是 public客户端里也必须是 public。Group 默认建议都用SEATA_GROUP但如果你在config.txt导入时用了别的 Group客户端和服务端必须完全一致。这个不一致的报错很典型客户端能启动但拉取配置时为空然后 Seata Server 的默认配置兜底生效事务数据进不了 db回滚也查不到日志。7.3 数据源代理不代理就白搭在 Spring Boot 集成中Seata 要求数据源必须被代理否则全局事务拦截到了但分支事务不会参与。通常的写法是Configuration public class DataSourceConfig { Bean ConfigurationProperties(prefix spring.datasource) public DruidDataSource druidDataSource() { return new DruidDataSource(); } Bean public DataSource dataSource(DruidDataSource druidDataSource) { return new DataSourceProxy(druidDataSource); } }如果没有这个代理配置GlobalTransactional注解虽然生效但分支事务注册不到 Seata Server线程变量里的 XID 也传不过去最终效果就是事务像没加一样。这个问题代码层面上不报错只在日志里能看到分支事务没有注册所以容易被忽略。7.4 按顺序排查的参考清单我把排查顺序整理成一张表方便直接对照现象先检查什么再检查什么客户端报连不上 Seata ServerNacos 服务列表里的 IP 是否为宿主机 IP8091 端口映射是否正确客户端报找不到事务分组Nacos 配置中心是否有 vgroupMapping 配置客户端 tx-service-group 是否拼写正确回滚失败业务库是否有 undo_log 表表结构是否与官方脚本一致事务不生效无分支日志数据源是否被 DataSourceProxy 代理GlobalTransactional是否加在了入口方法上这张表是我实际排查多个项目后的总结每一行都出过问题。如果按这个顺序走完还没解决再看一遍 docker logsSeata 日志会把异常原因写得很清楚。7.5 最后一个实用建议如果你只是个人学习或做 Demo镜像是官方镜像版本用 1.6.1别用太新的版本因为新版本对 Spring Boot 3.x 的适配要求不一样默认依赖冲突会耗费不少时间。等基础链路跑通了再考虑升级版本。另外建议把整个环境的启动命令写成脚本包括建网、拉镜像、启动容器、导入配置这样以后重置环境时一条命令就能恢复不用再对着文档敲一遍。部署这套环境真正花时间的不是容器本身而是理解 Seata 的配置模型和数据源代理机制。把registry.conf、config.txt、事务分组和数据源代理这四个点的关系理顺你的 Docker 部署才算真正完成。