微服务架构里,”分布式事务”是个绕不开的问题。在做订单相关业务时,一次下单操作需要同时写订单服务、库存服务、积分服务,如果中间某个服务挂了,就会出现数据不一致的情况。
引入 Seata 是为了解决这个问题。Seata 是阿里开源的分布式事务框架,支持 AT、TCC、SAGA、XA 四种模式。项目里用的是 AT 模式,改造成本最低,只需要加一个注解。
AT 模式原理简述
AT 模式基于对业务代码无侵入的两阶段提交:
- 一阶段:每个参与者(RM)在本地数据库执行 SQL,同时在同一个库里的
undo_log 表记录”回滚日志”(前镜像 + 后镜像),然后向 Seata TC(事务协调者)注册分支事务。 - 二阶段提交:如果所有分支都成功,TC 通知各 RM 删除
undo_log,提交完成。 - 二阶段回滚:如果有分支失败,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: server-addr: 192.168.1.61:8848 namespace: local group: SEATA_GROUP data-id: seataServer.properties username: nacos password: nacos registry: type: 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 的配置,核心内容:
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
service.vgroupMapping.default_tx_group=default
|
完整的配置模板参考 Seata 官方 config.txt。
4. 创建 Seata 存储数据库
新建数据库 seata,执行官方 MySQL 建表脚本,会建三张表:global_table、branch_table、lock_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 enable-auto-data-source-proxy: true 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
@GlobalTransactional public void createOrder(OrderRequest request) { orderService.save(request); inventoryService.deduct(request.getSkuId(), request.getQuantity()); 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 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。