Seata 分布式事务接入记录

微服务架构里,”分布式事务”是个绕不开的问题。在做订单相关业务时,一次下单操作需要同时写订单服务、库存服务、积分服务,如果中间某个服务挂了,就会出现数据不一致的情况。

引入 Seata 是为了解决这个问题。Seata 是阿里开源的分布式事务框架,支持 AT、TCC、SAGA、XA 四种模式。项目里用的是 AT 模式,改造成本最低,只需要加一个注解。


AT 模式原理简述

AT 模式基于对业务代码无侵入的两阶段提交:

  1. 一阶段:每个参与者(RM)在本地数据库执行 SQL,同时在同一个库里的 undo_log 表记录”回滚日志”(前镜像 + 后镜像),然后向 Seata TC(事务协调者)注册分支事务。
  2. 二阶段提交:如果所有分支都成功,TC 通知各 RM 删除 undo_log,提交完成。
  3. 二阶段回滚:如果有分支失败,TC 通知各 RM 根据 undo_log 恢复数据,再删除日志。

优点:业务代码基本不需要改,只加 @GlobalTransactional
缺点:性能有损耗(写 undo_log + 全局锁),适合对强一致性有要求但不是极高并发的场景。


部署 Seata Server(Docker + Nacos)

1. 拉取并初始化容器

# 拉取镜像
docker pull seataio/seata-server:1.6.1

# 先运行临时容器,用于拷贝默认配置
docker run -d --name seata-tmp seataio/seata-server:1.6.1

# 创建挂载目录并拷贝配置
mkdir -p /mydata/seata/seata-server
docker cp seata-tmp:/seata-server /mydata/seata/

# 删除临时容器
docker stop seata-tmp && docker rm seata-tmp

2. 修改配置文件

编辑 /mydata/seata/seata-server/resources/application.yml

server:
port: 7091

spring:
application:
name: seata-server

console:
user:
username: seata
password: seata # 管理控制台密码,建议修改

seata:
config:
type: nacos # 从 Nacos 读取配置
nacos:
server-addr: 192.168.1.61:8848
namespace: local
group: SEATA_GROUP
data-id: seataServer.properties # Nacos 中的配置 Data ID
username: nacos
password: nacos
registry:
type: nacos # 注册到 Nacos
nacos:
application: seata-server
server-addr: 192.168.1.61:8848
group: SEATA_GROUP
namespace: local
cluster: default
username: nacos
password: nacos

3. 在 Nacos 中添加 Seata 配置

在 Nacos 对应命名空间的 SEATA_GROUP 下,创建 Data ID 为 seataServer.properties 的配置,核心内容:

# 事务存储模式:db(需要建表)
store.mode=db
store.lock.mode=db
store.session.mode=db

store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://192.168.1.61:3306/seata?useUnicode=true
store.db.user=root
store.db.password=your_password

# 服务映射(客户端通过这个找到 Seata)
service.vgroupMapping.default_tx_group=default

完整的配置模板参考 Seata 官方 config.txt

4. 创建 Seata 存储数据库

新建数据库 seata,执行官方 MySQL 建表脚本,会建三张表:global_tablebranch_tablelock_table

5. 正式启动

docker run --name seata \
--restart always \
-p 8091:8091 \
-p 7091:7091 \
-v /mydata/seata/seata-server:/seata-server \
-e SEATA_IP=192.168.1.61 \
-e SEATA_PORT=8091 \
-d seataio/seata-server:1.6.1

启动后通过 Nacos 控制台确认 seata-server 已注册成功,或访问 http://192.168.1.61:7091 查看 Seata 控制台。


业务数据库准备

每个参与 AT 事务的业务数据库,都需要建 undo_log 表:

CREATE TABLE `undo_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`branch_id` bigint NOT NULL COMMENT '分支事务 ID',
`xid` varchar(100) NOT NULL COMMENT '全局事务 ID',
`context` varchar(128) NOT NULL COMMENT '序列化方式',
`rollback_info` longblob NOT NULL COMMENT '回滚数据',
`log_status` int NOT NULL COMMENT '0:正常 1:防御',
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

业务服务接入

1. 添加依赖


<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<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.6.1</version>
</dependency>

2. 业务服务配置

seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: default_tx_group # 必须与 Nacos 配置中的 vgroupMapping 一致
enable-auto-data-source-proxy: true # 开启数据源代理(AT 模式必须)
data-source-proxy-mode: AT
config:
type: nacos
nacos:
server-addr: 192.168.1.61:8848
namespace: local
group: SEATA_GROUP
data-id: seataServer.properties
username: nacos
password: nacos

3. 使用 @GlobalTransactional

// A 方法是入口,需要保证 B 和 C 的数据一致性
@GlobalTransactional
public void createOrder(OrderRequest request) {
// 1. 写订单
orderService.save(request);
// 2. 扣库存(调用库存服务,走 Feign)
inventoryService.deduct(request.getSkuId(), request.getQuantity());
// 3. 加积分(调用积分服务,走 Feign)
pointService.add(request.getUserId(), request.getPoints());
}

只要给入口方法加 @GlobalTransactional,下游服务的 @Transactional 正常写就行,Seata 会自动协调。如果任意一个步骤抛出异常,所有已执行的 SQL 都会根据 undo_log 回滚。


踩坑记录

1. seata-spring-boot-starter 版本问题

:使用 1.4.2 版本时,某些情况下序列化会有问题,具体表现为回滚时无法正确反序列化 undo_log 数据。

建议:直接用 1.6.x,不要用老版本。

2. 与 dynamic-datasource 共用时的配置

项目中使用了 dynamic-datasource-spring-boot-starter 做多数据源切换时,需要额外配置:

spring:
datasource:
dynamic:
seata: true # 必须加上,否则 Seata 的数据源代理会失效
primary: master

不加这个配置的话,Seata 的数据源代理无法正确拦截到 SQL,AT 模式不会生效。

3. tx-service-group 与 vgroupMapping 必须对应

这是最常见的配置错误:业务服务配置的 tx-service-group: default_tx_group,必须在 Nacos 的 seataServer.properties 中有对应的映射:

service.vgroupMapping.default_tx_group=default

两边名字不一致会导致服务启动后找不到 Seata TC,所有 @GlobalTransactional 注解失效但不报错(因为 Seata 默认不会强制要求 TC 存在)。

4. SEATA_IP 环境变量

如果 Seata Server 部署在 Docker 容器内,必须通过 SEATA_IP 环境变量指定宿主机 IP,否则 Seata 会把容器内的 IP 注册到 Nacos,业务服务无法通过这个 IP 连接到 Seata。