CAP 和 BASE 如何指导实际架构,而不只是面试概念?
CAP 讨论的是发生网络分区时,一个分布式读写对象无法同时保证线性一致性和可用性。这里的可用性是理论模型中的“每个到达非故障节点的请求最终得到响应”,不是常说的 99.99% uptime;返回旧值也可能满足 CAP 的 A。它不是让架构师平时在 C、A、P 中任选两个;分布式系统无法消除网络分区,真正的选择发生在具体操作和故障窗口内。
先定义一致性
CAP 中的 C 接近单副本线性一致性,不是数据库 ACID 的 C,也不是“最终数据一样”。A 也不代表低延迟或永不宕机,而是所有到达非故障节点的请求都能获得响应。模糊术语会让讨论失去意义。
CAP 也不是所有架构权衡的完整模型。没有分区时仍要在延迟与一致性之间选择,常用 PACELC 表达“有分区时 A/C,其他时候 Latency/Consistency”。实际系统还受持久性、吞吐、成本、故障检测和客户端会话保证约束,因此不要给整个数据库永久贴上“AP”或“CP”标签。
以库存扣减为例。分区时继续允许两个机房各自扣减(偏 AP)会超卖;拒绝无法确认配额的请求(偏 CP)会损失可用性。若事先给机房分配独立库存额度,则每个机房在额度内可用,额度调拨时采用强一致协调——这是把选择缩小到业务不变量,而非给整个系统贴标签。
BASE 是工程策略
BASE 不是像 CAP 那样具有单一严格形式化定义的定理,更适合作为一组工程风格:Basically Available、Soft State、Eventually Consistent。它表示允许中间态,通过异步传播最终收敛。但“最终”必须有触发条件、时间 SLO、监控和补偿;如果更新持续不断或消息永久失败,系统未必会自行收敛。
订单创建可采用本地事务写订单与 Outbox:
BEGIN;
INSERT INTO orders(id,status) VALUES(?, 'CREATED');
INSERT INTO outbox(event_id,aggregate_id,type,payload) VALUES(?,?,?,?);
COMMIT;
后台可靠发布事件,库存消费者按 event_id 幂等处理。此时订单与库存存在短暂不一致,应定义 PENDING_STOCK 状态、超时取消、重试上限和人工对账,而不是向用户谎称“已成功”。
按业务不变量选择
- 余额不能凭空增加:优先强一致账本,展示余额可异步聚合。
- 用户头像、点赞数:通常容忍短暂旧值,可读本地副本。
- 配置发布:读取要求高可用,但版本切换需要单调性和回滚。
- 唯一用户名:注册路径需要一致协调,搜索索引可以最终一致。
同一系统、同一实体的不同操作可以作不同选择。读自己的写、单调读、有界陈旧等会话保证,常比笼统的“强/最终一致”更贴近体验。
必须设计不一致窗口
明确最大收敛时间、冲突检测、合并规则、幂等键、重放机制、死信处理和对账来源。时间戳“最后写入获胜”可能因时钟漂移丢失重要更新;余额等不可交换操作更适合追加不可变流水,再由确定性规则计算状态。
生产检查清单
- 每个核心操作在分区时选择拒绝还是接受风险?
- 无分区时是否也评估了一致性带来的延迟与成本?
- 业务不变量是否以约束、配额或账本形式落地?
- 最终一致是否有明确的时间 SLO?
- 是否能观测积压、最老事件年龄与对账差异?
- 补偿操作是否幂等、可重放并保留审计记录?
- 用户是否能看懂“处理中”,而不是得到虚假成功?
CAP 的价值不在背诵,而在迫使我们回答:网络失联时,哪类错误更不可接受,以及系统如何显式承担这个选择。