SpringCloud Alibaba 核心组件解析:分布式事务(Seata)
2026/6/16 12:37:03 网站建设 项目流程

SpringCloud Alibaba 核心组件解析:分布式事务(Seata)

技术栈:Spring Boot 3.2.0 + Spring Cloud Alibaba 2023.0.0.0-RC1 +Seata AT 模式+ Nacos + MyBatis-Plus


3.1 是什么 — 分布式事务的核心概念

3.1.1 生活化类比:跨国汇款

你在中国的银行 A 向美国的银行 B 汇款 1000 美元: ① 银行 A 从你的账户扣除 $1000 ② 银行 A 通知银行 B:"给那个账户加 $1000" ③ 银行 B 收到通知,给目标账户增加 $1000 问题:如果第③步失败了(网络中断、银行 B 系统故障), 第①步已经扣了你的钱,怎么办? → 需要"分布式事务"来回滚第①步的操作。

3.1.2 技术定义

分布式事务:一个业务操作跨越多个独立的数据库/服务,需要保证所有操作要么全部成功(Commit),要么全部失败回滚(Rollback)。

3.1.3 Seata 的角色分工

┌──────────────────────┐ │ TC (Transaction │ ← 事务协调者(独立部署的 seata-server) │ Coordinator) │ 维护全局事务的提交/回滚状态 └──────────┬───────────┘ │ 注册/汇报 ┌──────────┼──────────┐ │ │ │ ▼ ▼ ▼ ┌─────────┐┌─────────┐┌─────────┐ │ TM ││ RM ││ RM │ │(发起方) ││(参与方) ││(参与方) │ └─────────┘└─────────┘└─────────┘
角色说明对应本项目的服务
TC事务协调者,独立部署seata-server
TM事务管理器,标注@GlobalTransactionalseata-order-service2001
RM资源管理器,管理分支事务seata-storage-service2002seata-account-service2003

3.2 为什么 — 四种分布式事务模式对比

3.2.1 AT 模式(本项目使用,推荐)

原理:一阶段执行业务 SQL + 记录 undo_log;二阶段提交时删除 undo_log,回滚时执行反向 SQL。

优点缺点
✅ 对业务代码零侵入❌ 依赖数据库 ACID
✅ 自动生成回滚 SQL❌ 仅支持关系型数据库
✅ 性能好(一阶段即提交)❌ 需要额外的 undo_log 表

3.2.2 四种模式对比

模式数据一致性性能业务侵入适用场景
AT最终一致⭐⭐⭐⭐一般微服务(推荐)
TCC最终一致⭐⭐⭐⭐⭐高(需实现 try/confirm/cancel)高性能场景
SAGA最终一致⭐⭐⭐⭐⭐中(需实现补偿)长事务/老系统
XA强一致⭐⭐银行/金融

3.2.3 AT 模式回滚原理

一阶段(执行业务 SQL + 记录 undo_log): Order → INSERT INTO t_order (id=1, status=0) undo_log: DELETE FROM t_order WHERE id=1 Storage → UPDATE t_storage SET used=used+10, residue=residue-10 WHERE product_id=1 undo_log: UPDATE t_storage SET used=used-10, residue=residue+10 WHERE product_id=1 Account → UPDATE t_account SET used=used+100, residue=residue-100 WHERE user_id=1 undo_log: UPDATE t_account SET used=used-100, residue=residue+100 WHERE user_id=1 二阶段-提交(无异常):删除所有 undo_log 二阶段-回滚(异常触发):执行 undo_log 中的反向 SQL,数据恢复到一阶段前

3.3 怎么做 — Seata AT 完整实战

3.3.0 小 Demo:先暴露痛点

// ❌ 没有分布式事务时:本地事务管不了远程调用@Transactional// 只管当前数据库publicvoidcreateOrder(Orderorder){orderMapper.insert(order);// ✅ 这个能回滚storageFeignApi.decrease(...);// ❌ Feign 调用不受 @Transactional 控制accountFeignApi.decrease(...);// ❌ 万一这里失败,库存已经扣了}

3.3.1 项目架构

用户下单 (userId=1, productId=1, count=10, money=100) │ ▼ ┌─────────────────────────┐ │ seata-order-service2001 │ TM(发起方) │ 数据库: seata_order │ → ① 创建订单 │ @GlobalTransactional │ └──────┬──────────┬────────┘ │ Feign │ Feign ▼ ▼ ┌──────────────┐ ┌──────────────────┐ │ storage-2002 │ │ account-2003 │ │ seata_storage│ │ seata_account │ │ → ② 扣库存 │ │ → ③ 扣余额 │ └──────────────┘ └──────────────────┘

3.3.2 步骤 ①:启动 Seata Server

# 下载 seata-server,修改 conf/application.yml 注册到 Nacos# seata:# registry:# type: nacos# nacos:# server-addr: 127.0.0.1:8848# group: SEATA_GROUP# application: seata-server# Windows 启动seata-server.bat

3.3.3 步骤 ②:三个服务公共 Seata 配置

# 每个服务的 application.yml(订单/库存/账户 都相同)seata:registry:type:nacosnacos:server-addr:127.0.0.1:8848namespace:""group:SEATA_GROUPapplication:seata-servertx-service-group:default_tx_group# 事务分组service:vgroup-mapping:# 事务分组 → TC 集群映射default_tx_group:defaultdata-source-proxy-mode:AT# AT 模式logging:level:io.seata:info

配置四要素

配置含义
tx-service-group事务分组名,与 TC 端service.vgroupMapping对应
vgroup-mapping将事务分组映射到 TC 集群名(生产环境有多个 TC 集群)
registry.type: nacosTM/RM 通过 Nacos 发现 TC 地址
data-source-proxy-mode: AT启用 AT 模式数据源代理

3.3.4 步骤 ③:订单服务 — TM(核心)

// seata-order-service2001/.../service/impl/OrderServiceImpl.java@Service@Slf4jpublicclassOrderServiceImplextendsServiceImpl<OrderMapper,Order>implementsOrderService{@ResourceprivateOrderMapperorderMapper;@ResourceprivateStorageFeignApistorageFeignApi;// Feign → 库存服务@ResourceprivateAccountFeignApiaccountFeignApi;// Feign → 账户服务@Override@GlobalTransactional(name="zzyy-create-order",rollbackFor=Exception.class)// ↑ ↑ ↑ 核心!声明全局事务publicvoidcreate(Orderorder){// 查看全局事务 XID(Seata 自动生成)Stringxid=RootContext.getXID();log.info("==================>开始新建订单, xid: {}",xid);// ① 新建订单order.setStatus(0);intresult=orderMapper.insert(order);OrderorderFromDB=null;if(result>0){orderFromDB=orderMapper.selectById(order.getId());log.info("-------> 新建订单成功: {}",orderFromDB);// ② 扣减库存(Feign 远程调用)log.info("-------> 开始调用 Storage 扣减库存");storageFeignApi.decrease(orderFromDB.getProductId(),orderFromDB.getCount());log.info("-------> 结束调用 Storage");// ③ 扣减余额(Feign 远程调用)log.info("-------> 开始调用 Account 扣减余额");accountFeignApi.decrease(orderFromDB.getUserId(),orderFromDB.getMoney());log.info("-------> 结束调用 Account");// ④ 修改订单状态 → 已完结orderFromDB.setStatus(1);orderMapper.updateById(orderFromDB);log.info("-------> 修改订单状态完成");}log.info("==================>结束新建订单, xid: {}",xid);}}

3.3.5 步骤 ④:Feign 接口

// cloud-api-commons/.../apis/StorageFeignApi.java@FeignClient(name="seata-storage-service")publicinterfaceStorageFeignApi{@PostMapping("/storage/decrease")ResultDatadecrease(@RequestParam("productId")LongproductId,@RequestParam("count")Integercount);}// cloud-api-commons/.../apis/AccountFeignApi.java@FeignClient(value="seata-account-service")publicinterfaceAccountFeignApi{@PostMapping("/account/decrease")ResultDatadecrease(@RequestParam("userId")LonguserId,@RequestParam("money")Longmoney);}

3.3.6 步骤 ⑤:库存服务 — RM

// seata-storage-service2002/.../service/impl/StorageServiceImpl.java@Service@Slf4jpublicclassStorageServiceImplextendsServiceImpl<StorageMapper,Storage>implementsStorageService{@ResourceprivateStorageMapperstorageMapper;@Overridepublicvoiddecrease(LongproductId,Integercount){log.info("------->storage-service 扣减库存开始");storageMapper.decrease(productId,count);log.info("------->storage-service 扣减库存结束");}}// SQL: UPDATE t_storage SET used=used+#{count}, residue=residue-#{count}// WHERE product_id=#{productId}

3.3.7 步骤 ⑥:账户服务 — RM(含超时测试)

// seata-account-service2003/.../service/impl/AccountServiceImpl.java@Service@Slf4jpublicclassAccountServiceImplextendsServiceImpl<AccountMapper,Account>implementsAccountService{@ResourceAccountMapperaccountMapper;@Overridepublicvoiddecrease(LonguserId,Longmoney){log.info("------->account-service 扣减余额开始");accountMapper.decrease(userId,money);// 模拟超时异常 → 触发全局事务回滚myTimeOut();// int age = 10/0; // 也可模拟除零异常log.info("------->account-service 扣减余额结束");}privatestaticvoidmyTimeOut(){try{TimeUnit.SECONDS.sleep(65);}catch(InterruptedExceptione){e.printStackTrace();}}}

💡测试:调用http://localhost:2001/order/create?...→ 账户服务 sleep 65 秒 → TC 超时 → 全局回滚:订单删除、库存恢复、余额恢复。


3.4 深入原理 — undo_log 表结构

每张参与分布式事务的表都需要创建undo_log表:

CREATETABLE`undo_log`(`id`bigintNOTNULLAUTO_INCREMENT,`branch_id`bigintNOTNULLCOMMENT'分支事务ID',`xid`varchar(100)NOTNULLCOMMENT'全局事务ID',`context`varchar(128)NOTNULL,`rollback_info`longblobNOTNULLCOMMENT'回滚信息(反向SQL)',`log_status`intNOTNULLCOMMENT'状态:0正常,1已全局提交',`log_created`datetimeNOTNULL,`log_modified`datetimeNOTNULL,PRIMARYKEY(`id`),UNIQUEKEY`ux_undo_log`(`xid`,`branch_id`))ENGINE=InnoDB;

3.5 面试题

Q1:Seata 的 AT 模式和 TCC 模式有什么区别?什么时候用哪个?

  • AT:自动生成反向 SQL,业务代码零侵入,依赖数据库 ACID。适合一般业务场景。
  • TCC:需手动实现 Try(预留)、Confirm(确认)、Cancel(取消)三个接口。性能更好但开发成本高。适合核心高性能链路。
  • 选型原则:能用 AT 则 AT,除非有极致性能要求才用 TCC。

Q2:@GlobalTransactional 和 @Transactional 有什么区别?

@Transactional是 Spring 本地事务,只控制单个数据源;@GlobalTransactional是 Seata 全局事务,由 TC 协调多个 RM 的本地事务,实现跨服务的整体提交或回滚。

Q3:Seata AT 模式下如果 TC Server 挂了怎么办?

:TC 需要高可用集群部署。AT 模式下,一阶段本地事务已提交,业务不会阻塞;但全局回滚能力暂时丧失。TC 恢复后会根据 undo_log 继续处理未完成的全局事务。


3.6 踩坑指南

现象原因解决
🔴 undo_log 不存在Table 'xxx.undo_log' doesn't exist未在每个业务库创建 undo_log 表在每个参与分布式事务的数据库中执行建表 SQL
🔴 全局事务不回滚异常了数据还在rollbackFor = Exception.class未配置显式写@GlobalTransactional(rollbackFor = Exception.class)
🔴 TC 连接不上启动报can not connect to seata-serverTC 未启动或 Nacos 配置不对确认seata-server.bat已运行,Nacos 中可看到seata-server服务
🔴 MyBatis-Plus 版本冲突NoSuchMethodErrorSeata 对 MyBatis 版本敏感统一使用父 POM 管理的版本
🔴 超时设置默认超时太短TC 默认全局事务超时 60s在 TC 端application.yml中调大service.default.grouplist.timeout

3.7 章节总结

要点说明
三大角色TC(协调者)+ TM(发起方,@GlobalTransactional)+ RM(参与方)
AT 模式自动生成 undo_log 反向 SQL,业务零侵入,二阶段执行提交或回滚
四种模式AT(推荐)、TCC(高性能)、SAGA(长事务)、XA(强一致)
核心注解@GlobalTransactional(name="...", rollbackFor=Exception.class)
核心配置tx-service-group + vgroup-mapping + registry.type +>

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

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

立即咨询