💱 分布式事务
2PC/3PC · TCC · Saga · 本地消息表 · Seata 框架
1. 两阶段提交(2PC)的原理和缺点?
流程(协调者 + 多个参与者):
- 准备阶段(Prepare):协调者问所有参与者"能不能提交"→ 参与者执行事务但不提交,回复 yes/no
- 提交阶段(Commit):全部 yes → 协调者发 commit,各参与者提交;任一 no/超时 → 发 rollback,全部回滚
致命缺点(同步阻塞):
- 同步阻塞:准备阶段所有参与者持有资源锁等协调者指令,长事务下性能差
- 协调者单点:协调者挂了,参与者一直阻塞在"已准备"状态
- 数据不一致窗口:commit 阶段部分参与者收到指令、部分没收到(网络分区)→ 部分提交部分回滚,无法自动恢复(要靠人工/补偿)
3PC 改进:拆成 CanCommit(询问)→ PreCommit(预提交)→ DoCommit 三阶段 + 超时机制,缓解阻塞,但不能完全解决不一致。实际生产极少用 3PC。
🎯 面试要点
- XA 事务(JTA)就是 2PC 的实现,Java 里几乎不用(性能差)
- 2PC 的价值在于"理论起点":面试从 2PC 缺点引出 TCC/Saga 柔性方案
2. TCC 模式?(Try-Confirm-Cancel)
思想:把每个服务的事务操作拆成三阶段:
- Try(预留):检查资源 + 预留资源(冻结库存、冻结余额——不直接扣减)
- Confirm(确认):把预留转成正式(扣减成功)——幂等,失败重试
- Cancel(取消):释放预留(解冻)——幂等,失败重试
特点:不持有数据库长锁(预留只是业务状态),性能比 2PC 好;但侵入性强(每个服务都要写 Try/Confirm/Cancel 三个方法)且 Cancel/Confirm 必须幂等。
TCC 示例:扣减余额 + 扣减库存
// 账户服务
try: // 冻结金额:balance_frozen += 100
confirm: // 确认扣减:balance -= 100; balance_frozen -= 100
cancel: // 解冻:balance_frozen -= 100(幂等:已取消则跳过)
// 库存服务同理(冻结库存 → 扣减/回滚)
// 协调者:Try 全部成功 → 依次 Confirm;任一失败 → 全部 Cancel
🎯 面试要点
- Try 的"预留"是 TCC 的灵魂:把不确定的操作变成可补偿
- 幂等 + 空回滚(Try 未执行就收到 Cancel)+ 悬挂处理(Try 结果丢失)是 TCC 工程三大坑,能说出来是大加分
3. Saga 模式?
思想:长事务拆成一串本地事务(T1, T2, ..., Tn),每个 Ti 有补偿 Ci。执行失败则逆序执行补偿(Cn, ..., C1)。
- 编排式(Orchestration):中央协调器(状态机)逐个调用,控制流程——实现直观、好监控
- choreography(协作式):服务间通过事件链式驱动,无中央——耦合低但流程难追踪
特点:适合长流程(跨服务多步骤,如订单→支付→物流);无资源预占(比 TCC 简单);缺点:无隔离性(中间状态对他人可见)、补偿逻辑要设计周全。
🎯 面试要点
- 对比 TCC:TCC 有"预留"防超卖(强约束),Saga 无预留(最终一致靠补偿)
- Saga 的补偿 = 反向操作,不是"回滚到原状",如"已发货"补偿是"取消订单+退款"
4. 本地消息表方案 & Seata 的三种模式?
本地消息表:业务 + 消息记录同一本地事务 → 定时扫描重发 → 下游消费(幂等)。实现简单可靠,缺点:每个服务要建表 + 定时任务(RocketMQ 事务消息是其升级版)。
Seata(阿里开源,主流方案):
- AT 模式(自动补偿,最常用):无需侵入业务 SQL——框架拦截 SQL 生成 undo_log,全局事务提交/回滚时自动回滚。缺点:锁冲突场景性能一般,依赖全局锁
- TCC 模式:业务自己实现三方法(如上)
- Saga 模式:业务提供正向+补偿,适合长流程
- XA 模式:直连数据库 2PC
🎯 面试要点
- AT 模式原理一句话:拦截 SQL + undo_log 镜像回滚 + 全局锁——讲清这个就是源码级理解
- 选择:事务不复杂且不想写补偿 → AT;高并发强一致 → TCC;长流程 → Saga;简单业务 → 本地消息表/事务消息
- 终极忠告:能用"业务设计避免跨库事务"(如把关联数据放同一库)就别上分布式事务