主题
界面
主题
CAP 是背得最熟、用得最糊的理论。本节不谈定义,直接做一次「网络分区」实验:
★ 三副本库存(10 件),分区期间两侧各卖 1 件,三种取舍的一手实测 ✓✓:
CP :A 侧拒写 ✗(可用性下降)→ 恢复后 A=9 B=9 C=9,**无冲突** ✓✓
AP :两侧都写成功 ✓ → 恢复后 A=9、B=9 —— **实际卖出 2 件、库存只扣 1 件:超卖** ✗✗✗
BASE:两侧写 + 记操作日志 → 恢复后合并(-1 与 -1)→ A=8 B=8 C=8 **收敛** ✓✓★ 只要数据存在两个地方,就有「同不同步」的问题 ✗✓:微服务拆开、缓存与库、多机房多活 —— 而网络一定会断(交换机重启、光纤被挖、云厂商抖动)✓✓:
| 场景 | 问题的样子 |
|---|---|
| 秒杀库存拆分 | 两侧都卖 → 超卖 ✗✗✗ |
| 缓存与数据库 | 更新不同步 → 读到旧值 ⚠️ |
| 多活机房 | 双写冲突 → 数据互相覆盖 ✗ |
| 字母 | 含义 | 现实 |
|---|---|---|
| C Consistency | 所有节点读到的数据一致 | 分区时可以放弃 |
| A Availability | 每个请求都能得到响应(不报错) | 分区时可以放弃 |
| P Partition tolerance | 网络分区时系统仍能运行 | 必须选(网络一定会断)✓✓✓ |
★★ 「三选二」是错的 ✓✓✓:P 不是选项,是前提 —— 真正要选的是:分区发生时,牺牲一致性(AP)还是牺牲可用性(CP) ✓✓。
★ CP:少数派拒写 ✓:
网络分区:A | B+C
A 侧客户端「减 1」:多数派需要 2/3 确认,A 只有自己 1 票 → **拒绝写入** ✗(可用性下降)
B 侧客户端「减 1」:B+C 两个确认 → 提交成功 ✓(同步复制给 C)
分区期间:A=10 B=9 C=9 ← A 还是旧值,但**没有产生冲突**
分区恢复:A 从多数派同步 → A=9 B=9 C=9 ✓✓ 三方一致、账实相符★ AP:两侧都写,恢复后超卖 ✗✗✗:
A 侧客户端「减 1」:本地直接成功 ✓(不等其他人)
B 侧客户端「减 1」:本地直接成功 ✓
分区恢复:A=9(10-1)、B=9(10-1),两个减 1 互相不知道
→ **实际卖出 2 件、库存只扣了 1 件:超卖 1 件** ✗✗✗★★★ 一句话选型 ✓✓✓:
| 字段性质 | 选择 | 例子 |
|---|---|---|
| 不能超卖 / 不能双花 ✗✗✗ | CP(宁可拒绝写) | 库存、余额、优惠券、分布式锁 |
| 可以容忍冲突 / 后到覆盖 ⚠️ | AP(宁可暂时不一致) | 购物车、点赞、非关键缓存 |
| 要最终一致 ✓ | BASE(写日志后收敛) | 订单状态、多活、计数器 |
★ BASE 实测(操作日志收敛) ✓✓:
分区期间:A 侧记 [减1]、B 侧记 [减1](不直接改值)
分区期间读取:A 侧读到 9、B 侧读到 9 ← **各读各的视角**(不一致窗口)⚠️
分区恢复:合并操作日志(-1 与 -1,共 -2)→ A=8 B=8 C=8 ✓✓ 收敛且无超卖| BASE 字母 | 含义 | 本节的体现 |
|---|---|---|
| BA Basically Available | 基本可用(允许降级) | 分区期间两侧都能写 ✓ |
| S Soft state | 中间态允许存在 | 收敛前读到 9 / 9 ⚠️ |
| E Eventually consistent | 最终一致 | 合并日志后三方相等 ✓✓ |
★★ 代价与前提 ✓✓:收敛前必须有「冲突合并规则」(操作日志 / 版本号 / LWW), 并且要有「对账」兜底(《分布式事务》 的最后一节)✓✓。
| 组件 / 用法 | 倾向 | 说明 |
|---|---|---|
| ZooKeeper / etcd | CP ✓ | 选主与配置:宁可短暂不可用,不可分脑 |
| Redis 主从(默认异步) | AP ⚠️ | 主挂了可能丢最后几条写(要 WAIT 换取更强一致) |
| Redis 集群 + Redlock | 偏 CP(有争议)⚠️ | 分布式锁场景要知道它的边界 |
| MySQL 主从半同步 | 之间 ✓ | semi-sync 是折中:至少一个从库确认 |
Kafka acks=all + ISR | 偏 CP ✓ | min.insync.replicas 决定能忍几个副本掉队 |
| 缓存与库的最终一致 | BASE ✓ | 先更库再删缓存 + 延迟双删 + 对账(《缓存穿透、击穿与雪崩》) |
CP :宁可拒绝服务,不可数据打架 → 少数派拒写
AP :宁可持续服务,容忍数据冲突 → 都写,冲突交给业务/合并
BASE:都写,但记「意图」而不是「结果」→ 恢复后按意图收敛问 1:这个字段错了会怎样?(超卖赔钱 → CP;点赞数不准 → AP)
问 2:能接受多长时间的「不一致窗口」?(秒级 → BASE 够;必须实时 → CP)
问 3:谁来修最后的不一致?(有对账 → BASE 敢用;没有 → 老老实实 CP)「我们的系统选了 CA」✗✗ ← 分布式系统里没有 CA:
一旦发生分区,CA 系统就是「既不一致、又不可用」(两边都写或不写都错)✓✓代码:labs/java-deep/src/main/java/cn/kb/learn/deep/distributed/ConsistencyModelLesson.java
实测输出:
===== 1. CP(一致性优先):分区期间少数派拒写 =====
网络分区:A | B+C(A 侧被隔离)
A 侧客户端「减 1」:多数派需要 2/3 确认,A 只有自己 1 票 → **拒绝写入** ✗(可用性下降)
B 侧客户端「减 1」:B+C 两个确认 → 提交成功 ✓(同步复制给 C)
分区期间:A=10 B=9 C=9 ← A 还是 10(旧值,但**没有产生冲突**)
分区恢复:A 从多数派同步 → A=9 B=9 C=9 ✓✓ 三方一致、账实相符
===== 2. AP(可用性优先):两侧都写 → 恢复后超卖 ✗✗ =====
分区期间:A=9 B=9 C=9 ← 两侧都自认为「卖出了 1 件」
→ **实际卖出 2 件、库存只扣了 1 件:超卖 1 件** ✗✗✗
===== 3. BASE(基本可用 + 最终一致):操作日志收敛 =====
分区恢复:合并两侧操作日志(-1 与 -1,共 -2)→ A=8 B=8 C=8 ✓✓ 收敛且无超卖★ 读法 ✓✓:① CP 输掉的是 A(拒绝写);② AP 输掉的是 C(超卖); ③ BASE 两头都不输硬,靠「窗口 + 合并规则 + 对账」把一致性推迟到未来 ✓。
| # | 错误 | 说明 |
|---|---|---|
| 1 | 背「CAP 三选二」 ✗✗✗ | P 必选;实际是 C vs A 二选一 ✓✓ |
| 2 | 说「我们选了 CA」 ✗✗✗ | 分布式里 CA 不存在(分区一来两头都不保)✓ |
| 3 | 库存用 AP 双写 ✗✗✗ | 一定超卖;秒杀必须预扣 + 串行化 ✓✓ |
| 4 | 以为 BASE 等于「不保证一致」 ✗✗ | 最终一致 + 冲突规则 + 对账,缺一不可 ✓✓ |
| 5 | 忽略「不一致窗口」的用户体验 ⚠️ | 窗口内用户可能看到旧值(要设计成「处理中」)✓ |
ConsistencyModelLesson 三段实验 ✓✓✓;验收方式:给定一个跨服务操作,能说出:这个字段该 CP 还是 AP、为什么、代价是什么、谁来兜底。
扩展任务:写一份《项目 A/B 的字段一致性清单》:字段 → 选择 → 理由 → 兜底手段 ✓✓
《分布式事务》 分布式事务:把「最终一致」落到幂等、补偿、对账的工程实现。 本节与 《Seata 分布式事务》(Seata 方案对比)配合:先选方案,再补机制。
无,可直接开始