分布式事务的困境:2PC、TCC、Saga 的优缺点对比
聊到分布式系统,事务问题永远绕不开。我早年做金融系统时,被分布式事务坑过好几次。说白了,在单库时代,ACID 是铁律;到了微服务时代,数据散落在几十个服务里,你再想用一把锁搞定所有事情,基本不现实。
这一节,我把主流的分布式事务方案——2PC、TCC、Saga——掰开揉碎讲清楚。它们各自的优缺点,以及为什么最终一致性成了大家的首选。
2PC:两阶段提交,理想很丰满
2PC 是最早的分布式事务方案。它有个协调者,负责问所有参与者:“准备好了吗?” 如果都说“准备好了”,协调者再发一条“提交”。
听起来简单,对吧?但我在项目中遇到过它的致命伤。
核心流程
- 第一阶段(准备):协调者问所有节点,节点锁住资源,返回“可以提交”。
- 第二阶段(提交/回滚):协调者收到全部“可以”,就发提交;否则发回滚。
| 维度 | 说明 |
|---|---|
| 优点 | 强一致性,实现简单,数据库原生支持(XA 协议) |
| 缺点 | 同步阻塞,协调者单点,脑裂风险,性能差 |
你想想看,2PC 在第二阶段如果协调者挂了,所有参与者都卡在“已准备”状态,资源锁着不放。我见过一个生产事故,就是因为协调者宕机,数据库连接池被占满,整个系统挂了半小时。
避坑指南
我曾经在一个高并发订单系统里尝试用 2PC,结果压测时 TPS 直接掉到个位数。2PC 只适合低并发、短事务、参与者少的场景。别在高流量核心链路里用它。
TCC:业务补偿,灵活但复杂
TCC 是 Try-Confirm-Cancel 的缩写。它把每个操作拆成三个阶段:预留资源(Try)、确认执行(Confirm)、取消回滚(Cancel)。
说白了,TCC 是业务层面的 2PC。它不依赖数据库锁,而是靠业务代码实现补偿。
| 阶段 | 动作 | 示例(账户扣款) |
|---|---|---|
| Try | 预留资源 | 冻结 100 元,状态标记为“冻结中” |
| Confirm | 确认执行 | 扣减冻结金额,状态改为“已扣款” |
| Cancel | 回滚补偿 | 解冻金额,状态恢复“可用” |
优缺点
- 优点:不锁数据库资源,性能比 2PC 好,适合高并发。
- 缺点:业务侵入性强,每个接口都要写 Try/Confirm/Cancel 三套逻辑。空回滚、幂等、悬挂问题处理起来很头疼。
我记得有一次做支付系统,TCC 的 Cancel 逻辑里忘了处理幂等,结果重复回滚导致用户账户多了一笔钱。嗯,那天的复盘会开了三个小时。
我的建议
TCC 适合对一致性要求高、但能接受短暂不一致的场景。比如账户转账、库存扣减。但一定要做好幂等和空回滚防护。
Saga:长事务的救星
Saga 是另一种思路。它把一个长事务拆成多个本地事务,每个本地事务都有对应的补偿操作。如果某个步骤失败,就反向执行补偿。
Saga 有两种模式:
- 编排模式(Choreography):每个服务完成后发事件,下一个服务监听事件触发。没有中心协调者。
- 协调模式(Orchestration):有一个协调者服务,负责告诉每个服务该做什么。
| 维度 | 编排模式 | 协调模式 |
|---|---|---|
| 复杂度 | 低,服务间通过事件解耦 | 高,协调者承担所有逻辑 |
| 可追踪性 | 差,流程分散在各服务日志 | 好,协调者记录完整流程 |
| 适用场景 | 简单链路,服务少 | 复杂链路,需要集中管控 |
我个人习惯用协调模式。虽然多了一个服务,但出问题时排查起来方便很多。我曾经在编排模式里追一个补偿失败的问题,翻了五个服务的日志才找到原因。
注意
Saga 没有隔离性。如果两个 Saga 事务并发操作同一数据,可能出现脏写。你需要业务层面做防重和幂等控制。
为什么消息最终一致性成了主流?
上面三种方案,各有各的痛点。2PC 太慢,TCC 太累,Saga 太绕。那有没有更简单的办法?
有。就是消息最终一致性。
核心思想很简单:
- 本地事务和发消息放在一个事务里(比如 Outbox Pattern)。
- 消息可靠投递到消息队列。
- 下游服务消费消息,执行自己的本地事务。
- 如果失败,通过重试+幂等保证最终一致。
你想想看,这比 2PC 轻量多了。没有锁,没有协调者,没有复杂的补偿逻辑。只要保证消息不丢、不重复消费,数据最终能对齐。
为什么它成了主流?
- 性能好:没有同步阻塞,吞吐量高。
- 解耦强:服务之间只依赖消息,不依赖 RPC。
- 容错好:消息队列本身有持久化、重试、死信机制。
- 实现简单:相比 TCC 的三套代码,消息方案只需要一个 Outbox 表和一条消息发送逻辑。
当然,它也有代价。最终一致性意味着在某个时间窗口内,数据是不一致的。比如用户下单后,积分服务可能延迟几秒才更新。但大部分业务场景能接受这个窗口。
我的经验
我在电商系统里用消息最终一致性处理订单和库存同步。高峰期每秒几千单,消息队列稳得很。偶尔有消息积压,但从来没丢过数据。相比之前用 TCC 时天天调补偿逻辑,这简直是解放。
三种方案对比一览
| 方案 | 一致性 | 性能 | 复杂度 | 业务侵入 | 适用场景 |
|---|---|---|---|---|---|
| 2PC | 强一致 | 低 | 中 | 低 | 低并发、短事务、参与者少 |
| TCC | 强一致 | 中 | 高 | 高 | 高并发、需要预留资源 |
| Saga | 最终一致 | 中 | 中高 | 中 | 长事务、多步骤流程 |
| 消息最终一致 | 最终一致 | 高 | 低 | 低 | 高吞吐、异步解耦、大部分业务 |
看到这里你应该明白了。没有银弹。选哪种方案,取决于你的业务对一致性的要求、对性能的容忍度、以及团队能承受的复杂度。
但如果你问我,我会说:能用消息最终一致性解决的,就别折腾 2PC 和 TCC 了。
这张图展示了分布式事务方案的演进脉络。从 2PC 到 TCC、Saga,再到消息最终一致性,趋势很清晰:从强一致走向最终一致,从中心化走向异步解耦。