15.2 CAP、读写一致性与降级边界
CAP 经常被画成三角形,让人误以为数据库平时可以任选两项。更准确的读法是:当系统发生网络分区,跨分区消息无法按要求到达时,某次操作不能同时保证线性一致性和所有非故障节点都及时返回成功。
三个字母具体指什么
- Consistency:这里通常指线性一致性——每次操作看起来在调用与返回之间某个瞬间原子发生,所有客户端服从同一个实时顺序;不是 ACID 的 C,也不是“副本最终会相同”。
- Availability:每个发给非故障节点的请求最终都得到非错误响应,但响应不保证包含最新写入。
- Partition tolerance:系统在节点间消息任意丢失或延迟时仍按规格运行。
只要系统跨网络部署,就不能靠“选择 CA”让分区消失。实际要设计的是:分区发生时,哪些操作坚持一致而拒绝服务,哪些操作允许返回旧值或接受稍后合并的写入。
CP 与 AP 是操作策略,不是永久标签
假设库存有两个副本,网络把它们隔开。
坚持线性库存扣减的一侧必须确认自己仍有合法多数派或租约;不能确认的副本拒绝写入。这牺牲分区期间部分可用性,避免两边都卖出最后一件商品。
若“点赞”允许两边继续接收,分区恢复后再用可合并计数或事件集合汇总,就保留更多可用性,但客户端在分区期间可能看不到另一边的最新结果。
同一个系统可以对登录权限采用 CP 策略,对浏览计数采用 AP 策略。产品名称不能代替每个 API 的语义说明。
一致性不只有强与最终
应用常见的会话或顺序保证包括:
- read-your-writes:用户能读到自己刚写入的值;
- monotonic reads:同一会话不会先看到新版本又退回旧版本;
- monotonic writes:同一会话的写按顺序生效;
- causal consistency:因果相关操作的观察顺序一致;
- bounded staleness:读取最多落后指定时间或版本数。
这些保证可能比全局线性一致性便宜,又比无约束最终一致性更符合产品体验。接口应明确适用范围:单键、单分区、同一 region,还是全局。
Quorum 交集只是基础条件
在 N 个副本中,若写等待 W 个响应、读查询 R 个副本,R + W > N 能让读写集合至少相交。但仅有交集并不能自动证明线性一致性,还需要版本选择、并发写冲突处理、失败节点恢复、读修复或共识等完整协议。
Cassandra 的 Consistency Level 控制协调者等待多少副本响应;写仍会被发往目标副本。QUORUM、LOCAL_QUORUM、ONE 的延迟和故障域不同,跨数据中心时尤其不能只看数字。
延迟与分区之外的 PACELC
即使网络没有完全分区,系统也一直面对延迟和一致性的取舍:同步等待更多副本通常提高确认强度,却增加尾延迟和对慢副本的敏感度。PACELC 用“分区时 A/C,平时 latency/consistency”提醒我们,CAP 只描述故障边界,不是完整选型框架。
冲突解决必须符合业务代数
“Last Write Wins”简单,但把物理或客户端时间戳当最终裁判可能让时钟偏差和迟到写入覆盖正确数据。更可靠的策略取决于数据能否合并:
- 集合添加可用去重事件或集合 CRDT;
- 计数可按副本分量合并;
- 用户资料的不同字段可做字段级合并;
- 余额、唯一用户名等不可任意合并的不变量,通常需要单一串行化点或共识事务。
不要用一个通用 JSON merge 处理所有冲突。合并函数至少应尽可能满足交换律、结合律和幂等性,才能承受乱序与重复交付。
把语义写进 API 契约
每个关键读写接口都应回答:
- 成功响应意味着写入了多少副本,是否已经持久化?
- 后续从哪里读取才能 read-your-writes?
- 网络分区或共识无多数派时,是失败、超时还是接受待合并写?
- 重试可能产生重复吗,幂等键保留多久?
- 冲突由谁发现、如何合并、是否会通知用户?
这些答案比“我们的数据库是 AP”更能指导客户端实现和故障演练。
一个按业务分类的例子
| 数据 | 分区时策略 | 原因 |
|---|---|---|
| 支付账本 | 无法确认法定写入权时拒绝 | 不能双花或任意合并 |
| 商品描述 | 可接受本地旧读,写入走权威区域 | 读陈旧代价可控 |
| 点赞事件 | 本地接受、去重、异步汇合 | 操作可交换、可幂等 |
| 访问控制策略 | 默认拒绝或使用有时限的已验证快照 | 错误放行代价高 |
“降级”应在故障前设计并测试,而不是分区发生后临时决定。
本节检查点
- 能说明 CAP 只在网络分区条件下约束一致性与可用性。
- 能区分线性一致性、ACID 一致性和最终收敛。
- 不会只凭
R + W > N宣称系统已线性一致。 - 能为具体业务操作写出故障时的成功、失败与合并语义。
参考
下一章进入实现层:数据怎样分片、复制组怎样选主、跨分片事务又怎样提交。