1. 为什么要在容器环境里折腾 Seata 2.x 这套组合
分布式事务这个话题,放在三五年前还是大厂架构组的专属议题,如今随着微服务拆分越来越细,一个下单动作背后横跨订单、库存、账户、积分四五个服务已经是常态,本地事务那套@Transactional早就兜不住了。我接触过的不少团队,业务代码写得挺漂亮,一到跨服务数据一致性就开始各显神通——有的靠定时任务对账兜底,有的干脆把强一致需求硬塞进一个服务里,结果服务边界被搅得一团糟。Seata 就是在这个背景下被反复提起的方案,它把分布式事务的协调逻辑抽出来做成独立组件,业务侧只需要加注解、配数据源,剩下的分支注册、回滚补偿交给它处理。
这次要聊的场景更具体一点:用 Docker 部署 Seata 2.x 服务端,再整合 SpringCloud 微服务,并且让 Nacos 同时承担配置中心和注册中心的角色。为什么强调这几个限定词?因为 Seata 的版本演进挺折腾人的,1.x 到 2.x 之间配置项结构、注册方式、脚本初始化都有变化,网上大量教程还停留在 1.4 甚至更早的版本,照抄很容易在启动阶段就卡住。而 Docker 化部署又引入了容器网络、配置文件挂载、时区、JVM 参数这些额外变量,Nacos 双角色则意味着 Seata 既要注册自己让客户端发现,又要从配置中心拉取参数,两条链路任何一条不通都会表现为“启动成功但事务不生效”这种隐蔽故障。
这篇文章适合谁看?如果你手上有一个 SpringCloud 项目,服务已经注册到 Nacos,现在需要引入分布式事务能力,并且希望服务端以容器方式统一管理,那这套流程基本可以照着走。我会把镜像选择、配置文件改造、数据库脚本初始化、客户端接入、常见故障排查这几个环节拆开讲,重点放在那些文档里不写、但实际部署时一定会撞上的细节上。全程基于我实际搭环境的经验,参数和步骤都经过验证,你可以直接抄作业,但建议先理解每一步在干什么,出问题时才好定位。
2. 整体方案设计与组件选型思路
2.1 为什么是 Seata 2.x 而不是继续用 1.x
版本选择这件事,我的建议是新项目直接上 2.x,老项目评估后再动。Seata 2.x 相对 1.x 最明显的变化是配置体系的重构,registry.conf这个老配置文件在 2.x 里被application.yml取代,注册和配置都统一走 Spring Boot 的配置风格,这对熟悉 SpringCloud 的人来说反而更顺手。另一个关键点是 2.x 对 Nacos 配置中心的支持更完整,支持通过seata.config.nacos前缀直接声明配置中心地址、命名空间、分组,不用再像 1.x 那样在registry.conf里写一堆嵌套结构。
还有一个容易被忽略的点:2.x 在 AT 模式的全局锁和回滚日志处理上做了优化,undo_log表的清理机制更完善。1.x 时代我遇到过回滚日志表膨胀到几个 G 的情况,还得手动写脚本清理,2.x 配合服务端的定时任务能缓解不少。当然,2.x 也不是没有代价,它对 JDK 版本、Nacos 客户端版本有一定要求,下面选型时会具体说。
2.2 Nacos 同时做配置与注册中心,链路怎么走
把 Nacos 一肩挑两职,本质上是减少一套中间件的维护成本。具体到 Seata 这里,两条链路要分清楚:
- 注册链路:Seata Server 启动后,以某个服务名(默认
seata-server)注册到 Nacos,微服务客户端通过这个服务名找到 Seata Server 的地址,建立 TC(事务协调器)连接。 - 配置链路:Seata Server 启动时,从 Nacos 的指定 dataId 拉取配置,比如存储模式、数据库连接、事务分组映射等;微服务客户端也会从 Nacos 拉取自己的 Seata 相关配置。
这两条链路用的是同一个 Nacos 集群,但命名空间、分组、dataId 要规划清楚,否则会出现“服务注册上了但配置拉不到”或者“配置拉到了但注册地址对不上”的割裂情况。我的做法是给 Seata 单独划一个命名空间,服务端配置和客户端配置用不同的 dataId 区分,分组统一,这样排查时一眼就能看出是哪一层的问题。
2.3 Docker 部署的取舍:官方镜像还是自建镜像
Seata 官方在 Docker Hub 上提供了镜像,2.x 版本可以直接拉取。用官方镜像的好处是省事,坏处是配置文件路径、启动参数是固定的,想改配置要么挂载覆盖,要么进容器改。我的建议是优先用官方镜像加挂载,因为 Seata 的配置文件结构比较固定,挂载application.yml和日志目录就够了,没必要自己写 Dockerfile 重新构建。除非你有特殊的 JVM 参数调优需求,或者要预置一些自定义的 SPI 实现,那才考虑基于官方镜像再打一层。
容器网络这块要提前想好。如果 Nacos 和 Seata 都跑在 Docker 里,最省心的是放在同一个自定义 bridge 网络下,用容器名互相访问;如果 Nacos 在宿主机或者外部环境,那 Seata 容器注册到 Nacos 的地址必须是宿主机能访问到的 IP,不能是容器内部的172.x地址,否则微服务从 Nacos 拿到地址后根本连不上。这个坑我踩过不止一次,表现就是客户端一直报连接超时。
2.4 存储模式的选择:file 还是 db
Seata Server 支持 file、db、redis 等存储模式。file 模式最简单,不用连数据库,但重启后事务状态丢失,只适合本地调试。生产环境必须用 db 模式,把全局事务会话、分支事务、锁记录落到数据库里,这样服务端重启或者多节点部署时状态才一致。
db 模式需要提前建库建表,Seata 官方提供了各数据库的初始化脚本,2.x 版本的脚本在源码的script/server/db目录下。MySQL 的话直接执行mysql.sql就行,注意脚本里建的表名有global_table、branch_table、lock_table、distributed_lock这几张,字符集建议用utf8mb4,排序规则跟业务库保持一致,避免跨库关联时出现排序问题。
3. 部署前的环境准备与关键参数确认
3.1 基础环境清单与版本对齐
动手之前先把版本对齐,这是避免后续一堆玄学问题的前提。我这次验证用的组合是:JDK 17、Spring Boot 3.x、SpringCloud 2023.x、SpringCloud Alibaba 2023.x、Nacos 2.3.x、Seata 2.0.x、MySQL 8.0。如果你的项目还在 Spring Boot 2.x,那 Seata 客户端依赖要选对应兼容的版本,别硬上最新的,否则会出现类找不到或者自动配置不生效。
这里有个细节值得说:Seata 2.x 服务端本身对 JDK 版本要求不算苛刻,JDK 8 也能跑,但如果你用 JDK 17 跑容器,记得在启动参数里加上必要的模块开放参数,否则反射相关的逻辑可能报错。官方镜像默认的启动脚本已经处理了一部分,但自己挂载配置时容易漏。
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 17 | 服务端与客户端保持一致更省心 |
| Seata Server | 2.0.0 及以上 | 2.x 配置体系与 1.x 不兼容 |
| Nacos | 2.2.x 及以上 | 需支持 gRPC 通信 |
| MySQL | 8.0 | 存储模式使用 db |
| SpringCloud Alibaba | 2023.x | 与 Spring Boot 3.x 匹配 |
3.2 Nacos 侧的命名空间与配置规划
在 Nacos 控制台先建好命名空间,比如叫seata-dev,记下命名空间 ID,后面配置文件里要用 ID 而不是名称。然后在配置管理里准备两个配置:一个是服务端用的,dataId 可以定为seataServer.properties,分组用SEATA_GROUP;另一个是客户端用的,dataId 按微服务名来,比如order-service-seata.properties,或者干脆把公共部分抽成一个共享配置。
服务端配置的内容主要是存储模式、数据库连接、事务分组。事务分组这个概念新手容易懵,简单说就是给一组微服务打个标签,客户端配置里声明自己属于哪个分组,服务端根据分组映射找到对应的 TC 集群。单机部署时分组映射可以指向默认集群,多集群时才需要仔细配。
3.3 数据库初始化与账号权限
db 模式必须先建库,我一般单独建一个seata库,跟业务库分开,避免误操作。执行官方脚本后检查表结构,重点看global_table的status字段和lock_table的索引,这两个直接影响事务提交和锁竞争的性能。数据库账号给足增删改查权限即可,不需要 DDL 权限,生产环境更要注意最小权限原则。
注意:初始化脚本里的表名和字段在不同小版本间可能有微调,务必用你实际部署版本对应的脚本,别拿 1.x 的脚本往 2.x 上套,字段对不上会导致启动时报 SQL 异常。
3.4 端口与网络连通性预检
Seata Server 默认监听两个端口:8091是控制台和 HTTP 服务端口,7091是内部通信端口(2.x 新增的 console 端口)。容器部署时这两个端口都要映射出来,客户端连接用的是注册到 Nacos 的地址加端口,所以映射后的宿主机端口要和注册地址一致。提前用telnet或者nc测一下 Nacos 的8848和9848(gRPC)端口是否通,Nacos 2.x 的客户端通信依赖 gRPC,只开 8848 是不够的,这个坑很多人踩。
4. Docker 部署 Seata Server 的完整实操
4.1 拉取镜像与目录规划
先拉官方镜像,标签选具体版本号而不是latest,方便回滚和排查。宿主机上建一个工作目录,比如/opt/seata,下面分config、logs、script三个子目录,分别放挂载的配置文件、日志输出、初始化脚本备份。目录权限给容器内运行用户可读写,官方镜像一般用非 root 用户跑,权限不对会报日志写不进去。
docker pull seataio/seata-server:2.0.0 mkdir -p /opt/seata/{config,logs,script} chmod -R 777 /opt/seata/logs4.2 编写服务端 application.yml
这是整个部署的核心文件,我把它拆成几块看。注册部分声明 Nacos 地址、命名空间、分组、应用名;配置部分声明从 Nacos 拉取配置的 dataId 和分组;存储部分声明 db 模式和数据库连接。下面是一份可直接改用的模板:
server: port: 7091 spring: application: name: seata-server seata: config: type: nacos nacos: server-addr: 192.168.1.100:8848 namespace: seata-dev-namespace-id group: SEATA_GROUP >service.vgroupMapping.order-tx-group=default service.vgroupMapping.inventory-tx-group=default service.enableDegrade=false service.disableGlobalTransaction=falsevgroupMapping这行是客户端能连上的关键,客户端配置里声明的事务分组名必须在这里有映射,否则客户端启动时会报找不到可用的 TC 集群。
4.4 启动容器与挂载验证
启动命令把配置、日志、端口都处理好:
docker run -d --name seata-server \ -p 8091:8091 -p 7091:7091 \ -v /opt/seata/config/application.yml:/seata-server/resources/application.yml \ -v /opt/seata/logs:/root/logs \ -e SEATA_IP=192.168.1.100 \ -e SEATA_PORT=8091 \ seataio/seata-server:2.0.0SEATA_IP这个环境变量很关键,它决定 Seata 注册到 Nacos 的地址。容器内部拿到的 IP 是172.x网段,外部微服务根本访问不到,所以必须显式指定宿主机 IP 或者对外可达的地址。启动后看日志,出现Server started并且 Nacos 控制台服务列表里能看到seata-server,说明注册链路通了;再看日志里有没有成功加载 Nacos 配置的提示,两条链路都通才算部署完成。
提示:如果日志里报 Nacos 连接失败但容器本身在跑,先检查命名空间 ID 是否填错,命名空间名称和 ID 是两个东西,配置里要填 ID。
5. SpringCloud 客户端整合的关键配置
5.1 依赖引入与版本匹配
客户端依赖主要是seata-spring-boot-starter,配合 SpringCloud Alibaba 的版本管理。如果你的项目用了 SpringCloud Alibaba 的 BOM,Seata 版本可以跟着 BOM 走,避免手动指定版本导致冲突。引入后检查依赖树里有没有重复的 Seata 相关包,尤其是老项目里可能残留 1.x 的seata-all,两个版本共存会引发类加载问题。
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> </dependency>5.2 客户端 yml 配置要点
客户端配置分两块:告诉客户端 Seata Server 在哪(通过 Nacos 注册发现),以及告诉客户端自己属于哪个事务分组。核心配置如下:
seata: enabled: true application-id: order-service tx-service-group: order-tx-group registry: type: nacos nacos: server-addr: 192.168.1.100:8848 namespace: seata-dev-namespace-id group: SEATA_GROUP application: seata-server config: type: nacos nacos: server-addr: 192.168.1.100:8848 namespace: seata-dev-namespace-id group: SEATA_GROUP >