Skip to content

2PC vs 3PC 对比

我们花了三篇文章讲 2PC 和 3PC,现在来做个横向对比。

但对比之前,我想先问你一个问题:

既然 2PC 有这么多缺陷,为什么银行转账、电商下单这些系统还在用它?

答案可能和你想的不一样——不是因为它完美,而是因为它的缺陷在特定场景下是可以接受的。


横向对比表

特性2PC3PC
阶段数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 改进了什么

  1. 减少了阻塞时间:参与者只在 CanCommit 阶段阻塞,PreCommit 阶段已经锁定资源,超时后可自动处理
  2. 引入了超时机制:参与者不再无限等待,有了「兜底」策略
  3. 多了一个确认阶段:CanCommit 让协调者先确认参与者状态,减少无效的 PreCommit

3PC 仍然存在的问题

  1. 数据一致性仍未保证:网络分区时,部分节点可能自动提交,部分收到协调者指令后回滚
  2. 复杂度显著增加:多一个阶段意味着更多的状态、更多的超时处理、更多的边界情况
  3. 实际收益有限:解决的是「小概率的阻塞问题」,但引入了「大概率的一致性风险」

trade-off 不划算。


工程选型指南

什么时候用 2PC

  • 参与方少(2-5 个)
  • 网络环境相对稳定(内网、金融专线)
  • 对一致性要求高,可以接受一定性能损失
  • 有完善的监控和人工介入机制

典型场景:分布式数据库内部事务、银行核心系统、电商订单系统

什么时候考虑 3PC

坦白说:很少考虑

如果你真的遇到了 2PC 的阻塞问题,大概率应该换一个方案而不是用 3PC。

什么时候完全不用

  • 对性能要求极高,允许最终一致
  • 参与方很多(超过 10 个)
  • 跨地域部署,网络延迟大

典型场景:互联网秒杀系统、社交Feed流、大规模微服务


从 2PC/3PC 到其他方案

2PC/3PC 解决的是「如何让多个节点同时提交或回滚」,这叫原子提交协议

但在实际系统中,2PC 的问题太明显,所以衍生出了其他方案:

TCC(Try-Confirm-Cancel)

把事务拆成三个操作:

  • Try:预留资源(锁定)
  • Confirm:确认使用资源
  • Cancel:释放资源
java
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/3PCPaxos
目标原子提交(所有节点要么全提交,要么全回滚)达成共识(多数派同意)
协调者单个协调者通过选举产生,无单点
一致性不保证(可能数据不一致)保证(多数派一致)
复杂度简单复杂
性能阻塞,性能差非阻塞,性能好

考点四:实际工程选择

面试官可能问你:「如果让你设计一个分布式事务系统,你会怎么做?」

这里的关键不是说「用 2PC」或「用 TCC」,而是展示你对不同方案 trade-off 的理解:

  • 小型系统、高一致性要求:2PC/XA
  • 大型系统、高性能要求:TCC 或 Saga
  • 强一致性、分布式存储:Raft/Paxos

留给你的问题

2PC 和 3PC 都假设「协调者是可信的」。但如果协调者被恶意攻击,或者协调者本身就是有问题的呢?

这就是拜占庭将军问题——如果节点可能发送错误的信息,分布式系统如何达成一致?

关于这个问题,有一个专门的算法家族叫做拜占庭容错协议(BFT)。著名的 PBFT(实用拜占庭容错算法)就是解决这个问题的。

但这是另一个话题了。

基于 VitePress 构建