24.3 3PC
一句话
3PC 在 2PC 前加了一个
canCommit阶段,想让参与者在 “协调者失联” 时能安全地自行决定;但只要网络会分区,“超时” 就无法区分 “对方挂了” 与 “对方还在但网不通”——所以 3PC 仍不保证一致,实践中几乎不用。
| 把 3PC 当“2PC 升级版”直接替换 | 多一个 RTT,且分区下仍不安全 |
| 不给 canCommit 设超时 | 协调者等不到回复,长时间挂起 |
三个阶段
| 阶段 | 协调者 | 参与者 | 与 2PC 的差别 |
|---|---|---|---|
| ① canCommit | 问 “你们能不能做” | 只判断可行性,不持锁 | 新增,用来提前排除不可能 |
| ② preCommit | 发 preCommit | 执行到可提交、持锁、写日志 | 相当于 2PC 的 prepare |
| ③ doCommit | 发 doCommit | 提交 | 同 2PC 的 commit |
它想解决什么
2PC 的痛点是 “协调者挂 → 参与者持锁等死”。3PC 的思路是:参与者在 preCommit 后若超时,可以自行提交(因为它知道 “能进 preCommit 说明大家都同意过”)。
| 场景 | 2PC | 3PC |
|---|---|---|
| 协调者在 prepare 后挂 | 参与者阻塞 | 超时后自行提交 |
| 协调者在 canCommit 后挂 | — | 参与者超时后自行回滚 |
| 网络分区 | 阻塞(安全) | 可能一部分提交、一部分回滚 |
为什么仍不安全
经典反例:网络分区把 “已 preCommit 的参与者” 与 “只到 canCommit 的参与者” 分开,前者超时后提交,后者超时后回滚,结果不一致。根因是:3PC 假设 “超时 = 对方故障”,而分区下这个假设不成立。
| 假设 | 现实 |
|---|---|
| 超时可以判定故障 | 分区、GC 停顿、网络抖动都像 “故障” |
| 参与者能自行决定 | 缺少全局信息,只能靠猜测 |
工程结论
| 方案 | 是否阻塞 | 是否一致(分区下) | 实践 |
|---|---|---|---|
| 2PC | 会 | 一致(牺牲可用性) | 数据库 XA、少数强一致场景 |
| 3PC | 减少阻塞 | 不一致 | 少见 |
| 共识复制协调者(Raft/Paxos) | 多数派可用即不阻塞 | 一致 | 现代主流(如 Spanner、TiDB、etcd) |
结论:用共识算法复制 “协调者状态”,既解决单点,也避免 3PC 的一致性漏洞。
代码:三段状态机(示意,生产不建议直接用)
协调者状态: WAIT → PRECOMMIT → COMMIT / ABORT
参与者状态: INIT → CANCOMMIT → PRECOMMIT → COMMITTED / ABORTED
参与者超时规则的差别(也是 3PC 的全部赌注):
CANCOMMIT 阶段超时 → 安全地 ABORT (还没人承诺过)
PRECOMMIT 阶段超时 → 默认 COMMIT (大家都同意过 → 赌“协调者会提交”)
问题就在最后一行:分区时“大家都同意过”可能只是本分区的共识,
对面的分区可能根本没收到 preCommit。于是两边分别 commit / abort。
反模式
| 反模式 | 后果 |
|---|---|
| 以为 3PC 比 2PC “更正确” | 分区下不一致,比阻塞更糟 |
| 用超时做 “故障判定” | 分区/停顿被误判,误提交 |
| 在跨地域用 3PC | 三阶段 RTT 叠加,延迟高且仍不安全 |
三条铁律
🔑 超时不能区分 “故障” 与 “分区”;任何基于超时的 “自行决定” 都有风险。
🔑 要 “不阻塞且一致”,只能靠多数派共识。
🔑 3PC 是历史方案;工程上直接用 2PC(短事务)或 Saga(长事务)。
自检
Q1 3PC 的 canCommit 阶段为什么能 “减少阻塞”?
▸ 答案:因为它把 “可行性判断” 提前:如果有人在 canCommit 阶段就回 no,协调者直接 abort,不必进入持锁的 preCommit。它减少的是无效的持锁,而不是分区下的不一致问题。
Q2 3PC 在什么情况下会导致 “部分提交、部分回滚”?
▸ 答案:网络分区导致两组参与者对 “协调者是否还活着” 判断不同:已 preCommit 的一组超时后自行提交,只到 canCommit 的一组超时后自行回滚。没有多数派作为仲裁,两边都 “合理”,结果不一致。
Q3 为什么现代系统用共识算法而不是 3PC?
▸ 答案:因为共识(Raft/Paxos)能给出唯一权威的决策记录:只要多数派可用,协调者状态就能恢复,参与者不必靠超时猜。代价是需要多数派可用(少数派分区期间不可用),但这正是 “一致性优先” 的正确取舍。
Q4 2PC 与 3PC 的消息量差多少?
▸ 答案:2PC 两轮(prepare + commit),3PC 三轮(canCommit + preCommit + doCommit):多一个 RTT 与一轮日志。跨机房时 3PC 的额外延迟往往超过它 “减少阻塞” 带来的收益。
Q5 如果必须用 2PC,怎么降低阻塞风险?
▸ 答案:① 协调者高可用(共识复制状态);② 事务尽量短(减少持锁时间);③ 参与者侧设超时与人工介入流程;④ 用 presumed abort 减少不确定窗口;⑤ 监控 “长时间 prepare” 的事务并告警。
Q7 如果给 3PC 加一条 “必须问多数派” 的规则,它还是 3PC 吗?
▸ 答案:不是了——那已经变成 共识算法(多数派投票 + 状态机复制)。这也是工程界的结论:与其给 3PC 打补丁,不如直接用 Raft/Paxos 复制协调者状态,既解决单点又不丢一致性。