欢迎光临
我们一直在努力

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

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 事务管理器,标注 @GlobalTransactional seata-order-service2001
RM 资源管理器,管理分支事务 seata-storage-service2002、seata-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 // 只管当前数据库
public void createOrder(Order order) {
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: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: ""
group: SEATA_GROUP
application: seataserver
tx-service-group: default_tx_group # 事务分组
service:
vgroup-mapping: # 事务分组 → TC 集群映射
default_tx_group: default
data-source-proxy-mode: AT # AT 模式
logging:
level:
io.seata: info

配置四要素:

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

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

// seata-order-service2001/…/service/impl/OrderServiceImpl.java
@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order>
implements OrderService {

@Resource
private OrderMapper orderMapper;
@Resource
private StorageFeignApi storageFeignApi; // Feign → 库存服务
@Resource
private AccountFeignApi accountFeignApi; // Feign → 账户服务

@Override
@GlobalTransactional(name = "zzyy-create-order", rollbackFor = Exception.class)
// ↑ ↑ ↑ 核心!声明全局事务
public void create(Order order) {

// 查看全局事务 XID(Seata 自动生成)
String xid = RootContext.getXID();
log.info("==================>开始新建订单, xid: {}", xid);

// ① 新建订单
order.setStatus(0);
int result = orderMapper.insert(order);

Order orderFromDB = 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")
public interface StorageFeignApi {
@PostMapping("/storage/decrease")
ResultData decrease(@RequestParam("productId") Long productId,
@RequestParam("count") Integer count);
}

// cloud-api-commons/…/apis/AccountFeignApi.java
@FeignClient(value = "seata-account-service")
public interface AccountFeignApi {
@PostMapping("/account/decrease")
ResultData decrease(@RequestParam("userId") Long userId,
@RequestParam("money") Long money);
}

3.3.6 步骤 ⑤:库存服务 — RM

// seata-storage-service2002/…/service/impl/StorageServiceImpl.java
@Service
@Slf4j
public class StorageServiceImpl extends ServiceImpl<StorageMapper, Storage>
implements StorageService {
@Resource
private StorageMapper storageMapper;

@Override
public void decrease(Long productId, Integer count) {
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
@Slf4j
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account>
implements AccountService {
@Resource
AccountMapper accountMapper;

@Override
public void decrease(Long userId, Long money) {
log.info("——->account-service 扣减余额开始");
accountMapper.decrease(userId, money);

// 模拟超时异常 → 触发全局事务回滚
myTimeOut();
// int age = 10/0; // 也可模拟除零异常

log.info("——->account-service 扣减余额结束");
}

private static void myTimeOut() {
try { TimeUnit.SECONDS.sleep(65); }
catch (InterruptedException e) { e.printStackTrace(); }
}
}

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


3.4 深入原理 — undo_log 表结构

每张参与分布式事务的表都需要创建 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,
`rollback_info` longblob NOT NULL COMMENT '回滚信息(反向SQL)',
`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;


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-server TC 未启动或 Nacos 配置不对 确认 seata-server.bat 已运行,Nacos 中可看到 seata-server 服务
🔴 MyBatis-Plus 版本冲突 NoSuchMethodError Seata 对 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 + data-source-proxy-mode
每库必备 undo_log 表,Seata 回滚的基础
赞(0)
未经允许不得转载:171主机测评 » SpringCloud Alibaba 核心组件解析:分布式事务(Seata)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址