☰
Seata Docker部署全流程:从注册Nacos到客户端接入与常见问题排查
2026/10/17 2:48:30 网站建设 项目流程

Seata这门技术,做微服务尤其是分布式事务方向的同学一定不陌生。它本质是一套开源分布式事务解决方案,解决微服务架构下跨服务、跨数据库调用来带的数据一致性问题。我是在维护一套订单、库存、积分类微服务项目时接触它的,第一次用Docker部署Seata Server就栽了不少跟头,从版本配置对不上,到服务注册不上Nacos,再到客户端全局事务不生效,前前后后折腾了一整天。这篇文章就把我完整的Docker部署运行Seata流程、配置细节、客户端接入和排查经验整理出来,给准备引入Seata但还没理清部署链路的同学做个参考。内容偏实操,你照着走一遍基本能跑通。

1. 先把Seata的角色和部署逻辑理清楚

1.1 Seata到底解决什么问题

微服务拆久了,一定会碰到一个问题:单个请求要跨多个服务写数据。比如用户下单,订单服务写订单表,库存服务扣库存,积分服务加积分,这三个服务各自连各自的库。在单体架构里,一次操作就是一个本地事务,要么全成要么全不成。拆分之后,每个服务各自提交自己的本地事务,就等于把原来一次原子操作拆成了好几次独立操作,一旦中间某一步挂了,前面的数据就留在那了,脏数据就是这么来的。

Seata做的事情,就是把这些分散在不同服务、不同数据库里的操作,重新纳入同一个"全局事务"里管理。当全局事务里任何一个分支失败,Seata会把已经成功执行的分支全部回滚掉,让整体数据保持一致。你可以把它理解成给微服务架构重新装回了一个跨库、跨服务的事务协调机制。

1.2 三个关键角色的分工

Seata设计里核心就三个角色,搞懂它们才好看懂整个部署架构:

  • TC(Transaction Coordinator)事务协调器:独立部署的服务端进程,负责全局事务的开启、提交、回滚决策。我们部署Seata Server,部署的就是这个TC。
  • TM(Transaction Manager)事务管理器:内嵌在业务应用里的客户端组件,负责向TC发起全局事务的开启和最终提交/回滚请求。
  • RM(Resource Manager)资源管理器:同样内嵌在业务应用里,负责管理分支事务对应数据源的操作,向TC注册分支、上报分支执行结果。

用个接地气的类比:TC是整个晚会的总导演,TM是提出"我要办一场晚会"的发起人,RM是每个上台表演的节目组。节目组各演各的,但能不能正式通过、要不要全部重来,由总导演统一拍板。

全局事务的执行流程大概是:TM向TC发起"开启全局事务",TC返回一个全局事务ID(XID),这个XID会沿着服务调用链往下传,每个参与服务里的RM拿到XID后向TC注册分支事务,执行自己的本地SQL。到最后TM根据业务结果决定提交或回滚,TC检查所有分支状态后统一给出结论。

1.3 四种事务模式速览

Seata提供了AT、TCC、SAGA、XA四种事务模式,部署前至少要清楚自己要用哪种:

模式业务侵入说明
AT侵入小通过undo_log自动完成数据回滚,业务上只需加一个注解,使用最普遍
TCC侵入大Try/Confirm/Cancel三段式接口需要业务自己实现,适合高性能和短事务场景
SAGA中大面向长事务,用状态机编排,特点是最终一致性
XA最小依赖数据库本身对XA协议的支持,对数据库版本有要求

我实际项目里默认选AT模式。它对业务代码改动最小,框架自动记录变更前镜像、自动生成补偿SQL,团队上手成本低。不过有个前提——每个参与AT事务的业务库都得建一张undo_log表,这点后面部署时会反复提到。

2. 部署前必须定的三件事:版本、依赖、目录端口

2.1 版本差异比你想的大

Seata的配置文件格式在不同版本之间有相当大的变化,这是最容易踩坑的地方。早期1.3、1.4版本是registry.conf加file.conf两件套,后来1.5版本开始兼容新的application.yml配置方式,到2.x就基本全面转向application.yml,很多配置项名称和层级全变了。

我部署时用的1.4.2版本,原因是网上可查的资料最多,团队客户端依赖也容易锁定。如果你网上随便搜一篇"部署Seata"的文章,跟着操作发现怎么都不对,八成就是文章版本和你镜像版本不一样。下面我给你列个对照表:

Seata版本Server配置文件事务通信端口说明
1.4.xregistry.conf + file.conf8091资料多,生态成熟,中小企业最常见
1.5.x兼容旧配置,同时支持application.yml8091过渡版本,注意配置文件格式差异
2.xapplication.yml8091为主新架构,配置方式变化大,别硬套老教程

这篇重点以1.4.2为例展开,后面我会专门说明新版本需要关注的差别。

2.2 存储、注册中心和配置中心怎么选

部署Seata Server之前,有三个依赖组件要提前想清楚:

  • 注册中心:用于TC服务实例的注册与发现。Nacos是主流选择,和Spring Cloud生态无缝集成;也可以选Consul、Eureka、ZooKeeper,原理都一样。
  • 配置中心:用于统一管理Seata的运行时配置。简单场景直接用本地文件配置就行,生产环境更推荐把配置推到Nacos上统一管理。
  • 存储模式:Seata Server的全局事务、分支事务、全局锁记录需要持久化。有file和db两种模式,file模式适合本地演示,生产环境一律用db模式,也就是把数据落到MySQL里。

我最后定的组合是Nacos做注册中心、本地文件config、MySQL做存储。这是最经典也最容易跑通的一套组合,先把这条链路走顺,再考虑把配置中心切到Nacos也不迟。

为什么生产环境必须用db而不是file?因为file模式数据存在本地文件里,单机还好,你要是部署多个TC节点,节点之间的事务数据就不同步了;而且容器一重建,文件里的数据就没了。db模式下所有TC节点共用同一套MySQL表,全局事务状态共享,才能真正支持高可用。

2.3 端口、目录和网络规划

Seata 1.4.x的默认RPC端口是8091,客户端和TC之间的长连接走这里。到了2.x版本,还会额外暴露一个7091端口,用于HTTP监控和管理。端口规划时我给的建议是:8091不要动,保持默认,对外映射端口也保持一致,省得客户端配置还要改端口。

再一个容易被忽视的是目录规划。我用Docker跑,习惯在宿主机上建 /opt/seata/conf 目录,把registry.conf和file.conf提前放好,然后挂载进容器。这样以后改配置不需要进容器、不需要重新构建镜像,改完重启容器就生效。

网络这块是Docker部署Seata的核心坑点。默认bridge模式下容器有自己的IP,宿主机上的业务服务通过映射端口连TC没问题,但Seata Server注册到Nacos时,默认上报的是容器IP。如果客户端没有和容器在同一个网络里,客户端从Nacos拿到容器IP后根本访问不到,就会报连接超时。解决办法就是设置SEATA_IP环境变量,让TC把宿主机IP上报给Nacos。

3. 完整部署流程:从建库建表到容器跑起来

3.1 初始化Seata的MySQL库

先给Seata Server准备一个专门存储事务数据的库,一般就叫seata。你需要创建三张表:global_table记录全局事务,branch_table记录每个分支事务,lock_table记录AT模式下的全局锁。

CREATE DATABASE IF NOT EXISTS `seata` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE `seata`; CREATE TABLE IF NOT EXISTS `global_table` ( `xid` VARCHAR(128) NOT NULL, `transaction_id` BIGINT, `status` TINYINT NOT NULL, `application_id` VARCHAR(32), `transaction_service_group` VARCHAR(32), `transaction_name` VARCHAR(128), `timeout` INT, `begin_time` BIGINT, `application_data` VARCHAR(2000), `notify` TINYINT, PRIMARY KEY (`xid`), KEY `idx_status` (`status`), KEY `idx_application_id` (`application_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; CREATE TABLE IF NOT EXISTS `branch_table` ( `branch_id` BIGINT NOT NULL, `xid` VARCHAR(128) NOT NULL, `transaction_id` BIGINT, `resource_group_id` VARCHAR(32), `resource_id` VARCHAR(256), `branch_type` VARCHAR(8), `status` TINYINT, `client_id` VARCHAR(64), `application_data` VARCHAR(2000), PRIMARY KEY (`branch_id`), KEY `idx_xid` (`xid`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; CREATE TABLE IF NOT EXISTS `lock_table` ( `row_key` VARCHAR(128) NOT NULL, `xid` VARCHAR(128), `transaction_id` BIGINT, `branch_id` BIGINT NOT NULL, `resource_id` VARCHAR(256), `table_name` VARCHAR(32), `pk` VARCHAR(36), `status` TINYINT NOT NULL DEFAULT '0', PRIMARY KEY (`row_key`), KEY `idx_branch_id` (`branch_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

这些建表语句在Seata官方仓库的script/server/db目录下都能找到,每个版本基本通用。执行的时候注意实际连的是哪个MySQL实例,别在开发库上顺手执行了。

业务库这边也不要忘。你所有要参与AT事务的业务数据库,比如order库、inventory库、user库,每张业务表对应的库里都得建undo_log表:

CREATE TABLE IF NOT EXISTS `undo_log` ( `branch_id` BIGINT 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, PRIMARY KEY (`branch_id`), KEY `idx_union` (`xid`, `branch_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

这是AT模式回滚机制的关键。框架执行本地SQL前,会把数据快照写进undo_log,一旦全局事务需要回滚,就根据undo_log里的前后镜像生成反向SQL。这段逻辑很多第一次用的人不清楚,导致业务库里漏建这张表,运行期直接报错。

3.2 编写registry.conf和file.conf

写两个配置文件是关键步骤,先看registry.conf,它的作用是告诉Seata Server怎么注册、要不要用配置中心:

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

这里我解释几个容易配错的点。application这个值会作为注册到Nacos的服务名,客户端配置里的registry.nacos.application必须和它保持一致,默认是seata-server。group默认是SEATA_GROUP,客户端那边同样要配置成这个组,否则两边各找各的,服务列表就是空的。namespace如果没建专门的命名空间就留空字符串,别写默认值这种字面量,空字符串才是无命名空间的意思。

cluster字段是集群名,默认default。客户端vgroup映射最终会映射到这个集群名上,所以集群名别乱改。

然后是file.conf,它告诉Server事务数据存到哪:

store { mode = "db" db { datasource = "druid" dbType = "mysql" driverClassName = "com.mysql.jdbc.Driver" url = "jdbc:mysql://172.16.0.10:3306/seata?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true" user = "root" password = "你的数据库密码" minConn = 5 maxConn = 100 globalTable = "global_table" branchTable = "branch_table" lockTable = "lock_table" queryLimit = 100 maxWait = 5000 } }

数据库URL里的地址千万不要写localhost或127.0.0.1。因为容器里的Seata Server要通过这个地址去连MySQL,localhost指向容器自己。这里需要写成MySQL所在机器的IP,或者如果MySQL本身也在Docker里,就写MySQL容器的服务名或同一个Docker网络里的别名。

如果MySQL是8.x,密码认证方式和5.7有差异,我建议URL里加上allowPublicKeyRetrieval=true,同时driverClassName改用com.mysql.cj.jdbc.Driver。1.4.2镜像自带的驱动比较老,连MySQL 8可能出现驱动类缺失或认证失败。真要改驱动,就把对应版本的mysql-connector-java jar扔进容器的lib目录,重新打一个自己的镜像最省事。

3.3 启动容器并验证注册结果

配置文件准备好后,拉镜像启动:

docker pull seataio/seata-server:1.4.2 docker run -d \ --name seata-server \ --restart=always \ -p 8091:8091 \ -e SEATA_IP=172.16.0.10 \ -e SEATA_PORT=8091 \ -v /opt/seata/conf/registry.conf:/seata-server/conf/registry.conf \ -v /opt/seata/conf/file.conf:/seata-server/conf/file.conf \ seataio/seata-server:1.4.2

SEATA_IP和SEATA_PORT这两个环境变量很重要。1.4.x的启动脚本会把这些值以系统属性的方式传给JVM,TC向注册中心上报实例IP和端口时用的就是这两个值。之前说过,bridge模式下容器自身IP对宿主机客户端不可达,所以SEATA_IP一定要写客户端能直接访问到的宿主机IP或者内网IP。

容器起来后,第一步看日志:

docker logs -f seata-server

正常情况下能看到Server启动完成、Netty服务绑定端口等日志,没有异常堆栈,说明Server进程本身没问题。第二步去Nacos控制台看服务列表,找到seata-server这个服务,点进详情,应该能看到一个健康实例,IP就是你设置的SEATA_IP,端口8091,集群名default。看到这一步,说明Server已经注册成功了。

如果Nacos那边看不到实例,优先检查registry.conf里的serverAddr、group、namespace,再用docker logs确认容器启动时有没有报Nacos连接失败。

3.4 用Docker Compose管理

单条docker run命令适合快速验证,日常维护我更建议用docker-compose把配置写成文件,团队里其他人拿到就能直接用:

version: "3.8" services: seata-server: image: seataio/seata-server:1.4.2 container_name: seata-server restart: always ports: - "8091:8091" environment: - SEATA_IP=172.16.0.10 - SEATA_PORT=8091 volumes: - /opt/seata/conf/registry.conf:/seata-server/conf/registry.conf - /opt/seata/conf/file.conf:/seata-server/conf/file.conf

启动命令就是docker-compose up -d,改配置后docker-compose restart seata-server。这里提醒一句,如果挂载的是单个配置文件,宿主机上这两个文件必须先存在,否则Docker会当成目录自动创建一个空目录,容器里看到的就是空文件,启动直接失败。

还有一点,如果你用了STORE_MODE=db这个环境变量,容器启动脚本会尝试对file.conf里的store.mode做替换。既然我们已经挂载了写好的file.conf,就没必要再加STORE_MODE变量,避免它和自定义文件互相干扰。

4. 客户端怎么接入

4.1 依赖和版本锁定

Server跑通了,剩下的就是把业务服务接进来。如果你用的是Spring Boot加Spring Cloud Alibaba,常规做法是引入这个starter:

<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <version>2021.0.5.0</version> </dependency>

这里有个隐患:Spring Cloud Alibaba的starter会默认带一个它自己锁定的Seata版本,可能和你Server部署的1.4.2不一致。客户端版本和服务端版本差异过大的话,Netty协议握手会报版本不兼容,全局事务根本建立不起来。

我的习惯是显式排除默认版本,改成和自己Server一致的具体版本:

<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <version>2021.0.5.0</version> <exclusions> <exclusion> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>1.4.2</version> </dependency>

如果你的项目不用Spring Cloud Alibaba那套依赖管理,直接用seata-spring-boot-starter再配上1.4.2就行。版本一致性是分布式事务里必须当成原则来遵守的事,不要抱侥幸心理。

4.2 客户端配置文件

以订单服务为例,application.yml里加入以下配置:

spring: application: name: order-service seata: enabled: true application-id: order-service tx-service-group: my_test_tx_group enable-auto-data-source-proxy: true registry: type: nacos nacos: server-addr: 172.16.0.10:8848 namespace: "" group: SEATA_GROUP username: nacos password: nacos application: seata-server config: type: file service: vgroup-mapping: my_test_tx_group: default grouplist: default: 172.16.0.10:8091

重点讲tx-service-group和vgroup-mapping的关系。tx-service-group是客户端定义的一个事务分组逻辑名称,可以随便起;vgroup-mapping的作用是把这个名字映射到Seata Server注册到Nacos的集群名。我们Server里registry.conf设置cluster = "default",所以这里my_test_tx_group就映射到default。两个值对不上,客户端就找不到可用的TC实例。

客户端本地的grouplist是给不使用注册中心的情况兜底的,如果走Nacos注册发现,客户端主要靠服务发现拿到TC实例地址。不过保留grouplist也不冲突,反而能在注册中心短暂异常时提供一点容错,我习惯保留。

配置里registry.nacos.application同样要保持seata-server,这个值和Server的registry.conf里application值必须一致。

4.3 注解使用和事务生效条件

客户端接入的代码改动很小,AT模式下核心就一个注解:

@Service public class OrderServiceImpl implements OrderService { @GlobalTransactional(name = "create-order-tx", rollbackFor = Exception.class) @Override public void createOrder(OrderCreateRequest request) { // 1. 本地事务:插入订单 orderMapper.insert(buildOrderEntity(request)); // 2. 远程调用扣减库存 inventoryFeignClient.decrease(request.getProductId(), request.getCount()); // 3. 远程调用增加积分 pointsFeignClient.increase(request.getUserId(), request.getPoints()); } }

@GlobalTransactional要加在全局事务的入口方法上,也就是那个负责编排远程调用的方法。加错位置、加在内部方法上,全局事务都不会按预期开启。

rollbackFor我每次都写Exception.class。Seata默认只对RuntimeException回滚,但你的远程服务可能抛出受检异常、自定义业务异常,不指定rollbackFor,全局事务就不会回滚,数据照样不一致。

还有一个细节:既然用了AT模式,框架需要接管数据源才能生成undo_log并做后镜像对比。seata.enabled和enable-auto-data-source-proxy默认就是开启的,正常用没问题。如果你的项目用了动态数据源、读写分离这类组件,注意数据源代理的执行顺序,否则Seata代理不到真正的数据源,事务也就不生效。

关于XID的传递,如果你是Spring Cloud生态,Feign/RestTemplate的拦截器会自动把XID放到请求头里透传。如果你的服务之间用的是自研RPC或者裸HTTP调用,需要手动实现XID的传递,否则下游服务不会把自己的分支事务注册到全局事务里。这个坑很隐蔽,日志里看不到明显报错,但数据就是不一致。

5. 常见问题排查与避坑手册

5.1 常见问题速查表

以下这些问题,我基本都是实际踩过或者看别人踩过的,整理成一张表方便你对照:

现象排查方向
启动后Nacos服务列表没有seata-serverregistry.conf的namespace/group是否和Nacos一致;容器能否访问Nacos;SEATA_IP是否配置
客户端报Can not connect to seata server客户端到8091端口网络是否通;注册中心拿到的实例IP是否为宿主机IP;vgroup-mapping映射是否匹配
日志里没看到XID@GlobalTransactional放错方法;远程调用链路上XID没有透传
报global_table/branch_table不存在建表脚本没执行,或执行到了错误的MySQL实例
报undo_log表不存在业务库漏建undo_log,不是只建seata库就够
数据库连接报错file.conf里的URL写成了localhost;MySQL 8需要allowPublicKeyRetrieval和正确的驱动
客户端服务端版本不一致控制台或日志出现版本不匹配,锁定同一版本

5.2 服务没注册上Nacos

这是我把Server跑起来后遇到的第一个问题。现象是Server日志没有报错,但Nacos控制台服务列表里翻不到seata-server。排查了一小时,发现是registry.conf里namespace写的"public"字符串,而Nacos控制台里public命名空间对应的实际上是一个空字符串,两边不匹配,服务注册到了别的分组里。

另外别忘了,Nacos 2.x客户端和1.x在连接方式上有差异,如果Nacos版本很新,而Seata Server内置的Nacos客户端版本较旧,可能出现注册请求没响应的情况。遇到这种问题,可以先在容器里用docker exec进入容器,测试到Nacos的8848端口连通性,再逐项核对group、namespace、用户名密码。

5.3 全局事务不生效

日志看起来一切正常,但故意制造一次失败,数据不自动回滚。这种问题我遇到过两次。一次是Feign调用链上XID没有传下去,查了半天发现拦截器被全局的RPC过滤器覆盖了。另一次是tx-service-group和vgroup-mapping没配上,客户端根本不知道往哪个集群发起全局事务。

排查思路很简单:在开启全局事务的入口方法里加日志打印RootContext.getXID(),有值说明TM和TC通信正常;再到下游服务打印XID,如果拿不到,问题就出在调用链传递环节;如果下游能拿到XID但最终没回滚,再查数据源代理和undo_log。

5.4 AT模式回滚失败的隐藏风险

就算全局事务正常回滚,也有一种情况让数据看起来"没恢复":回滚时目标行已经被并发事务修改。AT模式回滚逻辑是基于undo_log里保存的前镜像生成的补偿SQL,回滚前会校验当前数据和后镜像是否一致,如果不一致就会抛异常拒绝回滚,防止覆盖别的事务的修改。所以AT模式适合并发冲突不高的场景,如果你这块业务并发更新很频繁,就要考虑TCC模式或者业务层做防重设计。

还有一点,Seata Server和业务应用所在服务器的时间要尽量一致,时区差异会导致事务记录的时间字段对不上。Docker容器默认是UTC时间,我一般给容器加TZ=Asia/Shanghai环境变量,业务应用同理。不然你在控制台排查日志时,时间对不上会非常痛苦。

5.5 关于2.x版本,再说几句

如果你选的是2.x版本,配置文件不再是registry.conf加file.conf,而是application.yml,存储位置和配置项也有调整。挂载路径通常在容器的/seata-server/resources目录下,具体以镜像里的实际结构为准,docker exec进去看一眼就知道。2.x还新增了HTTP管理端口7091,用于提供health等监控接口,部署时记得一并映射出来。

2.x不再建议照抄任何1.4.x教程里的file.conf写法,配置项虽然名称相近,但层级和类型都有改动。最稳妥的办法是先拉镜像,docker inspect一下镜像内容,或者启动一次让它在默认配置下跑起来,再对照官方文档一项项改配置。

我个人在实际操作中的体会是,部署Seata最花时间的不是敲命令,而是把版本、注册中心、配置中心、存储这几个环节的匹配关系想清楚。Docker本身只是把Seata包成了一个方便分发的进程,真正决定成败的还是那几张表、几个配置文件、几个环境变量之间的对齐。

最后再分享一个小技巧:业务服务接入后,别急着写复杂流程,先做一个最简单的两个服务、两个库的冒烟测试,一个方法里先插入A库数据再在B库故意抛异常,确认数据能自动回滚。这条链路验证通过了,再往真实业务上加,排查成本会低很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询