5.1 分布式理论基石


5.1 分布式理论基石

本节摘要:CAP 定理是分布式系统的第一块基石:在网络分区必然发生的前提下,一致性与可用性只能二选一。它常被误读成"给系统贴一个 CP 或 AP 的标签",其实它描述的是分区发生的那个瞬间。无分区时,PACELC 定理补上了延迟与一致性之间的日常权衡。再把一致性从"一个开关"展开成"一条光谱"——线性一致、顺序一致、因果一致、最终一致——你就有了判断任何分布式系统行为的标尺。

你能学到什么

阅读完本节,你应当能够:

  1. 说出 CAP 定理中一致性、可用性、分区容忍性各自的准确定义。
  2. 解释为什么分区容忍性不是可选项,并识别"CP 系统"这个说法的毛病。
  3. 用 PACELC 描述无分区时的延迟与一致性权衡。
  4. 按强度排列线性一致、顺序一致、因果一致、最终一致,并举例说明。
  5. 用读写法定人数解释为什么 W 加 R 大于 N 能避免读到陈旧数据。

一、一台机器为什么撑不住

先回到一个朴素的起点:我们为什么非要把数据拆到多台机器上?

最直接的原因是装不下。单机的磁盘有上限,内存有上限,就算换上最贵的机器,物理极限也摆在那里。第二层原因是扛不住:一台机器每秒能处理的请求数是有限的,CPU 核数、网卡带宽、磁盘 IO 都是天花板。第三层原因更隐蔽,是怕坏掉——再可靠的机器,也迟早会宕机、会断电、会硬盘损坏。把数据只放在一台机器上,等于把整个业务的命押在一张牌上。

于是我们把数据切成片,分到不同的机器上;再给每一份数据抄几份副本,放到不同的机器上。这一拆一抄,看似简单,却把我们从单机的确定性世界里,一脚踢进了一个充满不确定性的世界。多台机器之间要靠网络通信,而网络会延迟、会丢包、会彻底断开;多台机器各自有自己的时钟,而时钟会漂移;任何一台机器都可能在任意时刻崩溃,而你无法立即知道它是真崩了还是只是"慢"。

一句话:单机数据库是街角那家只有一个窗口的邮局,你要寄信,走到窗口,办完,走人,因果清晰。分布式系统是一张遍布全城的邮政网络,有几十个网点,信可能从任何网点进出,网点之间还可能因为暴雨断路。这时候,"这封信到底寄出去没有"这个问题,突然变得没那么好回答了。

这一节要讲的,就是在这样的不确定性里,我们还能不能、以及如何谈论"正确"。

二、CAP 定理:一场必须做的取舍

2000 年前后,埃里克·布鲁尔提出一个猜想,两年后被吉尔伯特和林奇严格证明,这就是 CAP 定理。它的意思,用大白话说就是:一个跨越多台机器、靠网络通信的分布式系统,不可能同时做到下面三件事——

  • 一致性:所有节点在同一时刻读到的都是最新写入的值。这里的一致性,指的是最强的那个等级,线性一致。
  • 可用性:每一个没坏掉的节点,对每一个请求,都必须在有限时间内返回一个结果,哪怕这个结果不是最新的。
  • 分区容忍性:当网络出现分区——也就是一部分节点之间互相联系不上了——系统仍然能继续运行。

关键在第三点。很多人以为分区容忍性是一个可以勾选或取消的选项,其实不是。只要你的系统跑在多台机器上,网络分区就迟早会发生:光纤被挖断、交换机重启、机房断电、云厂商可用区之间的链路抖动。分区不是"会不会发生"的问题,而是"什么时候发生"的问题。所以 P 是前提,你没法放弃它,真正能选的,只有分区发生时你站在 C 那边,还是 A 那边。

这就引出了 CAP 最常被误读的地方。我们经常听到"某某数据库是 CP 的,某某是 AP 的",这种说法把系统简化成了贴标签。实际上,CAP 描述的是系统在分区发生的那一瞬间的行为,不是它平时的常态。一个平时读写飞快、强一致的数据库,在分区发生时可能选择拒绝写入(站在 C 这边);另一个平时用最终一致的系统,在分区时可能选择继续服务、接受读到旧数据(站在 A 这边)。分区一旦恢复,它们又回到各自的正常状态。

我更愿意把 CAP 想成"分区时刻的选择题",而不是"系统的属性卡"。这样想,你才不会拿着一个 CP 标签去给系统打包票,也不会因为一个系统在分区时选择了可用性,就断定它永远不一致。

CAP 与 PACELC 的关系

CAP 只覆盖了"分区发生"这一种情况。可现实里,分区是少数时候,大多数时候网络是好的。没分区的时候,系统就高枕无忧了吗?没有。就算网络通畅,一个跨洲的读请求,往返延迟也要上百毫秒。这时候你要不要为了强一致,等最慢的那个副本回话?这就是 PACELC 定理要补的空白:没有分区时(Else),你还要在延迟(Latency)和一致性(Consistency)之间做选择。

于是分布式系统要做的,其实是在两个场景里分别做一次选择:分区时选 C 还是 A,没分区时选低延迟还是强一致。这两个选择不必一致。一个系统完全可以在平时追求低延迟、读本地副本,而分区一发生就立刻转成强一致、拒绝写入。理解了这一点,你就理解了很多数据库那些看似矛盾的行为:平时很快,故障时很"倔"。

三、一致性是一条光谱,不是一个开关

CAP 里的"一致性"指的是最强的那个等级。但现实中,不是每个场景都需要那么强。把一致性当成一个布尔值,非黑即白,是最容易犯的错。它其实是一条光谱,从强到弱排列着多个被精确定义的等级。

最顶端是线性一致。它要求系统表现得像一台单机:任何一次读,都必须返回它开始之后、下一个写开始之前某个时刻的最新值;所有操作可以被排成一个符合真实时间顺序的序列。这是程序员的直觉延伸,也是最贵的一致性,因为它往往要求跨节点协调,牺牲延迟。

往下是顺序一致。它不要求"实时可见",但要求所有进程看到的操作顺序,和某个全局的串行顺序一致,且每个进程内部的操作顺序保持自己的程序顺序。它禁止了"乱序的幻觉",但允许一个操作在真实时间里已经完成,别的进程却过一会儿才看到。

再往下是因果一致。它只保证有因果关系的操作被按先后顺序看到:如果 B 读到了 A 写的数据,那么所有节点必须先看到 A,再看到 B。没有因果关系的操作可以并发、可以乱序。这更贴近人类的直觉——前因后果不能颠倒,但两件无关的事谁先谁后无所谓。

最后是最终一致。它只承诺:如果系统不再有新的写入,那么经过足够长的时间,所有副本最终会收敛到同一个值。中间的过程可以有旧数据、可以有短暂的分歧。这是最宽松的承诺,也是很多互联网系统在默认情况下给的东西。

在这条光谱上,还有两个针对单客户端体验的实用保证值得单独拎出来:单调读,保证一个客户端不会读到"倒退"的旧版本,读到新值之后不会再读回旧值;读己之所写,保证一个客户端写完一条数据后,它自己随后的读一定能看到这条写入。这两个是 Web 应用最基本的心理契约——你刷新页面,至少不该看到自己刚才提交的东西凭空消失。

常见一致性模型对比

一致性模型 核心承诺 典型代价 适用场景
线性一致 像单机一样,实时看到最新值 高延迟、需协调 分布式锁、选主、金融扣款
顺序一致 全局顺序一致但可延迟可见 中等协调开销 共享内存、部分缓存
因果一致 有因果的操作有序,无关的可乱序 需携带因果元数据 社交时间线、协作编辑
最终一致 停止写入后终会收敛 可能读到旧值、需处理冲突 商品详情、评论、DNS
单调读 单客户端不倒退 需按客户端绑定副本 用户会话内的连续读
读己之所写 自己写的自己必能读到 需追踪自身写入 表单提交后刷新

⚠️ 常见坑:把"最终一致"当成"不需要处理冲突"。最终一致只保证"最终会一致",不保证"怎么一致"。两个节点同时改了同一个字段,谁覆盖谁?这个问题系统不会自动替你回答,你必须用版本号、时间戳或业务规则去消解冲突。忽略它,上线后就会冒出"订单状态被旧值覆盖"这类事故。

💡 关键直觉:选择一致性等级,本质是在为"读到的数据有多新"这件事标价。你不该默认上最强的一致,也不该偷懒全用最终一致。问自己一句:这个读操作,读到旧值会造成什么后果?后果越严重,越该往上走;后果可以容忍或可以补偿,就往下走。

四、法定人数:把一致性落到具体数字上

光谱是方向,工程上还需要一把尺子。这把尺子就是法定人数,也就是我们常说的读写副本数 W 和 R。

假设一份数据有 N 个副本。每次写,要等 W 个副本确认才算成功;每次读,要从 R 个副本里读。只要保证 W 加 R 大于 N,那么任何一次读,至少会碰到一个"参与了最近那次成功写"的副本,于是你读到的一定包含最新值。这个不等式看起来简单,却是很多系统的一致性根基。

反过来调这两个参数,你就在一致性光谱上移动。写副本数取 1、读副本数取 N,写最快,但读最贵,而且读到的未必最新;写副本数取 N、读副本数取 1,读最快最便宜,但写要等所有副本,慢且脆弱。实际系统会在这两个极端之间找平衡:比如三副本里写二读二,写要等多数派,读也要等多数派,既保证读新,又容忍一个副本挂掉。

这个机制也解释了为什么"多数派"这么重要。选主、提交日志、判断提交与否,都依赖"过半节点同意"这个条件。因为任意两个过半的集合必有交集,这保证了任意两轮多数派决策之间能"传递记忆"——上一轮的决定,一定被下一轮的某个节点看到。

💡 关键直觉:法定人数不是越多越好。W 和 R 调大,一致性变强,但延迟变高、可用性变差;调小,延迟低,但可能读到旧值。这是一根可以实时拨动的旋钮,很多系统允许每个请求单独指定,而不是全局写死。

五、工程上的取舍:两个场景走一遍

理论落到工程,就是看你在具体场景里怎么拨这根旋钮。

场景一:电商库存扣减。 库存不能超卖,这几乎是硬约束。但如果我们每笔扣减都跨节点做线性一致的协调,大促期间就会慢到不可用。常见的做法是分层:缓存层用原子递减做快速预扣,接受一定程度的近似,靠异步消息把最终扣减落到数据库,再用定时对账和补偿把不一致抹平。这里的"一致性"被拆成了两段:前台要的是低延迟的近似一致,后台要的是最终收口的强一致。

场景二:金融转账。 转账不能"先扣后没加上",也不能"两边都扣了"。这是强一致的地盘,宁可在分区时拒绝服务,也不能出现账目不一致。所以金融核心系统普遍选择分区时站在一致性这边,牺牲可用性来保账本正确。

这两个场景告诉我们,CAP 和一致性光谱不是用来背的教条,而是用来描述"你到底能接受什么"的语言。有了这层语言,你才能在白板上把一次读操作到底容忍多少陈旧、一次写操作到底要等几个副本,谈得清清楚楚。

本章回顾

  • CAP 不是三选二:分区容忍性是前提,真正可选的只有分区时站在一致性还是可用性那边。
  • CAP 描述的是分区瞬间:它不是系统的永久标签,别拿 CP 或 AP 给系统打包票。
  • PACELC 补全了无分区场景:没有分区时,还要在低延迟和强一致之间做选择。
  • 一致性是一条光谱:线性一致、顺序一致、因果一致、最终一致,强度递减,代价也递减。
  • 法定人数是那把尺子:W 加 R 大于 N,就能保证读到的包含最新值。
  • 最终一致不等于免冲突:系统只保证收敛,不保证收敛的方式,冲突要自己处理。
  • 选一致性就是标价:读到旧值后果越严重,越该往上走;能容忍或能补偿,就往下走。

有了这套语言,下一节我们去看它怎么落地——数据如何被切成片、抄成多份,以及分片键和副本数这些具体旋钮怎么选。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U