标签:#Seata #分布式事务 #微服务 #SpringCloud #架构设计 #Java
💣 前言:微服务拆分后的“噩梦”
假设一个电商下单流程:
- 订单服务:创建订单 (
INSERT ORDER). - 库存服务:扣减库存 (
UPDATE STOCK SET count = count - 1).
如果没有分布式事务,当库存扣减成功,但网络超时导致订单服务回滚时,库存就永久丢失了(超卖/少卖)。
🤖 一、 Seata AT 模式:黑科技般的“时光倒流”
AT (Automatic Transaction)模式是 Seata 的默认模式,主打“无侵入”。你依然像写本地事务一样写代码,Seata 在底层帮你搞定一切。
1. 原理核心:Undo Log
Seata 会代理 JDBC 数据源。当你执行 SQL 时,Seata 会拦截:
- 第一阶段 (Prepare):
- 解析 SQL,查询更新前的数据(Before Image)。
- 执行 SQL,查询更新后的数据(After Image)。
- 把 Before/After Image 存入
undo_log表。 - 提交本地事务。
- 第二阶段 (Commit/Rollback):
- Commit:异步删除
undo_log。 - Rollback:根据
undo_log中的 Before Image,生成反向 SQL 把数据改回去。
AT 模式流程图 (Mermaid):
2. 代码实战
代码极其简单,只需要在发起方加上@GlobalTransactional。
// 订单服务@GlobalTransactional(name="create-order-tx",rollbackFor=Exception.class)publicvoidcreateOrder(StringuserId,StringcommodityCode,intcount){// 1. 本地逻辑:创建订单orderMapper.insert(newOrder(userId,commodityCode,count));// 2. 远程调用:扣减库存 (Feign)// 库存服务那边只需要普通的 @Transactional 即可storageClient.deduct(commodityCode,count);}3. 致命弱点:全局锁 (Global Lock)
为了防止脏写 (Dirty Write),Seata 在 AT 模式下需要获取全局锁。
在高并发热点商品扣减库存时,所有事务都要排队争抢同一行记录的全局锁。
结论:AT 模式不适合高并发抢购场景。
🛠️ 二、 Seata TCC 模式:硬核的“资源预留”
TCC (Try-Confirm-Cancel)模式不依赖数据库的 ACID,而是把逻辑上升到业务层。它要求开发者为每个操作实现三个方法。
1. 原理核心:资源预留
- Try:资源检测和预留(冻结库存)。
- Confirm:真正的业务提交(使用冻结的库存)。
- Cancel:业务回滚(释放冻结的库存)。
2. 库存扣减的 TCC 设计
我们需要在数据库表中增加一个frozen(冻结) 字段。
- Try 阶段:
UPDATE stock SET count = count - 1, frozen = frozen + 1 WHERE id = 1
(库存没真扣,只是挪到了冻结区) - Confirm 阶段:
UPDATE stock SET frozen = frozen - 1 WHERE id = 1
(真正的扣减,消耗冻结区) - Cancel 阶段:
UPDATE stock SET count = count + 1, frozen = frozen - 1 WHERE id = 1
(回滚,把冻结区的库存还回去)
TCC 流程图 (Mermaid):
3. 代码实战
代码量激增,需要定义接口。
// 定义 TCC 接口@LocalTCCpublicinterfaceStorageTccService{// Try: 返回 boolean 或 void,一定要加上 @TwoPhaseBusinessAction@TwoPhaseBusinessAction(name="deductStock",commitMethod="commit",rollbackMethod="rollback")booleantryDeduct(@BusinessActionContextParameter(paramName="code")StringcommodityCode,@BusinessActionContextParameter(paramName="count")intcount);// Confirmbooleancommit(BusinessActionContextcontext);// Cancelbooleanrollback(BusinessActionContextcontext);}4. 核心优势:无锁高性能
TCC 的锁在数据库行锁层面(本地事务短),没有全局锁。Try 阶段虽然锁了行,但提交极快。
更厉害的是:TCC 不依赖数据库!你甚至可以用 Redis 来做库存扣减(Try 在 Redis 冻结,Confirm 在 Redis 删除)。
⚔️ 三、 AT vs TCC:巅峰对决
| 维度 | AT 模式 (自动) | TCC 模式 (手动) |
|---|---|---|
| 代码侵入性 | 极低(注解即可) | 极高(写3个方法) |
| 开发效率 | 高 | 低 |
| 性能 | 中 (受限于全局锁) | 高(无全局锁) |
| 适用场景 | 后台管理、低并发业务 | 核心交易、高并发、Redis/非DB资源 |
| 复杂性 | 依赖 undo_log 表 | 需处理空回滚、悬挂、幂等 |
🧠 四、 艰难抉择:到底选谁?
回到我们的场景:订单与库存。
1. 场景 A:普通的 B2B 采购系统 (QPS < 100)
选择:AT 模式。
原因:开发快,维护简单,并发低,全局锁不会成为瓶颈。没必要为了那一丢丢性能去写复杂的 TCC。
2. 场景 B:双十一大促 / 直播带货 (QPS > 1000)
选择:TCC 模式(甚至结合 Redis)。
原因:AT 模式的全局锁会导致大量事务等待,拖垮数据库。TCC 允许你在 Try 阶段预占用资源,且优化空间极大(例如将库存放入 Redis 做 TCC)。
🎯 总结
- 能用 AT 就用 AT,毕竟“懒”是程序员的美德。
- 性能瓶颈上 TCC,这是架构师能力的体现。
- TCC 的三个坑:一定要注意处理空回滚(未 Try 先 Cancel)、幂等(多次 Cancel)、悬挂(Cancel 比 Try 先到)。
Next Step:
如果你的业务并发极高,TCC 都嫌慢,那就只能上RocketMQ 事务消息了。那是“最终一致性”的领域,虽然不再保证强一致,但能获得极致的吞吐量。建议去了解一下 RocketMQ 的Half Message机制。