两阶段提交是分布式事务的第一次认真尝试。它几乎成功了,直到协调者挂掉的那一刻。然后 CAP 定理告诉我们:你不能同时拥有一切。
前情回顾
上一篇我们看到,分布式系统面临三大困难:
- 网络不可靠:消息会丢、会重复、会乱序
- 时钟不一致:不能用时间戳判断先后
- 节点会挂:部分失效是常态
今天我们来看人类的第一次尝试:两阶段提交(2PC)。
从一个婚礼说起
想象一场婚礼。牧师问:
牧师:「张三,你愿意娶李四吗?」张三:「我愿意。」
牧师:「李四,你愿意嫁给张三吗?」李四:「我愿意。」
牧师:「我宣布你们结为夫妻!」
这就是两阶段提交的核心思想:
- 第一阶段(准备):问所有参与者「你准备好了吗?」
- 第二阶段(提交):如果都说准备好了,就正式提交
2PC 的工作流程
假设我们要完成一笔跨行转账:北京银行扣 100 元,上海银行加 100 元。
正常流程:
协调者 北京银行 上海银行
│ │ │
│ [阶段1: 准备] │ │
│ │ │
│─── 准备扣100元 ───►│ │
│─── 准备加100元 ─────────────────────►│
│ │ │
│◄── 准备好了 ────────│ │
│◄── 准备好了 ───────────────────────│
│ │ │
│ [阶段2: 提交] │ │
│ │ │
│─── 提交! ─────────►│ │
│─── 提交! ──────────────────────────►│
│ │ │
│◄── 完成 ────────────│ │
│◄── 完成 ─────────────────────────────│
如果有人没准备好:
协调者 北京银行 上海银行
│ │ │
│─── 准备扣100元 ───►│ │
│─── 准备加100元 ─────────────────────►│
│ │ │
│◄── 准备好了 ────────│ │
│◄── 余额不足! ──────────────────────│
│ │ │
│ [收到一个拒绝,全部回滚] │
│ │ │
│─── 回滚! ─────────►│ │
│─── 回滚! ──────────────────────────►│
看起来很完美?问题来了。
2PC 的致命缺陷
缺陷一:协调者单点故障
如果协调者在第二阶段开始前挂了:
协调者 北京银行 上海银行
│ │ │
│◄── 准备好了 ────────│ │
│◄── 准备好了 ───────────────────────│
│ │ │
X (协调者挂了) │ │
│ │
我该怎么办? 我该怎么办?
提交?回滚? 提交?回滚?
参与者陷入困境:
- 不能提交:万一另一个参与者要回滚呢?
- 不能回滚:万一另一个参与者已经提交了呢?
- 只能等待:资源被锁住,一直等协调者恢复
这就是 2PC 的「阻塞问题」。
缺陷二:资源长时间锁定
在等待期间,参与者必须锁住相关资源:
北京银行
│
│ 张三账户:已锁定,等待最终决定
│
│ 这时候张三想查余额?抱歉,等着
│ 这时候有人要给张三转账?抱歉,等着
│
▼ (一直等到协调者恢复或超时)
在高并发场景下,这种阻塞是致命的。
缺陷三:网络分区时的困境
如果网络分区发生在第二阶段:
协调者 北京银行 上海银行
│ │ │
│─── 提交! ─────────►│ ✓ 收到 │
│─── 提交! ──────────────X (网络断了) │
│ │ │
│ 北京提交了 上海没收到
│ │ │
│ 数据不一致!
北京执行了提交,上海没收到消息。数据暂时不一致,需要等协调者重试或网络恢复。如果协调者也崩溃且日志丢失,则永久不一致。
3PC:尝试解决阻塞
为了解决 2PC 的阻塞问题,有人提出了三阶段提交(3PC):
| 阶段 | 2PC | 3PC |
|---|---|---|
| 1 | 准备 | CanCommit(询问能否提交) |
| 2 | 提交 | PreCommit(预提交) |
| 3 | - | DoCommit(正式提交) |
3PC 的改进:
- 引入超时机制,参与者不再无限等待
- 多一个阶段,减少不确定状态的窗口
但 3PC 仍然无法解决网络分区问题。 当网络断开时,两边可能做出不同的决定。
CAP 定理:不可能三角
2000 年,加州大学伯克利分校的 Eric Brewer 提出了一个猜想,2002 年被正式证明为定理:
在一个分布式系统中,当网络分区发生时,你只能在一致性和可用性之间二选一。
三个字母的含义
| 字母 | 英文 | 含义 | 大白话 |
|---|---|---|---|
| C | Consistency | 一致性 | 所有节点看到的数据一样 |
| A | Availability | 可用性 | 每个请求都能得到响应 |
| P | Partition Tolerance | 分区容忍 | 网络断了系统还能运行 |
注意:CAP 的 C 和 ACID 的 C 是完全不同的概念:
| 缩写 | 含义 | 关注点 |
|---|---|---|
| CAP 的 C | 线性一致性(Linearizability) | 分布式节点间的数据同步 |
| ACID 的 C | 一致性约束(Consistency) | 单节点上的数据完整性(如外键约束) |
CAP 的 C 是说「所有节点同时看到相同的最新数据」,ACID 的 C 是说「事务执行后数据库仍满足约束条件」。不要混淆。
为什么三选二?
关键洞察:P 不是可选的。
在分布式系统中,网络分区一定会发生。这不是「如果」,而是「什么时候」。
所以真正的选择是:当网络分区发生时,你选 C 还是 A?
一个具体的例子
假设你有两个数据中心,北京和上海,各存一份用户余额:
北京数据中心 上海数据中心
余额 = 100 余额 = 100
现在网络断了。北京用户要充值 50 元。
选择 1:保 C(一致性),牺牲 A(可用性)
北京数据中心
│
│ 用户:我要充值 50 元
│
│ 系统:抱歉,我联系不上上海
│ 为了保证数据一致
│ 这笔操作暂时无法完成
│ 请稍后再试
│
▼ 用户请求被拒绝
选择 2:保 A(可用性),牺牲 C(一致性)
北京数据中心 上海数据中心
│ │
│ 用户:充值50元 │
│ │
│ 系统:好的! │
│ 余额 = 150 │ 余额 = 100 (不知道北京发生了什么)
│ │
▼ ▼
两边数据不一致
你必须选一边。这就是 CAP。
CP vs AP:两种架构风格
CP 系统:宁可不可用,也要一致
典型代表:
- ZooKeeper:分布式协调服务
- etcd:Kubernetes 的配置存储
- HBase:强一致的分布式数据库
适用场景:
- 金融交易
- 库存扣减
- 订单创建
- 任何「不能出错」的场景
代价:
- 网络分区时,部分请求会失败
- 延迟较高(需要等多数节点确认)
AP 系统:宁可不一致,也要可用
典型代表:
- Cassandra:高可用分布式数据库
- DynamoDB:AWS 的 NoSQL 服务
- DNS:域名解析系统
适用场景:
- 社交媒体动态
- 用户浏览历史
- 购物车(临时数据)
- 任何「可以容忍短暂不一致」的场景
代价:
- 可能读到旧数据
- 需要处理数据冲突
一张图对比
| 维度 | CP 系统 | AP 系统 |
|---|---|---|
| 网络分区时 | 拒绝部分请求 | 继续服务 |
| 数据状态 | 强一致 | 最终一致 |
| 延迟 | 较高 | 较低 |
| 适用场景 | 金融、库存、订单 | 社交、缓存、日志 |
| 典型代表 | ZooKeeper, etcd | Cassandra, DynamoDB |
CAP 的常见误解
误解一:「三选二」意味着放弃一个
不是的。 CAP 说的是「当 P 发生时」的选择。
在网络正常时,你可以同时拥有 C 和 A。只有网络分区发生的那一刻,你才需要做选择。
误解二:系统必须全局选择 CP 或 AP
不是的。 同一个系统的不同部分可以做不同选择。
例如一个电商系统:
- 库存服务:CP(不能超卖)
- 商品评论:AP(晚几秒显示没关系)
- 用户购物车:AP(临时数据,可以容忍短暂不一致)
误解三:CAP 是唯一需要考虑的
不是的。 还有延迟、吞吐量、数据持久性等其他维度。
后来的 PACELC 定理对此做了扩展:
如果有分区(P),选 A 还是 C?
否则(E = Else),选低延迟(L)还是强一致(C)?
2PC 在 CAP 中的位置
现在我们可以理解 2PC 的问题了:
2PC 试图实现 CP:在网络分区时,它选择阻塞(牺牲可用性)来保证一致性。
但它做得不够好:
- 协调者单点故障导致「既不 C 也不 A」
- 网络分区时可能导致数据不一致
- 长时间锁定资源
2PC 是一个不完美的 CP 实现。
那怎么办?
2PC 的核心问题是:它依赖单个协调者来做决定。
如果我们不依赖单点呢?如果我们让多个节点一起投票决定呢?
这就是下一篇的主题:Paxos 和 Raft——让一群节点达成共识的算法。
常见问题
Q:既然 2PC 有这么多问题,为什么银行还在用?
A:因为场景匹配 + 补偿机制。
- 银行内部网络相对可靠,分区概率低
- 业务量相对可控,阻塞影响有限
- 配合超时回滚 + 人工对账,能处理异常情况
- 强一致性在金融场景是刚需,宁可慢也不能错
Q:CAP 是不是说分布式系统一定有缺陷?
A:是的,但这是物理定律决定的。
光速有限,网络延迟不可避免。当两个节点之间的通信需要时间,你就必须在「等待确认」和「先响应」之间做选择。
这不是算法的问题,是物理世界的限制。承认这个限制,才能设计出正确的系统。
Q:有没有办法绕过 CAP?
A:不能绕过,但可以优化权衡。
- 减少分区影响范围:多机房部署、网络冗余
- 缩短不一致窗口:更快的同步机制
- 根据业务选择:核心数据 CP,非核心数据 AP
- 混合策略:平时 AP,关键操作 CP
总结
2PC 的贡献与局限:
| 贡献 | 局限 |
|---|---|
| 提出了分布式事务的基本框架 | 协调者单点故障 |
| 「准备-提交」两阶段思想 | 网络分区时可能不一致 |
| 成为后续算法的基础 | 资源长时间锁定 |
CAP 定理告诉我们:
在分布式系统中,当网络分区发生时,你必须在一致性和可用性之间做选择。
没有银弹,只有权衡。
这不是悲观的结论,而是设计分布式系统的基础认知。知道了边界在哪里,才能在边界内做出最优选择。
2PC 的核心问题是依赖单个协调者。如果协调者挂了,系统就瘫痪了。
有没有办法让一群节点自己选出一个领导者?有没有办法在领导者挂了之后自动选出新的?
下一篇,我们来看 Paxos 和 Raft——两个改变分布式系统历史的共识算法。
上一篇:单机到分布式:一致性为何变难
本系列: