2PC vs 3PC 对比
我们花了三篇文章讲 2PC 和 3PC,现在来做个横向对比。
但对比之前,我想先问你一个问题:
既然 2PC 有这么多缺陷,为什么银行转账、电商下单这些系统还在用它?
答案可能和你想的不一样——不是因为它完美,而是因为它的缺陷在特定场景下是可以接受的。
横向对比表
| 特性 | 2PC | 3PC |
|---|---|---|
| 阶段数 | 2(Prepare + Commit) | 3(CanCommit + PreCommit + DoCommit) |
| 阻塞时间 | 整个 Prepare 阶段 | CanCommit + PreCommit(缩短了) |
| 单点失败影响 | 协调者崩溃后,参与者无限等待 | PreCommit 阶段超时后可能自动提交 |
| 数据一致性保证 | 不保证(Commit 消息可能丢失) | 不保证(网络分区时仍可能不一致) |
| 协调者角色 | 决策者 + 执行触发者 | 决策者 + 执行触发者 |
| 参与者状态 | 等待指令 | 超时可自动处理 |
| 复杂度 | 简单 | 复杂 |
| 实际应用 | XA 事务、分布式数据库 | 很少使用 |
2PC 为什么仍然被广泛使用
场景一:分布式数据库 XA 事务
MySQL、PostgreSQL、Oracle 的 XA 事务,本质上都是 2PC。
为什么银行还在用?
因为 XA 事务的一致性是可以接受的。
- 参与者是数据库,它们有 redo log、undo log,可以恢复
- 网络分区是小概率事件,银行的内网相对稳定
- 出了问题,有人工介入的流程
场景二:参与方少且可控
如果只有 2-3 个参与者,2PC 的缺陷影响范围有限。
- 同步阻塞时间短
- 协调者崩溃的概率低
- 即使出问题,排查和恢复也快
对于核心业务(如转账),宁可慢一点,也要保证不错。
3PC 的改进点与局限
3PC 改进了什么
- 减少了阻塞时间:参与者只在 CanCommit 阶段阻塞,PreCommit 阶段已经锁定资源,超时后可自动处理
- 引入了超时机制:参与者不再无限等待,有了「兜底」策略
- 多了一个确认阶段:CanCommit 让协调者先确认参与者状态,减少无效的 PreCommit
3PC 仍然存在的问题
- 数据一致性仍未保证:网络分区时,部分节点可能自动提交,部分收到协调者指令后回滚
- 复杂度显著增加:多一个阶段意味着更多的状态、更多的超时处理、更多的边界情况
- 实际收益有限:解决的是「小概率的阻塞问题」,但引入了「大概率的一致性风险」
trade-off 不划算。
工程选型指南
什么时候用 2PC
- 参与方少(2-5 个)
- 网络环境相对稳定(内网、金融专线)
- 对一致性要求高,可以接受一定性能损失
- 有完善的监控和人工介入机制
典型场景:分布式数据库内部事务、银行核心系统、电商订单系统
什么时候考虑 3PC
坦白说:很少考虑。
如果你真的遇到了 2PC 的阻塞问题,大概率应该换一个方案而不是用 3PC。
什么时候完全不用
- 对性能要求极高,允许最终一致
- 参与方很多(超过 10 个)
- 跨地域部署,网络延迟大
典型场景:互联网秒杀系统、社交Feed流、大规模微服务
从 2PC/3PC 到其他方案
2PC/3PC 解决的是「如何让多个节点同时提交或回滚」,这叫原子提交协议。
但在实际系统中,2PC 的问题太明显,所以衍生出了其他方案:
TCC(Try-Confirm-Cancel)
把事务拆成三个操作:
- Try:预留资源(锁定)
- Confirm:确认使用资源
- Cancel:释放资源
public class TCCTransaction {
// Try:预留资源,比如冻结库存
public boolean tryDeduct(InventoryService inventory, int amount) {
return inventory.freeze(amount); // 冻结,不是真正扣减
}
// Confirm:真正扣减
public void confirmDeduct(InventoryService inventory, int amount) {
inventory.confirmFreeze(amount); // 确认扣减
}
// Cancel:释放冻结
public void cancelDeduct(InventoryService inventory, int amount) {
inventory.unfreeze(amount); // 释放冻结
}
}TCC 是业务层面的 2PC,把「锁」变成「预留」,减少了数据库层面的阻塞。
Saga
Saga 把长事务拆成多个短事务,每个短事务都可以独立提交/回滚。
如果某个短事务失败,之前的短事务依次回滚(补偿)。
A → B → C → D
失败时:D 回滚 → C 回滚 → B 回滚 → A 回滚Saga 不提供隔离性保证,需要业务层处理并发冲突。
面试总结:核心考点
考点一:2PC/3PC 的缺陷
这是最基础的考察点。必须能清晰地说出:
- 2PC 的三大缺陷:同步阻塞、单点问题、数据不一致
- 3PC 的改进:减少阻塞时间、引入超时
- 3PC 的局限:仍不能保证强一致性
考点二:为什么工业界用 2PC 不用 3PC
标准答案:trade-off 不划算。3PC 解决的痛点不是核心问题,而引入的复杂度却很高。
考点三:2PC/3PC vs Paxos
这是进阶考察点。
| 2PC/3PC | Paxos | |
|---|---|---|
| 目标 | 原子提交(所有节点要么全提交,要么全回滚) | 达成共识(多数派同意) |
| 协调者 | 单个协调者 | 通过选举产生,无单点 |
| 一致性 | 不保证(可能数据不一致) | 保证(多数派一致) |
| 复杂度 | 简单 | 复杂 |
| 性能 | 阻塞,性能差 | 非阻塞,性能好 |
考点四:实际工程选择
面试官可能问你:「如果让你设计一个分布式事务系统,你会怎么做?」
这里的关键不是说「用 2PC」或「用 TCC」,而是展示你对不同方案 trade-off 的理解:
- 小型系统、高一致性要求:2PC/XA
- 大型系统、高性能要求:TCC 或 Saga
- 强一致性、分布式存储:Raft/Paxos
留给你的问题
2PC 和 3PC 都假设「协调者是可信的」。但如果协调者被恶意攻击,或者协调者本身就是有问题的呢?
这就是拜占庭将军问题——如果节点可能发送错误的信息,分布式系统如何达成一致?
关于这个问题,有一个专门的算法家族叫做拜占庭容错协议(BFT)。著名的 PBFT(实用拜占庭容错算法)就是解决这个问题的。
但这是另一个话题了。
