架构与设计
跨微服务扣款怎么保一致?两阶段提交 2PC 与 TCC 分布式事务实战
用户下单,涉及三个微服务:
订单服务:创建订单,状态改为"待支付"
支付服务:扣用户余额
库存服务:扣减商品库存
订单创建成功、支付扣款成功,但库存扣减失败——用户的钱已经扣了,但库存没扣,订单超卖。
单机数据库里一个事务就能搞定。但在分布式环境下,一个事务跨越三个服务、三个数据库,MySQL 的 BEGIN / COMMIT 管不了。
分布式事务就是来解决这个问题的。
白话版

两种主流方案,用两个生活场景来类比:
2PC(两阶段提交) — 像婚礼上的交换戒指仪式:
第一阶段(准备阶段 Prepare):
司仪问:"新郎,你愿意娶新娘为妻吗?" -> 新郎回答:"我愿意"
司仪问:"新娘,你愿意嫁给新郎吗?" -> 新娘回答:"我愿意"
第二阶段(提交阶段 Commit):
双方都同意 -> 司仪宣布:"交换戒指,正式结为夫妻!"(事务提交)
只要有一方说"不愿意" -> 婚礼中断(事务回滚)
TCC(Try-Confirm-Cancel) — 像预订酒店房间:
1. Try(预留阶段):
调用酒店接口,冻结房间(预留资源),但不正式扣款。
2. Confirm(确认阶段):
客人确认入住,正式扣款消费(真正的业务操作)。
3. Cancel(取消阶段):
客人行程取消,解冻预留的房间(释放资源)。
2PC 与 TCC 深度对比
| 维度 | 2PC (Two-Phase Commit) | TCC (Try-Confirm-Cancel) |
|---|---|---|
| 实现层级 | 数据库/XA 协议层 (底层) | 业务应用代码层 (高层) |
| 锁定资源 | 锁定数据库记录 (长锁,性能低) | 仅冻结/预留业务资源 |
| 性能吞吐 | 低 (强同步阻断) | 高 (非阻塞高并发) |
| 业务入侵性 | 无侵入 (由 DB/框架管理) | 高侵入 (每个接口都要写 Try/Confirm/Cancel 3个方法) |
| 适用场景 | 传统单体拆分少、对性能要求低的系统 | 互联网高并发微服务、核心支付交易 |
TCC 异常处理三大坑
在实际微服务生产环境中,TCC 模式必须处理以下三种网络异常情况:
1. 空回滚 (Empty Cancel)
- 现象:
Try请求因网络丢包压根没收到,但事务协调器超时触发了Cancel。 - 解决:在
Cancel方法中识别Try是否执行过,若未执行,直接返回成功。
2. 悬挂 (Hanging)
- 现象:
Try因网络拥堵延迟到达,而Cancel已经先执行并完成。 - 解决:在
Try执行时检查Cancel是否已执行,若已 Cancel,则拒绝执行Try。
3. 幂等性 (Idempotency)
- 现象:网络重试导致
Confirm或Cancel被重复调用。 - 解决:使用本地事务记录 + 唯一事务 ID 进行去重防重。
小结
- 2PC 适合简单的数据库层分布式事务,但性能较低。
- TCC 适合微服务高性能核心场景,虽然业务代码量翻倍,但并发能力极强。
关注「善忘技术夹」全媒体矩阵
扫描上方宣传海报二维码,第一时间获取最新技术文章、开源项目与免安装小程序体验。