分布式理论02:CAP / BASE

从订单、库存、缓存和读写延迟这些项目场景出发,理解 CAP / BASE 真正在提醒我们做什么取舍,以及最终一致性为什么必须有收敛机制。

字数 1829 阅读时长 ≈ 6 分钟 2026-5-30 2026-7-12
分布式理论02:CAP / BASE

CAP / BASE 解决什么问题

CAP 最容易被背成一句话:一致性、可用性、分区容错性三者不可兼得。问题是,很多人背完以后还是不知道项目里该怎么选。

更接近工程现场的说法是:当服务之间的网络不可靠、节点可能暂时无法通信时,系统要优先保证”所有人看到同一个结果”,还是优先保证”请求尽量还能被处理”。

这里的 P 不是一个可以随便放弃的选项。只要系统跨进程、跨机器、跨机房,就必须承认网络分区和通信异常会发生。真正需要取舍的是 C 和 A。

比如订单服务写入订单后,要通知库存、积分、搜索索引和消息中心。某一刻消息服务不可用,订单服务是直接失败,还是先让订单成功、后续补发消息?这不是定理题,而是业务后果题。

C 和 A 不是抽象名词

一致性不是”数据库一定立刻一样”这么窄。它要回答的是:一次操作完成后,相关读写方能不能看到符合业务承诺的结果。

可用性也不是”永远成功”。它更像是:系统在局部异常时,能不能给调用方一个可接受的响应,而不是一直卡住、超时或整体不可用。

在 Java 后端项目里,常见取舍大概是这样:

数据/链路倾向原因
支付成功状态更偏一致不能随便显示未支付
库存扣减更偏一致宁可失败也不要超卖
订单列表至少读己之写用户提交后要能看到
积分到账最终一致可以延迟,但要能补偿
搜索索引最终一致重点是最终能追上主数据
浏览计数允许短暂不准甚至允许少量丢失

所以不要问”这个系统选 CP 还是 AP”。一个系统里不同链路、不同数据,可能有不同取舍。

BASE:怎么降级承诺

BASE 经常被理解成”反正最终一致就行”。这很危险。

BASE 的核心不是放弃正确性,而是把强一致的即时承诺降级成可恢复、可收敛的承诺:

特性含义
基本可用(Basically Available)核心流程还能走,非核心能力可以降级
软状态(Soft State)中间状态允许短暂不一致
最终一致(Eventually Consistent)系统要通过重试、补偿、对账等机制回到正确结果

比如下单成功后积分没有立即到账,可以接受。但必须有消息重试、失败表、补偿任务和告警。用户第二天还没拿到积分,就不是”最终一致”,而是丢数据。

最终一致不是”不用管”,而是”当下不强行同步,但事后必须收敛”。

一个项目里的判断方式

假设现在要设计”订单支付成功后更新会员等级”。支付是核心链路,会员等级是权益链路。

一种强同步做法是:支付回调里直接调用会员服务,会员更新失败就让支付回调失败。这会让会员服务的不稳定影响支付链路,风险很大。

更稳的做法通常是:

  1. 支付服务先确认支付状态
  2. 写入本地事件或发送可靠消息
  3. 会员服务消费事件更新等级
  4. 消费失败进入重试或补偿表
  5. 对账任务定期扫描支付成功但权益未更新的数据

这就是一个偏 BASE 的设计。它没有否认一致性,只是把”支付和会员等级同时完成”的承诺,改成了”支付先正确完成,会员等级在可观测、可补偿的机制里追上”。

读路径的一致性也要设计

很多团队讨论 CAP 时只盯写入,其实线上更常见的问题是读路径不一致。

比如用户提交订单后马上进入订单列表页。如果订单写在主库,列表查的是从库,主从复制延迟 500ms,用户就可能看到”订单不存在”。从数据库角度看,这是正常复制延迟;从用户体验看,这是刚提交就丢了。

这里有几种处理方式:

方案做法适用场景
强制读主提交后的短窗口内,订单列表读主库核心数据、量不大时
读己之写给用户会话打标,自己的新数据优先读主用户体验敏感场景
结果页兜底提交成功页直接展示本次订单快照提交后马上展示结果
异步提示列表延迟可接受时,明确展示”处理中”后台任务、非实时数据

这些方案没有哪个永远正确。读主会增加主库压力;读己之写要维护路由标记;展示快照要注意后续状态变化;异步提示要业务能接受。CAP / BASE 真正落地时,就是在这些具体读写路径上做取舍。

缓存也是同一个问题。数据库更新成功后,缓存删除失败怎么办?先删缓存还是先写数据库?延迟双删有没有必要?这些都不是纯粹的缓存技巧,而是在决定”短时间读到旧值的后果能不能接受”。

如果是商品详情页的浏览量,旧一点没关系;如果是用户余额,旧值就很危险。所以一致性不要按组件分,而要按数据后果分。

一致性承诺分级

项目里可以把每个数据项写进一张简单表里:

数据项写入方读取方可接受延迟兜底方式
支付状态支付服务订单/用户不接受对账 + 状态机
订单列表订单服务当前用户秒级内读己之写短期读主
积分会员服务用户中心分钟级重试 + 补偿
搜索索引搜索服务搜索页分钟级重建索引
统计报表数仓任务后台运营小时级批处理校验

这张表比”我们系统追求最终一致”更有用。它能让开发、测试、产品、运维对同一件事达成共识:哪里不能错,哪里可以慢,哪里必须能补。

如果没有这个分级,最终一致很容易变成事故后的解释话术。

常见误区

误区一:把 CAP 当成选型标签

比如说 Redis 是 AP、ZooKeeper 是 CP,然后就结束了。组件倾向只能提供背景,业务链路怎么承诺一致性,仍然要自己设计。

误区二:只看写,不看读

很多不一致问题出现在读路径:主库写完后读从库、数据库更新后读缓存、订单写完后搜索页查不到。读路径的承诺要单独设计。

误区三:把最终一致交给 MQ 自动解决

MQ 只负责传递消息,不能自动保证业务幂等、状态流转正确、补偿任务有效。