Skip to content
Go back

分布式系统入门:从网络不确定性到 CAP 与 FLP

客户端发出一次写请求,等待几秒后超时。此时你无法确定服务器崩溃了、请求在网络中丢失了、服务器仍在处理,还是响应正在返回途中。

四种情况在客户端看来完全一样,却需要不同处理。直接重试可能让同一笔写入执行两次;不重试又可能把一次临时网络抖动变成用户可见错误。分布式系统最难的地方,正是节点之间无法立即分辨「失败」与「缓慢」。

Harshit Khosla 的系列开篇从这类不确定性出发,串起分布式计算八个误区、CAP、PACELC、FLP,以及同步、异步和部分同步模型。本文在此基础上补充这些理论的严格边界,并把它们转换成日常工程判断。

什么情况下算分布式系统

分布式系统由多个独立计算节点组成,对外提供一个整体服务。数据库集群、通过网络协作的微服务、负载均衡后的多个副本,都属于这个范围。

「独立」意味着每个节点拥有自己的内存、时钟和故障状态。节点 A 看不到节点 B 的内部状态,只能通过消息推断。消息又可能延迟、丢失、重复或乱序,因此本地函数调用中的确定性很难原样带到网络调用中。

一个很实用的区分方式是:

本地调用失败:调用方通常立即得到进程或系统返回的失败信息
远程调用超时:调用方只知道等待超过了期限,无法据此确定远端是否执行

这解释了为什么分布式接口通常需要请求 ID、幂等键、去重记录和可查询的最终状态。它们共同解决的核心问题是:调用方重试时,系统应识别这次动作是否已经发生。

八个危险的网络假设

Peter Deutsch 等人在 Sun Microsystems 总结的「分布式计算八个误区」,列出了设计者经常无意中采用的错误假设。

错误假设常见后果最小应对
网络可靠丢包或断连导致请求悬挂超时、重试、失败记录
延迟为零跨服务调用链越来越慢延迟预算、减少串行调用
带宽无限大对象和重复传输拖垮链路限制载荷、分页、压缩
网络安全内网调用缺少认证和加密身份验证、授权、传输加密
拓扑不变扩缩容或故障切换后地址失效服务发现、健康检查
只有一个管理员多团队操作产生配置差异自动化配置、变更记录
传输成本为零序列化、跨区流量和调用开销失控观察流量与编解码成本
网络同质不同设备、协议和版本产生兼容问题标准协议、版本协商

这些误区可以当作架构审查清单。只要设计跨越进程或机器,就应逐项问:网络中断时会发生什么,消息延迟时如何观察,重复请求会不会产生额外副作用。

CAP 讨论的是分区期间的保证

CAP 定理讨论分布式数据服务的三个性质:

Gilbert 与 Lynch 在异步网络模型中证明,发生网络分区时,系统无法同时满足这三项严格保证。

工程讨论里常把 CAP 概括成「三选二」,这句话容易让人以为三项可以自由组合。多节点系统无法控制网络是否发生分区。分区出现后,设计者需要决定:

因此,CAP 更像一个故障期间的选择题。所谓 CP 和 AP 也只是简写,真实系统往往允许按操作、数据类型或配置选择不同保证。把整个数据库永久贴成一个标签,会隐藏大量细节。

一个库存例子

假设两个机房共同销售最后一件商品。机房之间断连时:

哪个选择更合适,取决于错误成本。社交动态可以接受短暂旧读,付款、唯一编号和库存扣减通常需要更强约束。

PACELC 补上正常时期的取舍

网络分区只占系统运行的一小部分。大多数时间网络可用,系统仍需要在延迟与一致性之间选择。Daniel Abadi 提出的 PACELC 把两种情形放进同一框架:

P:发生分区时,在 A(可用性)与 C(一致性)之间选择
E:没有分区时,在 L(延迟)与 C(一致性)之间选择

强一致读取通常需要联系领导者或等待多个副本确认,会增加一次或多次网络往返。就近读取副本可以降低延迟,却可能返回尚未同步的数据。

PACELC 提醒我们,系统选择每天都在发生。一次跨区域同步写、一致读取的法定人数、缓存读取策略,都会在正常运行时支付延迟或数据新鲜度成本。

评估数据库时,可以具体询问:

  1. 分区时哪些请求继续,哪些请求失败?
  2. 正常时期读写需要联系几个副本?
  3. 一致性是全局固定,还是能按单次操作调整?
  4. 冲突由谁检测,如何合并,是否可能丢失更新?

这些问题比单独询问产品属于 CP 还是 AP 更有信息量。

FLP 限制了完全异步共识

Fischer、Lynch 和 Paterson 在 1985 年证明:在完全异步的消息系统中,只要允许一个进程发生崩溃,就不存在能够保证所有正确进程总会终止并达成共识的确定性算法。

这个结论需要注意三个限定:

FLP 给出的结论是,任何协议都存在一种可能永远不终止的执行过程。它没有宣称每次运行都会失败,也没有否定工程系统中的共识。

Paxos、Raft 和实际协调服务会增加模型假设,例如超时、最终稳定的网络、故障检测器或随机化。网络恢复稳定后,系统可以选出领导者并继续推进。时间假设主要帮助活性,即系统最终取得进展;安全性仍应在消息延迟和重复时保持。

这也是自制分布式锁或领导者选举容易出错的原因。一个简单脚本可能在正常网络中运行良好,却没有处理旧领导者恢复、租约过期、时钟偏移和网络分区。成熟的共识组件已经围绕这些边界积累了协议设计、测试和运维经验。

三种时间模型

理解 CAP 和 FLP 之前,需要先知道系统对时间作了什么假设。

同步模型

消息延迟和处理时间都有已知上限。超过上限未响应时,可以可靠判断节点故障。这个模型容易推理,但互联网和跨区域网络很难长期提供如此严格的保证。

异步模型

消息可能很快到达,也可能延迟任意长时间。系统无法仅凭等待时长区分慢节点与故障节点。FLP 的不可能结果建立在这一模型上。

部分同步模型

现实系统大多采用部分同步假设:延迟上限可能存在,但起初未知,或系统经过一段不稳定期后才恢复到某个上限以内。

RPC 超时和心跳间隔可以看成工程师对这个上限的估计。设置过短,会把暂时变慢的健康节点判成故障;设置过长,会延迟故障发现和恢复。它们需要结合延迟分布、故障恢复目标和误判成本调整。

理论如何进入接口设计

这些结论最终会落到几个很具体的选择上。

远程写入要支持安全重试

为有副作用的请求提供唯一幂等键。服务端保存键与结果,再次收到相同请求时返回原结果,避免重复扣款、重复建单或重复发消息。

超时只表示等待结束

调用超时后,把结果记录为「未知」比直接记为「失败」更准确。若业务重要,应提供状态查询或异步回执,让调用方确认远端是否已经执行。

明确每类数据的一致性要求

账户余额、库存和权限数据通常需要更强一致性;浏览计数、推荐结果和部分缓存可以接受短暂旧读。要求所有数据使用最强一致性会增加延迟和协调成本,全部放宽又会把冲突推给业务层。

把共识交给成熟组件

需要分布式锁、成员变更、领导者选举或配置一致性时,优先使用经过验证的协调服务和数据库能力。自行实现前,应能说明故障模型、安全性质、活性条件和恢复过程。

在测试中主动制造不确定性

常规单元测试很难覆盖分区故障。集成测试可以加入延迟、丢包、重复消息、节点重启、时钟偏移和请求乱序,并检查系统是否保持关键业务约束。

设计前的六个问题

下一次评审分布式功能时,可以先回答:

  1. 请求超时后,远端是否可能已经完成操作?
  2. 重试是否安全,幂等记录保存多久?
  3. 分区期间,系统优先保留哪类请求和哪项业务约束?
  4. 正常时期,为更强一致性要付出多少网络往返?
  5. 哪个组件负责共识,它依赖怎样的时间与故障假设?
  6. 监控和测试能否观察延迟、丢包、重复执行与副本差异?

这些问题能把抽象定理转换成可验证的设计条件。分布式系统中的异常很少真正随机,它们通常来自网络不确定性、时间假设或一致性选择。把假设写清楚,再用幂等、观察信号、成熟协议和故障测试保护边界,系统在真实故障下才更容易预测。

Aide Hub 会继续分享 AI 助手、开发工具和软件工程实践。

参考


Tags


Previous

分布式系统时间:Lamport、向量时钟与 HLC

Next

Harness 工程:让 AI Agent 真正可靠