2222
2323### 简介
2424
25- ** CAP** 也就是 ** Consistency(一致性)** 、 ** Availability(可用性)** 、 ** Partition Tolerance(分区容错性) ** 这三个单词首字母组合 。
25+ CAP 定理讨论 Consistency(一致性)、 Availability(可用性)和 Partition Tolerance(分区容错) 。
2626
2727![ ] ( https://oss.javaguide.cn/2020-11/cap.png )
2828
@@ -32,9 +32,9 @@ CAP 理论的提出者布鲁尔在提出 CAP 猜想的时候,并没有详细
3232
3333在理论计算机科学中,CAP 定理(CAP theorem)指出对于一个分布式系统来说,当设计读写操作时,只能同时满足以下三点中的两个:
3434
35- - ** 一致性(Consistency)** : 所有节点访问同一份最新的数据副本
36- - ** 可用性(Availability)** : 非故障的节点在合理的时间内返回合理的响应(不是错误或者超时的响应)。
37- - ** 分区容错性(Partition Tolerance)** : 分布式系统出现网络分区的时候,仍然能够对外提供服务 。
35+ - ** 一致性(Consistency)** :在 Gilbert/Lynch(2002)的证明语境里,CAP 的一致性 C 指的是 ** Atomic Consistency ** ,通常等同于 ** Linearizability(线性一致性) ** 。即所有操作按实时顺序线性化,即写操作一旦完成,后续所有读操作都必须返回该写入的值(或更新的值)。 ** 注意: ** 这里的 Consistency 与数据库 ACID 中的 Consistency(一致性约束)含义不同,后者指事务执行前后数据库状态满足完整性约束。
36+ - ** 可用性(Availability)** : 非故障的节点在合理的时间内返回合理的响应(不是错误或者超时的响应)。
37+ - ** 分区容错性(Partition Tolerance)** :在网络分区发生时,系统需选择牺牲 C 或 A 以维持 P。但 P 并非绝对前提——在 PACELC 框架(Abadi 2012)下,无分区时需权衡 C 与延迟(Latency),有分区时权衡 A 与 C 。
3838
3939** 什么是网络分区?**
4040
@@ -46,9 +46,18 @@ CAP 理论的提出者布鲁尔在提出 CAP 猜想的时候,并没有详细
4646
4747大部分人解释这一定律时,常常简单的表述为:“一致性、可用性、分区容忍性三者你只能同时达到其中两个,不可能同时达到”。实际上这是一个非常具有误导性质的说法,而且在 CAP 理论诞生 12 年之后,CAP 之父也在 2012 年重写了之前的论文。
4848
49- > ** 当发生网络分区的时候,如果我们要继续服务,那么强一致性和可用性只能 2 选 1。也就是说当网络分区之后 P 是前提,决定了 P 之后才有 C 和 A 的选择。也就是说分区容错性(Partition tolerance)我们是必须要实现的。 **
49+ > ** 当发生网络分区的时候,如果我们要继续服务,那么强一致性和可用性只能 2 选 1。**
5050>
51- > 简而言之就是:CAP 理论中分区容错性 P 是一定要满足的,在此基础上,只能满足可用性 A 或者一致性 C。
51+ > 简而言之就是:CAP 理论中分区容错性 P 不是一定要满足的,但当选择满足 P 时,在此基础上只能满足可用性 A 或者一致性 C。
52+
53+ 这里需要引入 ** PACELC 理论** (CAP 的扩展)来更全面地解释:
54+
55+ Daniel J. Abadi 提出的 PACELC 理论指出:** 如果存在分区(P),必须在可用性(A)和一致性(C)之间选择;否则(E,Else),必须在延迟(L)和一致性(C)之间选择。**
56+
57+ 实际意义:即使无网络分区,分布式系统仍需在低延迟(异步复制)和强一致(同步复制)之间权衡。例如:
58+
59+ - Dynamo/Cassandra:无分区时可通过 QUORUM 读写调节一致性级别
60+ - MySQL 主从:无分区时选择异步复制(低延迟)或半同步复制(强一致)
5261
5362因此,** 分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 架构。** 比如 ZooKeeper、HBase 就是 CP 架构,Cassandra、Eureka 就是 AP 架构,Nacos 不仅支持 CP 架构也支持 AP 架构。
5463
@@ -70,25 +79,73 @@ CAP 理论的提出者布鲁尔在提出 CAP 猜想的时候,并没有详细
7079
7180常见的可以作为注册中心的组件有:ZooKeeper、Eureka、Nacos...。
7281
73- 1 . ** ZooKeeper 保证的是 CP。** 任何时刻对 ZooKeeper 的读请求都能得到一致性的结果,但是, ZooKeeper 不保证每次请求的可用性比如在 Leader 选举过程中或者半数以上的机器不可用的时候服务就是不可用的。
74- 2 . ** Eureka 保证的则是 AP。** Eureka 在设计的时候就是优先保证 A (可用性)。在 Eureka 中不存在什么 Leader 节点,每个节点都是一样的、平等的。因此 Eureka 不会像 ZooKeeper 那样出现选举过程中或者半数以上的机器不可用的时候服务就是不可用的情况。 Eureka 保证即使大部分节点挂掉也不会影响正常提供服务,只要有一个节点是可用的就行了。只不过这个节点上的数据可能并不是最新的。
75- 3 . ** Nacos 不仅支持 CP 也支持 AP。**
82+ #### ZooKeeper 3.8.x(CP 架构)
7683
77- ** 🐛 修正(参见: [ issue # 1906 ] ( https://github.com/Snailclimb/JavaGuide/issues/1906 ) )** :
84+ ZooKeeper 倾向 ** CP 架构 ** 。ZooKeeper 3.x 通过 ZAB 协议提供 ** Linearizable Writes(线性化写入 )** ,但读取行为需区分 :
7885
79- ZooKeeper 通过可线性化(Linearizable)写入、全局 FIFO 顺序访问等机制来保障数据一致性。多节点部署的情况下, ZooKeeper 集群处于 Quorum 模式。Quorum 模式下的 ZooKeeper 集群, 是一组 ZooKeeper 服务器节点组成的集合,其中大多数节点必须同意任何变更才能被视为有效。
86+ - ** Sync 读取** :强制与 Leader 同步,保证线性一致性(Linearizability)。
87+ - ** 普通读取** :默认提供 ** 顺序一致性(Sequential Consistency)** ,保证全局更新操作的顺序,客户端视图绝不会发生回退,但可能读到稍旧数据(存在读取滞后)。
8088
81- 由于 Quorum 模式下的读请求不会触发各个 ZooKeeper 节点之间的数据同步,因此在某些情况下还是可能会存在读取到旧数据的情况,导致不同的客户端视图上看到的结果不同,这可能是由于网络延迟、丢包、重传等原因造成的 。ZooKeeper 为了解决这个问题,提供了 Watcher 机制和版本号机制来帮助客户端检测数据的变化和版本号的变更,以保证数据的一致性 。
89+ > ** 重要区别 ** :顺序一致性 ≠ 最终一致性 。ZooKeeper 的普通读取保证所有客户端看到相同的 ** 更新顺序 ** (全局 zxid 顺序),只是存在读取滞后;而最终一致性不保证全局顺序,仅保证最终收敛 。
8290
83- ### 总结
91+ 在 Leader 选举期间或 Follower 节点数不足 Quorum(N/2+1)时,ZooKeeper 会拒绝服务以维持一致性,表现为不可用(牺牲 A)。
92+
93+ 在多节点部署下,集群采用 Quorum 模式:多数派节点(n/2+1)必须同意变更才有效。
94+
95+ ZooKeeper 提供 Watcher 机制(异步通知变更)和版本号机制(zxid 校验新鲜度)以缓解读取滞后问题。
96+
97+ 失败路径与状态机表现:
98+
99+ | 故障场景 | 系统状态 | 客户端表现 |
100+ | ------------------------------- | ------------------------------- | ------------------------------------------------------------- |
101+ | Quorum 失效(半数以上节点故障) | ** LOOKING** 状态,Leader 选举中 | 写入请求拒绝,读取请求可能返回旧数据或超时 |
102+ | Follower 与 Leader 分区 | Follower 进入 ** ELECTION** 状态 | 该 Follower 无法参与投票,但可响应读取(滞后数据) |
103+ | Leader 与多数派分区 | Leader 自动降级,集群重新选举 | 原Leader的写入丢失,需客户端重试(检测到 zxid 回退) |
104+ | Watcher 丢失 | 网络抖动或 GC 压力导致 | 客户端需重试(指数退避 + Jitter),监控 ` Watches ` 队列防背压 |
105+
106+ #### Eureka 2.0.x(AP 架构)
107+
108+ Eureka 2.0.x 采用 AP 架构:节点对等,通过 gossip 协议同步,无 Leader 选举。
84109
85- 在进行分布式系统设计和开发时,我们不应该仅仅局限在 CAP 问题上,还要关注系统的扩展性、可用性等等
110+ 失败路径与状态机表现:
86111
87- 在系统发生“分区”的情况下,CAP 理论只能满足 CP 或者 AP。要注意的是,这里的前提是系统发生了“分区”
112+ | 故障场景 | 系统状态 | 客户端表现 | 自我保护机制 |
113+ | ---------------------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
114+ | 网络分区(脑裂) | 分区两侧** 独立运行** ,均可读写 | 客户端可能读到旧注册信息(不一致窗口 = 心跳间隔 30s + gossip 传播延迟,10 节点拓扑中 P99 <60s) | 当续约阈值 < 85% 时触发** 自我保护** ,暂停实例剔除,避免"误杀"健康实例 |
115+ | 半数节点故障 | 剩余节点继续服务,但数据可能分叉 | 读操作正常,写入可能仅存于少数派节点 | 自我保护触发,待节点恢复后通过 gossip 自动合并 |
116+ | 节点短暂重启 | 从 Peer 批量拉取注册表(Registry Fetch) | 服务发现短暂不可用(< 1min),缓存起作用 | 正常模式,自动恢复 |
117+ | 注册风暴(大量实例同时注册) | 写队列堆积,可能导致请求丢弃 | 部分注册请求超时,需客户端重试 | 可配置限流与背压(如 Ribbon 重试策略) |
118+
119+ ** 自我保护机制详细说明** :
120+
121+ Eureka Server 在 15 分钟内统计续约阈值:
122+
123+ ```
124+ 期望续约数 = 当前注册实例数 × (2 / 心跳间隔秒数)
125+ 实际续约率 = 实际收到的续约数 / 期望续约数
126+ ```
127+
128+ 当 ` 实际续约率 < 85% ` 时:
129+
130+ 1 . 进入 ** SELF PRESERVATION** 模式
131+ 2 . 停止剔除过期实例(EvictionTask 暂停)
132+ 3 . 日志输出:` ENTER SELF PRESERVATION MODE `
133+
134+ ** 设计权衡** :宁可保留"僵尸"实例,也不误杀健康实例——因为在微服务场景下,短暂的服务降级好过大规模服务不可用。客户端通常配置重试与熔断来处理不可用实例。
135+
136+ #### 总结
137+
138+ 选择 CP 或 AP 取决于场景:ZooKeeper 适合强一致需求,如配置管理;Eureka 适合高可用注册,如微服务发现。
139+
140+ Nacos 不仅支持 CP 也支持 AP。
141+
142+ ### 总结
88143
89- 如果系统没有发生“分区”的话,节点间的网络连接通信正常的话,也就不存在 P 了。这个时候,我们就可以同时保证 C 和 A 了 。
144+ CAP 理论指导我们:在分布式系统可能出现网络分区(P)的前提下,我们必须在强一致性(C)和高可用性(A)之间做出权衡 。
90145
91- 总结:** 如果系统发生“分区”,我们要考虑选择 CP 还是 AP。如果系统没有发生“分区”的话,我们要思考如何保证 CA 。**
146+ - ** CP 架构** :牺牲可用性,保证强一致性。适用于对数据一致性要求极高的场景(如金融交易、分布式锁)。
147+ - ** AP 架构** :牺牲一致性,保证高可用性。适用于对系统可用性要求较高,能容忍短暂数据不一致的场景(如社交动态、商品搜索)。
148+ - ** PACELC** :在无分区(E)时,需在延迟(L)和一致性(C)之间权衡。
92149
93150### 推荐阅读
94151
@@ -139,26 +196,79 @@ CAP 理论这节我们也说过了:
139196
140197#### 最终一致性
141198
142- 最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
199+ 最终一致性(Eventual Consistency)强调: ** 若系统在一段时间内无新的更新操作,则所有副本最终收敛到相同值。 **
143200
144- > 分布式一致性的 3 种级别:
145- >
146- > 1 . ** 强一致性** :系统写入了什么,读出来的就是什么。
147- > 2 . ** 弱一致性** :不一定可以读取到最新写入的值,也不保证多少时间之后读取到的数据是最新的,只是会尽量保证某个时刻达到数据一致的状态。
148- > 3 . ** 最终一致性** :弱一致性的升级版,系统会保证在一定时间内达到数据一致的状态。
149- >
150- > ** 业界比较推崇是最终一致性级别,但是某些对数据一致要求十分严格的场景比如银行转账还是要保证强一致性。**
201+ "一段时间"是未界定的——可能是毫秒级(局域网同步)或分钟级(跨地域复制)。生产环境中需通过 ** Read Repair(读修复)** 、** Anti-Entropy(反熵/后台同步)** 或 ** Quorum 写入** 主动加速收敛。
202+
203+ 系统会保证在一定时间内达到数据一致的状态,而不需要实时保证系统数据的强一致性。
204+
205+ 分布式一致性的 3 种级别:
206+
207+ 1 . ** 强一致性** :系统写入了什么,读出来的就是什么。
208+ 2 . ** 弱一致性** :不一定可以读取到最新写入的值,也不保证多少时间之后读取到的数据是最新的,只是会尽量保证某个时刻达到数据一致的状态。
209+ 3 . ** 最终一致性** :弱一致性的升级版,系统会保证在一定时间内达到数据一致的状态。
210+
211+ ** 业界比较推崇是最终一致性级别,但是某些对数据一致要求十分严格的场景比如银行转账还是要保证强一致性。**
212+
213+ 那实现最终一致性的具体方式是什么呢?
214+
215+ - ** 读时修复 (Read Repair)** : 在读取数据时,检测数据的不一致,进行修复。适合读多写少场景。
216+ - ** 写时修复 (Hinted Handoff)** : 在写入数据时,如果目标节点不可用,将数据缓存下来,待节点恢复后重传。** 写时修复** 优化了写入延迟,但增加了读取时的不一致风险(数据可能还在缓存队列中未落盘到目标节点)。
217+ - ** 异步修复 (Anti-Entropy/反熵)** : 通过后台比对副本数据差异并修复。工程实现中关键挑战是** 高效检测数据差异** ——暴力逐条比对(O(n))在大规模数据集下不可行,生产系统采用** 默克尔树(Merkle Tree)** 实现低开销差异定位:
218+
219+ ![ Merkle Tree用于反熵] ( https://oss.javaguide.cn/distributed-system/merkle-tree-anti-entropy.png )
220+
221+ ** Merkle Tree 工作原理** :
222+
223+ 1 . 叶节点存储数据块的哈希值
224+ 2 . 父节点存储子节点哈希组合后的哈希
225+ 3 . 根节点哈希代表整个数据集的指纹
226+ 4 . 两节点比对时,自顶向下递归,仅对哈希不匹配的子树进行同步,复杂度降至 ** O(log n)**
151227
152- 那实现最终一致性的具体方式是什么呢? [ 《分布式协议与算法实战》 ] ( http://gk.link/a/10rZM ) 中是这样介绍 :
228+ ** 典型应用 ** :
153229
154- > - ** 读时修复 ** : 在读取数据时,检测数据的不一致,进行修复。比如 Cassandra 的 Read Repair 实现,具体来说,在向 Cassandra 系统查询数据的时候,如果检测到不同节点的副本数据不一致,系统就自动修复数据。
155- > - ** 写时修复 ** : 在写入数据,检测数据的不一致时,进行修复。比如 Cassandra 的 Hinted Handoff 实现。具体来说,Cassandra 集群的节点之间远程写数据的时候,如果写失败 就将数据缓存下来,然后定时重传,修复数据的不一致性。
156- > - ** 异步修复 ** : 这个是最常用的方式,通过定时对账检测副本数据的一致性,并修复。
230+ - ** Cassandra/DynamoDB ** :使用 Merkle Tree 进行节点间的 Anti-Entropy 修复
231+ - ** Apache BookKeeper ** :Audit 日志完整性校验
232+ - ** IPFS ** :内容寻址与数据块验证
157233
158- 比较推荐 ** 写时修复** ,这种方式对性能消耗比较低。
234+ ** 权衡** :Anti-Entropy 提供强一致性保障,但消耗带宽与 CPU,通常配置为低频(如每小时一次)与 Read Repair 配合使用。
235+
236+ ** 选择建议** :
237+
238+ - ** 写时修复** :适合写多读少,优化写入性能,但牺牲一致性窗口。
239+ - ** 读时修复** :适合读多写少,保证读取数据的准确性。
240+ - ** Anti-Entropy** :后台兜底保障,适合数据规模大但对最终一致性要求高的场景。
159241
160242### 总结
161243
162244** ACID 是数据库事务完整性的理论,CAP 是分布式系统设计理论,BASE 是 CAP 理论中 AP 方案的延伸。**
163245
246+ 如果说 CAP 是分布式系统的宪法,那么 BASE 就是适用于大规模互联网系统的修正案。它告诉我们:** 绝大多数应用场景不需要强一致性,通过牺牲强一致性来获得高可用性,并最终达到一致性(Eventual Consistency)是更划算的选择。**
247+
248+ ## 生产落地建议
249+
250+ ### 选择 CP 还是 AP 的决策框架
251+
252+ | 场景特征 | 推荐架构 | 典型系统 |
253+ | ------------------------------ | ------------- | ------------------------ |
254+ | 强一致性要求(金融转账) | CP | ZooKeeper、etcd、Consul |
255+ | 高可用优先(配置中心) | AP | Eureka、Consul(可切换) |
256+ | 可调一致性(根据业务动态选择) | CP/AP 切换 | Nacos、Cassandra |
257+ | 写多读少 | AP + 写时修复 | Cassandra、HBase |
258+ | 读多写少 | AP + 读时修复 | DynamoDB、Voldemort |
259+
260+ ### 监控指标
261+
262+ - ** 分区检测时间** :多久发现网络分区
263+ - ** 收敛时间(Convergence Time)** :副本从不一致到一致的时间
264+ - ** 读写延迟 P99** :CAP 权衡的直接体现
265+ - ** 不一致窗口** :业务可接受的数据延迟
266+
267+ ### 常见误区
268+
269+ - ❌ "选择了 AP 就永远放弃一致性" → ✅ AP 系统可通过 Read Repair、Anti-Entropy(Merkle Tree)达到最终一致
270+ - ❌ "ZooKeeper 是强一致的" → ✅ ZooKeeper 提供** 线性化写入** + ** 顺序一致性读取** (非最终一致性),读取存在滞后但保证全局顺序
271+ - ❌ "顺序一致性 = 最终一致性" → ✅ 顺序一致性保证全局更新顺序,最终一致性不保证顺序;ZooKeeper 普通读取是前者而非后者
272+ - ❌ "银行系统必须 CP" → ✅ 实际银行采用 BASE + 补偿事务(Saga),核心账务强一致,查询服务可最终一致
273+
164274<!-- @include: @article-footer.snippet.md -->
0 commit comments