分布式事务
微服务和分库分表之后,一个业务操作要跨多个数据库或服务,本地事务失效,需要分布式事务方案。主线:强一致方案(2PC/TCC)和最终一致方案(Saga/本地消息表),选型看一致性要求和吞吐。
强一致:2PC 与 3PC
2PC(两阶段提交):协调者先问所有参与者“能提交吗”(准备阶段),全部同意才广播提交,任一拒绝则全部回滚。
问题:准备阶段后协调者宕机,参与者阻塞等待;协调者单点故障;同步阻塞性能差。
3PC:在 2PC 基础上加超时机制和 CanCommit 阶段,减少阻塞窗口,但引入新问题(网络分区时可能违反一致性),实际应用少。
2PC 在分布式事务里更多作为理论基础:XA 协议就是 2PC 的数据库实现,性能开销大,高并发场景基本不用。
TCC:业务补偿
TCC(Try-Confirm-Cancel)把每个操作拆成三个方法:
- Try:预留资源(冻结库存、冻结金额)
- Confirm:确认执行(扣减冻结的库存)
- Cancel:取消回滚(释放冻结)
优势:不依赖数据库锁,性能比 2PC 好。代价:业务侵入大,每个操作要写三个方法;要处理幂等、空回滚(Try 没执行就收到 Cancel)、悬挂(Cancel 先于 Try)。Seata 的 TCC 模式是生产常用选择。
Saga:最终一致
Saga 把长事务拆成一系列本地事务,每个事务有对应的补偿事务。正向执行,失败则反向补偿。两种编排:编排式(一个协调者串流程)和协同式(事件驱动,各服务自己订阅和响应)。
优势:无锁、适合长流程。代价:没有隔离性(中间状态对外可见),补偿逻辑要自己写,业务要能容忍中间状态。
本地消息表(可靠消息最终一致)
写业务和写消息表在同一个本地事务里,消息表作为“发出去的保证”:
- 业务操作和消息写入同事务提交
- 定时任务扫描未发送消息,投递 MQ
- 消费方处理成功后回执,标记消息已处理
变体是 Outbox 模式(用 binlog 监听替代定时扫描)。这是最终一致方案里实现最简单、最可靠的,适合异步场景。
选型
| 方案 | 一致性 | 性能 | 业务侵入 | 适用 |
|---|---|---|---|---|
| XA(2PC) | 强 | 低 | 低 | 低并发、强一致 |
| TCC | 强 | 中 | 高 | 核心资金类,Seata 支持 |
| Saga | 最终 | 高 | 中 | 长流程、跨服务 |
| 本地消息表 | 最终 | 高 | 低 | 异步、可延迟 |
一个务实的结论:大多数业务能接受最终一致,优先本地消息表或 Saga;只有资金类强一致场景才上 TCC。Seata 是目前最主流的分布式事务框架,支持 AT(自动补偿)、TCC、Saga 多种模式。
面试追问
- 2PC 的问题? 同步阻塞、协调者单点、准备后宕机参与者悬挂。是理论基础,XA 实现性能差
- TCC 和 2PC 的区别? TCC 用业务补偿替代数据库锁,Try 预留、Confirm 确认、Cancel 回滚。性能好但侵入大,要处理幂等、空回滚、悬挂
- Saga 怎么保证一致性? 正向本地事务加反向补偿事务,失败反向执行。没有隔离性,中间状态可见
- 本地消息表原理? 业务和消息写同事务,定时任务投递,消费回执。Outbox 用 binlog 监听替代扫描
- Seata 怎么选模式? AT 模式自动补偿侵入最小,TCC 适合强一致核心链路,Saga 适合长流程。能接受最终一致就别上强一致