☰
第八章:分布式系统的挑战
2026/9/29 15:03:31 网站建设 项目流程

第八章:分布式系统的挑战

这一章是全书的一个转折点。前七章讨论的是"如何让分布式系统正常工作",第八章则揭示一个残酷现实:分布式系统里没有什么是可靠的——网络会延迟、时钟会漂移、进程会暂停、节点会"假死"。Kleppmann 的核心论点是:在分布式系统中,你无法区分"节点挂了"和"节点很慢",也无法依赖时钟和网络来建立全局秩序。所有分布式算法,都必须在这种不确定性之上建立。

本章结构:先讲网络不可靠,再讲时钟不可靠,然后讲进程暂停,最后讨论这些不确定性带来的根本性知识问题。


一、本章的核心命题

分布式系统的根本困难,不在于"机器会坏",而在于"你无法确定它到底坏没坏"。

在单机系统中,一个进程崩溃是明确的。但在分布式系统中:

  • 一个节点没响应,可能是挂了,可能是网络慢,可能是 GC 停顿,可能是对方在处理但没回
  • 你无法区分这些情况
  • 这就是所谓的部分失效(Partial Failure)

Kleppmann 的比喻:

单机编程像在自家房子里走动,你知道每个房间在哪;分布式编程像在异国他乡,你连方向都不确定,还要跟一群说着不同语言、可能随时消失的人合作。


二、不可靠的网络

1. 网络的基本事实

  • 数据包可能丢失、延迟、重复、乱序
  • 发送方无法知道对方是否收到
  • 超时是唯一的手段,但超时无法区分"慢"和"死"

2. 网络故障的常见误解

误解一:网络是可靠的

  • 实际上,数据中心网络也会有丢包、延迟抖动
  • 大规模系统中,网络故障是常态

误解二:延迟是固定的

  • 实际上,延迟波动极大(从微秒到秒级)
  • 原因:排队、拥塞、路由变化、GC

误解三:TCP 能保证可靠

  • TCP 只保证"最终送达或明确失败"
  • 无法保证延迟上限
  • 应用层超时后,TCP 可能还在重传

3. 网络分区(Network Partition)

定义:网络被分成多个部分,部分之间无法通信。

后果:

  • 节点之间无法达成一致
  • 可能出现脑裂(Split-brain):多个节点都认为自己是主节点
  • 这是分布式系统中最棘手的问题之一

Kleppmann 的提醒:

网络分区不是"会不会发生",而是"什么时候发生"。设计系统时必须假设分区会发生。

4. 超时的困境

超时设置的两难:

  • 太短:误判慢节点为故障,触发不必要的切换,可能造成脑裂
  • 太长:故障节点迟迟不被发现,服务中断时间长

没有完美答案:

  • 网络延迟是动态的,无法用固定超时适应所有情况
  • 自适应超时(如 Phi Accrual 故障检测器)是一种改进,但仍不完美

实践建议:

  • 用实验测量网络延迟分布(如 AWS 的混沌工程)
  • 超时设置为 p99.9 延迟的若干倍
  • 接受"误判"是必然的,设计容错机制

三、不可靠的时钟

1. 时钟的两种类型

(1)墙上时钟(Time-of-Day Clock)

  • 返回当前日期时间
  • 与 NTP 同步
  • 问题:
    • 可能跳变(NTP 校正、闰秒)
    • 不同机器可能不同步
    • 精度有限

(2)单调时钟(Monotonic Clock)

  • 返回自某起点以来的时间
  • 保证单调递增
  • 适合测量时间间隔
  • 不适合比较不同机器的时间

关键区分:

墙上时钟用于"现在几点",单调时钟用于"过了多久"。跨机器比较必须用墙上时钟,但墙上时钟不可靠。

2. 时钟同步的问题

  • NTP 同步有误差(毫秒到秒级)
  • 网络延迟导致同步不准
  • 闰秒导致时间跳变
  • 虚拟机时钟可能被暂停

Kleppmann 的警告:

不要用时间戳来决定分布式系统中的事件顺序。时钟不可靠,时间戳不能作为全局顺序的依据。

3. 依赖时钟的陷阱

(1)最后写入胜出(LWW)

  • 用时间戳解决冲突
  • 问题:时钟不同步,可能丢掉"更晚"的写入
  • 代表:Cassandra

(2)时间戳作为事务 ID

  • 问题:时钟回拨导致 ID 重复或乱序
  • 解决:混合逻辑时钟(HLC)

(3)租约(Lease)

  • 用时间保证"我在这段时间内是主节点"
  • 问题:时钟漂移导致租约失效,可能脑裂

4. 时钟的合理使用

  • 测量时间间隔:用单调时钟
  • 跨机器排序:用逻辑时钟(Lamport 时钟、版本向量)
  • 需要时间戳时:用 HLC,或接受不精确

四、进程暂停

1. 进程可能随时暂停

原因:

  • GC 停顿:Java 等语言的 GC 可能停顿数秒
  • 虚拟机暂停:迁移、快照、宿主机资源紧张
  • CPU 调度:操作系统可能抢占
  • 磁盘 I/O 阻塞:慢磁盘导致进程挂起

后果:

  • 进程在"暂停"期间,对外界来说像是挂了
  • 恢复后,可能发现世界已经变了
  • 租约过期、锁被释放、主节点被切换

2. 进程暂停的经典场景

场景:租约续期

  • 节点 A 持有租约,需要定期续期
  • A 发生 GC 停顿,错过续期
  • 节点 B 认为 A 挂了,接管
  • A 恢复后,仍以为自己持有租约
  • 脑裂

场景:分布式锁

  • 节点 A 获取锁,正在操作
  • A 停顿,锁超时释放
  • 节点 B 获取锁,开始操作
  • A 恢复,继续操作
  • 数据损坏

Kleppmann 的结论:

进程暂停是分布式系统中最容易被忽视的问题。任何依赖"进程会在预期时间内响应"的设计,都是脆弱的。

3. 应对进程暂停

  • Fencing Token:给每个锁请求一个递增编号,存储层拒绝旧编号的写入
  • 租约 + 心跳:但仍有窗口
  • 减少依赖:尽量设计无状态服务
  • GC 调优:减少停顿时间

五、知识、真相与谎言

1. 分布式系统的根本问题

你无法知道另一个节点在想什么。

  • 它可能挂了
  • 它可能活着但网络不通
  • 它可能活着但被暂停
  • 它可能活着但状态错误

这就是"分布式系统的知识问题":

在一个分布式系统中,每个节点只能根据自己的局部信息做判断,而这些信息可能是不完整、不准确的。

2. 主节点与"真相"

问题:谁来决定"谁是主节点"?

  • 主节点自己认为自己是主节点
  • 从节点可能认为主节点挂了
  • 没有全局的"真相"

解决:共识算法(Paxos、Raft)

  • 通过多数派投票决定
  • 但多数派也可能被网络分区分裂

3. 拜占庭故障(Byzantine Fault)

定义:节点可能发送错误、矛盾、恶意的消息。

  • 普通故障:节点崩溃或停止响应
  • 拜占庭故障:节点继续运行,但行为异常

应对:

  • 拜占庭容错算法(PBFT、PoW)
  • 区块链场景常用
  • 但代价高,大多数系统假设无拜占庭故障

Kleppmann 的提醒:

大多数分布式系统假设节点是"诚实的但可能崩溃",只有少数场景(如区块链)需要处理拜占庭故障。

4. 系统模型

为了讨论分布式算法,需要定义系统模型:

(1)网络模型

  • 可靠网络:消息不丢,但有延迟
  • 公平丢失网络:消息可能丢,但最终送达
  • 不可靠网络:消息可能丢,不保证送达

(2)节点模型

  • 崩溃-停止:节点崩溃后永久停止
  • 崩溃-恢复:节点可能崩溃后恢复
  • 拜占庭:节点可能任意行为

(3)时间模型

  • 同步系统:网络延迟和时钟有上限
  • 异步系统:无时间假设
  • 部分同步:大部分时间同步,偶尔异步

Kleppmann 的实践观点:

真实系统大多是部分同步的。算法设计要在异步模型下正确,在同步模型下高效。


六、本章的核心思想总结

主题核心观点
网络不可靠,无法区分"慢"和"死",超时是唯一手段
网络分区必然发生,脑裂是最大风险
时钟墙上时钟不可靠,不能用于全局排序
进程暂停GC、虚拟机、调度都可能暂停进程,导致租约失效
知识问题节点只能基于局部信息判断,没有全局真相
系统模型网络、节点、时间模型,是讨论分布式算法的前提

三个贯穿全书的判断:

  1. 分布式系统的根本困难是不确定性:你无法确定另一个节点的状态,也无法确定自己的判断是否正确。
  2. 时钟和网络不可靠,是设计约束而非 bug:任何依赖它们建立全局秩序的设计都是脆弱的。
  3. 进程暂停是最隐蔽的敌人:它让"看起来正常"的节点实际上已经失去同步。

七、这一章在全书中地位

第八章是从"能用"到"可靠"的转折点:

  • 第五章数据复制:复制滞后和冲突,根源是网络和时钟不可靠
  • 第六章数据分区:再平衡和路由,依赖网络和协调服务
  • 第七章事务:分布式事务面临网络分区和进程暂停
  • 第九章一致性与共识:在第八章的不确定性之上,建立一致性和共识
  • 第十章批处理、第十一章流处理:大规模数据处理同样面临这些问题

一句话概括本章:

分布式系统没有全局真相。网络不可靠、时钟不可靠、进程可能暂停,节点只能基于局部信息做判断。理解这些根本性挑战,才能理解为什么分布式算法如此复杂,以及为什么"简单"的分布式系统往往不可靠。这一章为第九章的一致性与共识奠定了问题基础。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询