mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1480 字
4 分钟
2PC 与 3PC:分布式事务的协议基础
2025-02-01

某银行跨行转账场景:从 A 银行扣款、向 B 银行入账,两个操作必须同时成功或同时失败。如果 A 扣款成功但 B 入账失败,资金就会凭空消失。这就是分布式事务的经典问题——如何保证跨节点操作的原子性。2PC(两阶段提交)和 3PC(三阶段提交)是解决这一问题的协议基础。

一、分布式事务的原子性问题#

单机事务由数据库通过 WAL(Write-Ahead Logging)保证原子性,但跨节点时没有单一协调者可以依赖。

分布式事务需要保证:

  • 原子性:所有参与者要么全部提交,要么全部回滚
  • 一致性:事务前后数据满足完整性约束
  • 隔离性:并发事务互不干扰
  • 持久性:提交后的结果不会丢失

2PC 和 3PC 主要解决原子性问题。

二、两阶段提交(2PC)#

2.1 协议流程#

2PC 引入一个 协调者(Coordinator)角色,分两阶段完成提交:

阶段一:准备阶段(Prepare)

  1. 协调者向所有参与者发送 Prepare 请求
  2. 参与者执行事务操作,写入 Undo/Redo 日志,但不提交
  3. 参与者返回 Yes(准备就绪)或 No(无法提交)
sequenceDiagram participant C as 协调者 participant P1 as 参与者1 participant P2 as 参与者2 participant P3 as 参与者3 C->>P1: Prepare C->>P2: Prepare C->>P3: Prepare P1-->>C: Yes P2-->>C: Yes P3-->>C: Yes

阶段二:提交阶段(Commit)

  • 如果所有参与者返回 Yes:协调者发送 Commit
  • 如果任一参与者返回 No 或超时:协调者发送 Abort
sequenceDiagram participant C as 协调者 participant P1 as 参与者1 participant P2 as 参与者2 participant P3 as 参与者3 C->>P1: Commit C->>P2: Commit C->>P3: Commit P1-->>C: ACK P2-->>C: ACK P3-->>C: ACK

2.2 参与者状态机#

stateDiagram-v2 [*] --> Init: 事务开始 Init --> Prepared: Prepare成功 Init --> Aborted: Prepare失败 Prepared --> Committed: 收到Commit Prepared --> Aborted: 收到Abort Committed --> [*] Aborted --> [*]

2.3 故障分析#

2PC 在各种故障场景下的行为:

故障场景影响处理方式
参与者 Prepare 前崩溃不影响协调者超时后 Abort
参与者 Prepare 后崩溃阻塞恢复后根据日志决定
协调者 Prepare 后崩溃阻塞参与者无限等待
网络分区阻塞多数派无法通信

2.4 2PC 的核心问题:同步阻塞#

2PC 最大的问题是同步阻塞:

  • 参与者在 Prepare 后必须锁定资源,等待协调者的最终决定
  • 如果协调者崩溃,参与者将无限期阻塞
  • 这种阻塞可能导致整个系统资源耗尽
时间线:
t1: 参与者 Prepare 成功,锁定资源
t2: 协调者崩溃
t3: 参与者等待...资源被锁定...其他事务被阻塞...
t4: 协调者恢复,发送 Commit/Abort
Info

2PC 的阻塞问题本质上是单点故障问题。协调者是单点,其崩溃会导致所有参与者阻塞。即使协调者恢复,如果它丢失了 Prepare 阶段的决策信息,也无法做出正确的提交决定。

三、三阶段提交(3PC)#

3.1 协议改进#

3PC 将 2PC 的提交阶段拆分为两个阶段,增加超时机制:

阶段一:CanCommit

协调者询问参与者是否”可能”提交,参与者只做可行性检查,不锁定资源。

阶段二:PreCommit

协调者发送 PreCommit,参与者执行事务操作并锁定资源,但不提交。

阶段三:DoCommit

协调者发送 DoCommit,参与者正式提交。

sequenceDiagram participant C as 协调者 participant P1 as 参与者1 participant P2 as 参与者2 C->>P1: CanCommit C->>P2: CanCommit P1-->>C: Yes P2-->>C: Yes C->>P1: PreCommit C->>P2: PreCommit P1-->>C: ACK P2-->>C: ACK C->>P1: DoCommit C->>P2: DoCommit P1-->>C: ACK P2-->>C: ACK

3.2 超时机制#

3PC 的关键改进是参与者的超时机制:

  • 如果参与者在 PreCommit 状态等待 DoCommit 超时,自动提交
  • 因为到达 PreCommit 说明所有参与者都同意了 CanCommit,提交是安全的
参与者超时逻辑:
- 等待 PreCommit 超时 → Abort(可能其他参与者拒绝了 CanCommit)
- 等待 DoCommit 超时 → Commit(所有参与者已同意,提交是安全的)

3.3 3PC 的局限#

3PC 解决了协调者崩溃后的阻塞问题,但仍有局限:

  • 网络分区时仍可能出现数据不一致
  • 如果参与者在 PreCommit 后崩溃,恢复后可能与其他参与者决定不同
  • 额外的通信轮次增加了延迟
  • 工程实现复杂度高,实际使用较少
Info

3PC 假设网络延迟有界(同步网络模型),但在实际异步网络中,超时机制无法完全避免不一致。因此 3PC 更多是理论上的改进,工程实践中很少直接使用。

四、2PC 的工程实现:XA 协议#

XA 协议是 2PC 的工业标准实现,由 X/Open 组织定义。

4.1 核心组件#

组件角色对应 2PC
AP(Application Program)应用程序发起事务
TM(Transaction Manager)事务管理器协调者
RM(Resource Manager)资源管理器参与者
CRM(Communication Resource Manager)通信资源管理器节点间通信

4.2 MySQL XA 事务#

-- 开始 XA 事务
XA START 'txn1';
UPDATE account SET balance = balance - 100 WHERE id = 1;
XA END 'txn1';
-- 第一阶段:Prepare
XA PREPARE 'txn1';
-- 第二阶段:Commit
XA COMMIT 'txn1';
-- 或回滚
-- XA ROLLBACK 'txn1';

4.3 XA 的问题#

问题描述影响
同步阻塞Prepare 后资源锁定性能下降
单点故障TM 崩溃导致阻塞可用性降低
日志开销Undo/Redo 日志写入延迟增加
跨数据库兼容不同数据库 XA 实现差异运维复杂

五、2PC/3PC 与其他方案的对比#

维度2PC3PCTCCSaga
一致性强一致强一致最终一致最终一致
阻塞减轻
性能较低
实现复杂度
业务侵入
适用场景短事务理论研究电商交易长流程

六、实践建议#

  1. 优先使用数据库本地事务:如果可以避免分布式事务,就不要引入
  2. 2PC 适用于短事务:资源锁定时间短,阻塞影响可控
  3. 长事务考虑 TCC/Saga:避免长时间锁定资源
  4. XA 协议注意超时配置:合理的超时可以减少阻塞影响
  5. 监控悬挂事务:Prepare 后未 Commit/Abort 的事务需要定期清理
-- MySQL 查看悬挂的 XA 事务
XA RECOVER;

2PC 是分布式事务的协议基础,理解其原理和局限是选择更优方案的前提。3PC 虽然在理论上改进了阻塞问题,但在异步网络模型下仍不完美,工程实践中更多采用 TCC 和 Saga 等最终一致性方案。

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

2PC 与 3PC:分布式事务的协议基础
https://blog.souloss.com/posts/distributed/distributed-2pc-3pc/
作者
Souloss
发布于
2025-02-01
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时