分布式事务这件事,说起来简单,做起来坑多。我见过太多团队在单体应用拆成微服务之后,订单扣了库存没扣、库存扣了余额没扣,最后对账对到怀疑人生。Seata 就是来解决这个问题的,而 2.x 版本相比 1.x 在配置结构和注册机制上有不少变化,加上 Docker 部署和 Nacos 做配置与注册中心,整套链路串起来的时候,细节相当多。这篇内容我打算把从零搭建一套Docker 部署 Seata 2.x + Spring Cloud 整合 + Nacos 双中心的完整过程拆开讲,包括版本怎么选、配置文件怎么写、Docker 容器怎么跑、Spring Cloud 侧怎么接、以及我实际踩过的那些坑。适合正在做微服务分布式事务改造、或者刚接触 Seata 想快速跑通一套可用环境的同学,有 Spring Cloud 基础会更容易跟上,但关键步骤我都会交代清楚背景。
1. 先搞清楚 Seata 2.x 的架构变化再动手
很多人上来就照着 1.x 的教程抄配置,结果容器起不来或者注册不上,根本原因是对 2.x 的变化没概念。动手之前花十分钟把架构和版本差异理清楚,后面能省掉大量排查时间。
1.1 TC、TM、RM 三个角色到底谁在干活
Seata 的核心模型是三个角色:TC(Transaction Coordinator,事务协调者)、TM(Transaction Manager,事务管理器)、RM(Resource Manager,资源管理器)。TC 就是 Seata Server,独立部署的那个服务端,负责维护全局事务和分支事务的状态,驱动全局提交或回滚。TM 是发起全局事务的一方,通常就是业务入口那个服务,它向 TC 申请开启一个全局事务,拿到 XID。RM 是管理分支事务资源的一方,比如订单库、库存库各自的操作,它负责向 TC 注册分支事务、上报状态。
理解这三个角色的分工,你才能明白为什么 Seata Server 要独立部署、为什么每个参与事务的微服务都要引入 seata 依赖、为什么 Nacos 在这里要同时承担注册中心和配置中心两个职责。TC 需要知道有哪些 RM 注册上来,TM 需要能找到 TC,这些寻址信息在微服务环境下最自然的载体就是注册中心。而 TC 自身的存储模式、事务分组这些参数,放在配置中心统一管理比散落在各个配置文件里靠谱得多。
提示:Seata 2.x 默认的事务分组映射逻辑有调整,
service.vgroupMapping的配置方式在 2.x 里通过配置中心下发更常见,别再死磕 1.x 那种写在 file.conf 里的老路子。
1.2 2.x 相比 1.x 最该注意的几个改动
第一个改动是配置结构。1.x 时代大量使用registry.conf和file.conf两个文件,2.x 虽然还兼容,但官方更推荐把配置全部托管到配置中心,本地只留最少的引导配置。这意味着你在 Docker 启动 Seata Server 时,需要通过环境变量或者挂载的方式告诉它去哪里拉配置。
第二个改动是注册中心与配置中心的分离配置。2.x 里registry和config两个段落可以分别指定类型,比如注册用 Nacos、配置也用 Nacos,但两者的 namespace、group 可以不同。这个灵活性是好事,但也容易配错,尤其是 namespace 的 ID 和名称别搞混。
第三个改动是存储模式。Seata Server 支持 file、db、redis 三种存储模式。file 模式适合单机测试但重启丢数据,db 模式是生产常用方案需要建表,redis 模式性能好但要注意持久化配置。Docker 部署时如果用 db 模式,要确保容器能连到数据库,网络配置不能马虎。
| 对比项 | 1.x 常见做法 | 2.x 推荐做法 |
|---|---|---|
| 配置存放 | 本地 file.conf | Nacos 配置中心托管 |
| 注册配置 | registry.conf 单文件 | registry 与 config 分段配置 |
| 事务分组映射 | 本地配置 | 配置中心下发 |
| 存储模式 | file 为主 | db 为主,redis 可选 |
| 启动参数 | 改配置文件 | 环境变量注入 |
这张表不是让你死记,而是提醒你:网上搜到的教程如果还是 1.x 的写法,直接套到 2.x 上大概率出问题。判断教程新旧的简单办法是看它有没有提到配置中心托管和事务分组映射的下发方式。
1.3 为什么选 Nacos 同时做注册与配置中心
微服务环境下,注册中心和配置中心分开用两套组件当然可以,但维护成本翻倍。Nacos 同时具备服务发现和配置管理能力,Seata Server 注册到 Nacos 之后,各个微服务通过 Nacos 就能发现 TC 的地址,不需要在代码里硬编码 IP。同时 Seata 的各项配置通过 Nacos 配置中心下发,改配置不用重启容器,这对调试阶段特别友好。
这里有个容易忽略的点:Seata Server 注册到 Nacos 时用的服务名和命名空间,必须和微服务客户端配置的一致。我见过有人 Server 注册在 public 命名空间,客户端却配了个自定义命名空间,结果客户端一直报找不到 TC。命名空间、分组、服务名这三样,Server 端和客户端必须严格对齐,这是后面所有配置的基础。
2. 环境准备与版本对齐的实操细节
版本不对齐是这套组合拳里最高频的翻车点。Seata 2.x 的各个小版本之间、Spring Cloud Alibaba 的版本、Nacos 的版本,三者之间是有兼容关系的,随便抓最新版凑一起,很可能启动就报类找不到或者协议不匹配。
2.1 版本选型:别盲目追新
我的建议是锁定一套经过验证的组合,而不是每个组件都取最新。以我实际跑通的组合为例:Nacos 用 2.x 的稳定版本,Seata Server 用 2.x 的较新稳定版,Spring Cloud Alibaba 选择与 Spring Boot 版本匹配的发行版。关键在于Spring Cloud Alibaba 里集成的 seata 客户端版本要和 Server 端大版本一致,2.x 的 Server 配 2.x 的客户端,跨大版本基本必出问题。
具体操作上,先在项目的依赖管理里确认spring-cloud-alibaba-dependencies的版本,它决定了 seata 客户端的版本。然后去 Seata 官方仓库找对应大版本的 Server 镜像。Docker 镜像的 tag 要和客户端大版本对齐,比如客户端是 2.0.x,Server 就用 2.0.x 的镜像,别用 2.1 的 Server 配 2.0 的客户端。
注意:版本对齐这件事没有捷径,宁可花时间查兼容矩阵,也不要抱着"试试看"的心态。启动报错还算好的,最怕的是能启动但事务行为异常,排查起来极其痛苦。
2.2 Docker 环境与网络的前置检查
Docker 部署 Seata Server 之前,确认几件事:Docker 引擎正常运行、能拉取镜像、容器网络能和 Nacos 以及数据库互通。如果 Nacos 和数据库都跑在宿主机上,容器内访问宿主机要用宿主机在容器网络里可达的地址,Linux 下通常是宿主机网桥 IP 或者用host.docker.internal(需要额外配置),Windows 和 Mac 的 Docker Desktop 对host.docker.internal支持较好。
我习惯把 Nacos、数据库、Seata Server 都放在同一个自定义 Docker 网络里,这样容器之间用服务名就能互相访问,不用操心 IP 变化。创建网络的命令很简单:
docker network create seata-net然后把 Nacos 容器、数据库容器、Seata 容器都--network seata-net接进去。这样 Seata 配置里连数据库写容器名就行,连 Nacos 也写容器名,清爽且稳定。这个习惯强烈建议养成,能避免大量因为 IP 变动导致的连接失败。
2.3 数据库准备与 Seata 建表
如果用 db 存储模式,需要提前在数据库里建好 Seata 的全局事务表。Seata 官方仓库的 script 目录下有对应数据库的建表脚本,MySQL 的话主要是global_table、branch_table、lock_table三张核心表,2.x 还可能涉及distributed_lock表。把脚本跑一遍,确认表结构建好。
建表时注意字符集和引擎,统一用 InnoDB,字符集跟你的业务库保持一致。另外给 Seata 单独建一个库或者单独一个 schema 会更清晰,避免和业务表混在一起。数据库连接账号要有这几张表的读写权限,Docker 容器里连数据库用的账号密码要提前确认好,后面写进 Seata 配置里。
3. Nacos 侧的配置怎么组织才不乱
Nacos 在这里承担两个职责,配置和注册。配置这块如果组织得乱,后面改一个参数要找半天。我一般按"一个 Seata Server 对应一组配置"的思路来组织,命名清晰,改起来一目了然。
3.1 配置中心的 dataId 与 group 规划
Seata Server 从 Nacos 拉配置时,依赖特定的 dataId 和 group 约定。2.x 里常见的做法是把 Seata 的配置拆成几个部分:registry 相关、config 相关、store 相关、transport 相关。这些配置可以放在一个 dataId 里,也可以拆开,取决于你的管理习惯。
我倾向于把 Server 端配置放在一个 dataId 下,比如seataServer.properties,group 用SEATA_GROUP或者自定义的 group。关键是Seata Server 启动时指定的 dataId、group、namespace 要和 Nacos 里实际创建的一致。这个一致性靠环境变量或者启动参数保证,后面 Docker 启动命令里会体现。
配置内容上,重点几项:store.mode=db、数据库连接四要素、store.db.driverClassName、store.db.url、store.db.user、store.db.password。还有server.recovery相关的超时参数,测试环境可以适当调小方便观察回滚行为,生产环境按业务事务时长合理设置。
3.2 注册中心的服务名与命名空间对齐
注册这块,Seata Server 启动后会以某个服务名注册到 Nacos。这个服务名要和客户端配置里seata.registry.nacos.application或者对应的服务名配置一致。命名空间也要一致,Nacos 的命名空间有 ID 和名称两个概念,配置时用的是命名空间 ID,不是名称,这点特别容易搞错。
分组(group)同样要对齐。Server 注册用的 group 和客户端查找用的 group 不一致,就会出现"服务明明注册了但客户端找不到"的诡异现象。我的做法是:先在 Nacos 控制台确认命名空间 ID,然后 Server 端和客户端配置里都填这个 ID,group 统一用一个明确的值,比如SEATA_GROUP,服务名统一用seata-server。三处对齐,基本不会出寻址问题。
3.3 事务分组映射的配置位置
事务分组映射(vgroupMapping)是连接客户端事务分组和 Server 集群的桥梁。客户端配置里指定seata.tx-service-group=my_tx_group,然后需要有一个映射告诉它my_tx_group对应哪个 Server 集群。2.x 里这个映射可以通过配置中心下发,键的格式类似service.vgroupMapping.my_tx_group=default,值对应 Server 端的集群名。
这个配置放在 Nacos 的 Seata 配置里,客户端启动时会去拉取。如果映射缺失或者写错,客户端会报找不到可用的 TC 地址。我踩过一次坑:映射的键名里分组名拼错了一个字母,排查了半天才发现是拼写问题。所以配置这类映射时,建议把客户端的分组名和映射键名放在一起对照检查。
4. Docker 启动 Seata Server 的完整命令拆解
配置都准备好了,接下来是把 Seata Server 跑起来。Docker 启动命令里每个参数都有讲究,我逐段拆开讲,方便你按自己的环境调整。
4.1 镜像拉取与容器启动参数
先拉镜像,tag 用和客户端大版本对齐的版本:
docker pull seataio/seata-server:2.0.0启动容器的核心参数包括:容器名、网络、端口映射、环境变量。端口方面 Seata Server 默认监听 8091(2.x 的默认端口,1.x 是 8091 也有用 8091 的,具体看版本),另外还有控制台端口。环境变量主要用来指定注册中心和配置中心的连接信息。
一个可参考的启动命令结构如下:
docker run -d \ --name seata-server \ --network seata-net \ -p 8091:8091 \ -p 7091:7091 \ -e SEATA_IP=宿主机可达IP \ -e SEATA_PORT=8091 \ -e STORE_MODE=db \ -e CONSOLE_PORT=7091 \ -e SEATA_CONFIG_NAME=file:/root/seata-config/registry \ seataio/seata-server:2.0.0这里SEATA_IP很关键,它决定 Server 注册到 Nacos 时上报的地址。如果填了容器内网 IP,宿主机上的客户端可能访问不到;填宿主机 IP,容器网络内的客户端又可能绕路。最稳妥的做法是让所有客户端也在同一 Docker 网络里,或者确保填的 IP 对所有客户端可达。测试环境我一般填宿主机的局域网 IP,客户端在宿主机上跑,直连没问题。
4.2 配置文件挂载还是环境变量注入
Seata Server 的配置来源有两种方式:挂载配置文件到容器内,或者通过环境变量注入。2.x 更推荐配置托管到 Nacos,本地只留引导配置。引导配置主要告诉 Server 去哪里找 Nacos、用什么 dataId 和 group 拉配置。
我的做法是准备一个精简的registry.conf(或者 2.x 对应的引导配置文件),挂载到容器里,里面只写注册中心和配置中心的类型及连接信息,其余业务配置全部放 Nacos。挂载命令加一段:
-v /your/local/path/registry.conf:/root/seata-config/registry.conf然后在启动参数里用SEATA_CONFIG_NAME指向它。这样本地配置文件极简,改配置去 Nacos 改,容器不用重启,符合 2.x 的设计思路。挂载时注意文件路径和容器内路径的对应关系,路径写错容器启动会直接报找不到配置。
4.3 启动后的健康检查与日志排查
容器起来后别急着往下走,先确认它真的健康。看容器日志:
docker logs -f seata-server日志里重点看几件事:有没有成功连上 Nacos、有没有成功连上数据库、有没有报注册失败。如果看到注册成功的日志,再去 Nacos 控制台的服务列表里确认seata-server这个服务出现了,实例数是 1 且健康。这一步确认通过,说明 Server 端基本就绪。
如果日志里报数据库连接失败,检查容器网络能否通到数据库、账号密码对不对、数据库是否允许远程连接。如果报 Nacos 连接失败,检查 Nacos 地址在容器内是否可达、命名空间 ID 是否正确。这些排查思路后面还会展开,这里先记住"看日志、对配置、验网络"这个顺序。
5. Spring Cloud 客户端整合的关键配置
Server 跑起来了,接下来是业务微服务接入。客户端整合的核心是让每个参与全局事务的服务都能找到 TC、能正确注册分支事务、能在事务结束时正确提交或回滚。
5.1 依赖引入与版本锁定
在 Spring Cloud 项目里引入 Seata 客户端,通常通过 Spring Cloud Alibaba 的 starter。依赖大致是:
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> </dependency>版本由spring-cloud-alibaba-dependencies统一管理,所以父 pom 里锁定这个 BOM 的版本即可。这里再次强调版本对齐:BOM 里带的 seata 客户端版本要和 Server 端大版本一致。如果 BOM 版本较老带的客户端是 1.x,而 Server 是 2.x,就会出现协议不兼容。必要时可以排除默认的 seata 依赖,手动引入对应版本的客户端。
依赖引入后,确认启动类或者配置类上没有遗漏必要的注解。数据源代理这块,Seata 需要对数据源做代理才能拦截 SQL 生成回滚日志,2.x 的自动配置一般会处理,但如果用了多数据源或者自定义数据源,可能需要手动配置代理,这点后面单独说。
5.2 application 配置项逐条说明
客户端的配置项不少,逐条说清楚每个是干什么的。核心配置大致如下:
seata: enabled: true application-id: order-service tx-service-group: my_tx_group registry: type: nacos nacos: server-addr: 127.0.0.1:8848 namespace: your-namespace-id group: SEATA_GROUP application: seata-server config: type: nacos nacos: server-addr: 127.0.0.1:8848 namespace: your-namespace-id group: SEATA_GROUP service: vgroup-mapping: my_tx_group: default逐条解释:application-id是当前服务标识,方便在 TC 侧区分来源;tx-service-group是事务分组名,要和 vgroup-mapping 的键对应;registry段告诉客户端去哪里找 TC,application就是 Server 注册到 Nacos 的服务名;config段告诉客户端去哪里拉 Seata 配置;vgroup-mapping把事务分组映射到 Server 集群名。
这里最容易错的是namespace和group。namespace 填 ID 不填名称,group 要和 Server 端一致,application要和 Server 注册的服务名一致。这三处任何一处不对,客户端都找不到 TC。我建议把这几项单独拎出来做成配置模板,所有微服务统一引用,减少手误。
5.3 数据源代理与事务注解的正确用法
数据源代理是 Seata 能拦截业务 SQL 的前提。Spring Cloud Alibaba 的 starter 在满足条件时会自动代理数据源,但如果你用了 Druid 或者多数据源框架,自动代理可能失效,需要手动配置DataSourceProxy。判断是否代理成功,可以在启动日志里找相关提示,或者看事务回滚时有没有生成 undo_log。
业务代码里,发起全局事务的方法加@GlobalTransactional注解。这个注解加在 TM 角色的一方,也就是事务发起方。参与方的方法不需要加这个注解,但它们的数据库操作会被 RM 拦截并注册为分支事务。这里有个常见误区:以为每个参与方都要加@GlobalTransactional,其实只有发起方需要,参与方加了反而可能造成事务嵌套混乱。
提示:
@GlobalTransactional尽量加在事务边界清晰的入口方法上,方法内部不要再嵌套调用其他带该注解的方法,避免事务传播行为不符合预期。
6. 联调阶段最容易翻车的几个点
配置都写完了,真正跑起来的时候才是考验。下面这几个问题是我和身边同行反复遇到的,按出现频率排序,附上排查思路。
6.1 客户端报找不到 TC 服务的排查链路
报错信息通常是找不到可用的服务或者连接超时。排查顺序建议这样走:第一步,去 Nacos 控制台确认seata-server服务是否存在且实例健康,如果不存在,问题在 Server 端注册;第二步,确认客户端配置的 namespace、group、application 和 Server 注册的完全一致,特别注意 namespace 用的是 ID;第三步,确认客户端所在网络能访问到 Server 注册上报的 IP 和端口,如果 Server 上报的是容器内网 IP 而客户端在宿主机,就会连不上。
第三步是最隐蔽的。解决办法要么让客户端也进同一 Docker 网络,要么把SEATA_IP设成客户端可达的地址。我一般测试环境直接设宿主机局域网 IP,省事。生产环境则根据部署拓扑规划,确保注册上报的地址对所有客户端可达。
6.2 事务不回滚的几种典型原因
明明抛了异常但数据没回滚,原因可能有好几层。第一层,异常类型不对:@GlobalTransactional默认对RuntimeException和Error回滚,如果抛的是受检异常且没配置rollbackFor,不会回滚。第二层,异常被吞了:方法内部 try-catch 把异常吃掉了,外层感知不到,自然不会触发回滚。第三层,数据源没被代理:SQL 没被拦截,分支事务没注册,回滚时没有 undo_log 可用。第四层,undo_log 表缺失或结构不对。
排查时按这个顺序:先看异常有没有正常抛出方法边界,再看数据源代理是否生效,再看 undo_log 表是否存在且有数据写入。我遇到过一次是 undo_log 表建在了错误的库,业务库操作正常但回滚日志写不进去,表现就是事务提交了但回滚失败,日志里会有相关提示。
6.3 高并发下锁冲突与超时的处理思路
Seata 的 AT 模式依赖全局锁,高并发下同一行数据的并发更新容易产生锁冲突,表现为获取全局锁超时。这不是配置错误,而是模式本身的特性。处理思路有几个方向:缩短事务执行时间,把非数据库操作移出事务边界;调整全局锁等待超时参数,给合理的时间窗口;对热点数据做业务层面的拆分或排队;评估是否适合用 AT 模式,某些场景 TCC 或 Saga 更合适。
超时参数在 Server 端和客户端都有相关配置,调整时要两边配合。测试环境可以把超时调短方便复现问题,生产环境则要根据实际事务耗时分布来定,别拍脑袋。这块没有银弹,得结合业务特点调。
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 找不到 TC | 注册信息不一致或网络不通 | 查 Nacos 服务列表、对配置、验网络 |
| 事务不回滚 | 异常类型、异常被吞、数据源未代理 | 查异常边界、代理日志、undo_log |
| 全局锁超时 | 并发冲突、事务过长 | 缩短事务、调超时、评估模式 |
| 启动报类找不到 | 版本不兼容 | 对齐 Server 与客户端大版本 |
7. 生产化之前值得做的几件事
测试环境跑通只是第一步,真要上生产还有几件事得提前考虑。这些不是必须马上做,但心里要有数。
7.1 存储模式从 file 切到 db 的注意事项
测试时图省事用 file 模式,重启丢数据无所谓。生产必须切 db 或 redis。切 db 时注意数据库的高可用,Seata 的全局事务表是核心状态数据,库挂了整个分布式事务链路就瘫了。连接池参数要合理配置,避免事务高峰期连接不够用。建表脚本要在生产库提前执行,字符集和业务库对齐。
另外,db 模式下 Seata Server 的数据库和业务数据库最好物理隔离,避免业务库压力影响事务协调。如果资源有限必须共用,至少做好监控和限流。
7.2 控制台端口的安全暴露
2.x 的 Seata Server 带控制台,方便查看事务状态。控制台端口默认和事务端口不同,Docker 部署时如果映射了控制台端口,要注意访问控制。生产环境不建议把控制台端口直接暴露到公网,通过内网访问或者加一层鉴权更稳妥。测试环境无所谓,但要有这个意识,别把测试的暴露习惯带到生产。
7.3 监控与告警的接入点
Seata Server 暴露了一些指标,可以接入监控体系。重点关注的指标包括活跃全局事务数、分支事务数、锁等待情况、TC 连接数等。客户端侧关注事务成功率和耗时分布。告警阈值根据业务基线来定,比如全局事务失败率突增、锁等待超时频繁,都值得告警。
监控接入不是一蹴而就的,可以先接基础指标,跑一段时间有了基线再细化告警规则。我个人的经验是,分布式事务的问题往往在流量高峰才暴露,所以压测阶段就要把监控打开,观察指标变化,别等生产出事才补监控。
整套环境搭下来,最深的体会是:Seata 的坑大多不在 Seata 本身,而在版本对齐和配置一致性上。Server 和客户端的版本、Nacos 的命名空间和分组、注册与配置的对应关系,这几样对齐了,剩下的就是业务代码里注解和异常处理的细节。Docker 部署本身不复杂,复杂的是让容器网络、注册中心、配置中心、数据库这几方互相看得见、说得上话。我习惯在搭环境时把每个组件的连通性单独验证一遍,再串起来联调,这样出问题时能快速定位是哪一环断了。另外,测试环境跑通后别急着上生产,先把存储模式切到 db、把监控接上、把控制台端口收好,这几步做完,心里才踏实。