19 分布式系统与 NFS/AFS — 不可靠网络上的可靠错觉¶
1. 这一章讲什么¶
把文件系统的幻觉延伸到网线那头。坏消息是分布式的定义性事实: 任何部件都会以任何方式失败,而且失败往往是部分的、静默的、 临时的(对面没死,只是网络断了,你分不清)。本章先讲通信 (在不可靠之上搭可靠:ack、超时重试、序列号),然后讲两个 真实系统怎样对这一事实作出相反的回应:NFS 选择无状态 (服务器什么都不记,重启即恢复),AFS 选择缓存到底 (打开文件就是整份下载,服务器用「回调」承诺通知失效)—— 两者都是为了让「对面靠不住」这件事尽量不伤及用户。
2. 顶层全景¶
通信层(不可靠 → 可靠的搭法):
UDP:尽力而为,丢了不吭声(有校验和,防位翻转)
ack:收到请回执 ──▶ 回执丢了?超时重试
重试 ⇒ 重复 ⇒ 序列号 ⇒ 接收方去重 ──▶ 「恰好一次」
超时的取值:太短白费重发,太长拖慢;洪峰时指数退避
│
NFSv2(1980s,Sun;设计目标 = 服务器崩溃后秒级恢复):
无状态:每个请求自带完成它所需的全部信息(文件句柄+偏移+数量)
文件句柄 = 卷号 + inode 号 + 代际号(防旧句柄串到新文件)
全部操作幂等(READ/WRITE 天然幂等)⇒ 客户端无脑重试即可
客户端缓存一致性:轮询 GETATTR 对比修改时间(属性缓存约 3 秒窗口)
│
AFS(1980s,CMU;设计目标 = 一个服务器撑尽可能多的客户端):
全文件缓存:open = 整份拉回本地(客户端内存+磁盘)
回调(callback):服务器承诺「这文件变了我会通知你」
缓存一致性 = 打开时拿最新一致副本,语义简单可讲给用户听
代价:小改大文件也要整份回写;回调状态在内存(服务器重启要重建)
图说:上半是通信原语,下半是一对镜像设计——一个扔掉状态换恢复, 一个囤积状态换规模。
3. 核心原理¶
3.1 主线例子:一条消息的「恰好一次」之旅¶
书里的渐进构造,照原书的教法走1:
- 裸发(UDP)。客户端(发起请求的那台机器)发「hello world」,服务器(应答的那台)回 「goodbye world」。丢了呢?没人知道。UDP 只保一件事: 校验和,能发现位翻转,发现不了丢失2。
- 加 ack。收件方回执。ack 收到 = 对方确实收到了。 ack 没收到 = 两种可能:消息丢了,或 ack 丢了——发件方分不清, 只好超时重试。于是发送方必须留副本、设定时器: 「到点没回执就重发」3。
- 重试引来重复。ack 丢了导致同一条消息送达两次—— 下载文件里混进重复包可不是小事。要恰好一次, 每条消息得带唯一编号,接收方记「见过没」,见过就再 ack 一次 但不交给应用。朴素的无限记录太贵,实用解是序列号: 双方各维护一个计数器,消息带当前值 N,发完加一; 接收方只认「下一条该来的号」4。
- 超时取多大?太短:白重发,烧 CPU 和网络;太长:慢了。 还有一个系统级考量:一堆客户端超时重试本身会加剧服务器过载 (丢包可能就是过载的信号)——解法是老祖宗 Aloha 网就有的 指数退避:超时翻倍再重试5。
这套 ack/重试/序列号三件套之上的请求-响应,就是 RPC (远程过程调用)的骨架;RPC 的语义分级(至多一次、至少一次、 恰好一次)是分布式接口设计的常识刻度6。
3.2 NFSv2:把状态从服务器上全部请出去¶
出发点:多客户端单服务器环境里,服务器一倒,全场停工—— 所以协议的头号目标是崩溃后快速恢复7。
有状态的协议为什么怕崩?以 open() 为例:服务器返回文件描述符, 后续 read 都引用它。这个描述符是客户与服务器的共享状态 (Ousterhout 叫它「分布式状态」):服务器在两次 read 之间崩了重启, fd 指向什么它早忘了,必须跑一套恢复协议;反过来客户端崩了, 服务器还得侦测「对面是不是死了」以便关文件8。
NFS 的解法:每个请求自含全部信息。协议核心是三件套的 文件句柄(file handle)= 卷标识 + inode 号 + 代际号 ——代际号是关键细节:inode 号被回收复用时代际号 +1, 拿着旧句柄的客户端就不会误摸到新文件9。协议表一目了然: READ 请求 = (句柄, 偏移, 数量),自含;LOOKUP 请求 = (目录句柄, 名字),自含……服务器不需要任何会话记忆10。
幂等是重试的安全垫。超时重试若造成重复执行会怎样? READ 重复执行十次结果一样(只读);WRITE 也一样—— 往偏移 X 写数据 D,写几次都是 D。NFS 的协议设计 刻意让所有操作幂等(「赋值」幂等,「加一」不幂等), 于是客户端崩溃恢复只剩一句:「重试到成功为止」11。
客户端缓存的一致性代价。无状态的另一面:服务器不知道 谁缓存了什么。客户端读缓存之后,文件可能被别的机器改—— NFSv2 的办法是轮询:每次用缓存块前先 GETATTR 问修改时间, 变了就丢缓存;属性本身又可以缓存约 3 秒——窗口期内你能读到 过期的文件(后来实现放宽到 30–60 秒)。这是一个明确写在 行为里的妥协:单客户端靠「关闭时冲刷」(flush-on-close)保证 更新可见;多客户端则是「最多滞后一个属性缓存窗口」12。
3.3 AFS:把交互压到极限换规模¶
AFS(CMU,1980s,Satyanarayanan 主持)的目标和 NFS 正交: 不是快速恢复,是一个服务器撑多少客户端。瓶颈在协议往返数—— NFS 的轮询每次都要碰服务器13。
全文件缓存:打开文件 = 整份从服务器拉回(Vice 是服务器端, Venus 是客户端),缓存在本地磁盘(不只是内存); 关闭时如果有改动,整份 Store 回去。此后所有读写都本地化, 零服务器交互14。
回调(callback)取代轮询:客户端缓存文件时,服务器记一笔 「它缓存了 X」并发一个承诺:X 被修改时我会主动通知(打断)你。 客户端再打开 X 前只看本地的回调状态:VALID 直接用, INVALID 才重新拉。服务器从「每次访问都被打扰」变成 「只在真正变化时出声」——规模问题就此缓解15。
一致性语义也因此变得干净:打开时总能拿到「最新的 一致副本」(close-to-open 一致性)——这是可以直接讲给 用户听的语义;对比 NFS 的「3 秒 + 30 秒窗口」, AFS 的语义不那么依赖实现细节16。
代价清单(书里如实给出):小文件大流量场景全文件传输是浪费; 只改一小块的大文件也要整份回写(传输翻倍); 回调是内存状态——服务器重启后所有回调承诺作废, 客户端要逐个重建(好在协议能自愈); 回调断连期间,客户端可能错过打断,重新连上后要用版本向量 之类机制对账17。
对照表(本章的总结论):
| NFSv2 | AFS | |
|---|---|---|
| 首要目标 | 服务器崩溃快恢复 | 单服务器支撑更多客户端 |
| 状态的住处 | 客户端(服务器无状态) | 服务器(回调承诺)+ 客户端(整份缓存) |
| 崩溃恢复 | 免费(重试即恢复) | 需重建回调状态(协议自愈) |
| 缓存失效 | 客户端轮询 mtime(秒级窗口) | 服务器回调打断(打开时保证最新) |
| 一致性语义 | 模糊、依赖超时窗口 | 干净(close-to-open) |
| 典型代价 | 一致性弱 | 大文件小改也整份传输 |
图说:没有全优;「状态放哪端」这道选择题,换来的是崩溃恢复、 缓存一致性、扩展性三者中你更在乎的那个。
4. 作者的判断与证据¶
书里给了证据的判断:
- ack/超时/序列号的构造逐步给出交错图,每步的失败模式都演示过1;
- NFS 幂等性的论证逐操作给出(READ/WRITE 幂等,MKDIR 不幂等 但无所谓,CREATE 难幂等所以「何苦」)11;
- AFS 的性能分析(服务上千客户端)与回调失效的边界情形都有展开17。
作者的判断(立场):
- 「开放协议造就开放市场」被点名为 NFS 成功的主因—— 标准是部署的杠杆18。
- 作者对两个系统都不站队:第 51 章对话里教授提醒学生 「设计目标不同,没有输赢」;但书里明显偏爱 「语义能讲给用户听」的设计(对 AFS 的一致性评价更正面)。
5. 边界与局限¶
- NFSv4 已转向有状态(打开状态、委托、更细的缓存语义)—— 书里以 v2 为教学对象,并明说 v4 是大改;今天的 NFS 已不是 「纯无状态」样本(补充:这是协议演进对现实的让步)。
- AFS 的回调在广域/断连环境下会退化;其思想后裔是 Coda(断连操作)与各类同步盘(Dropbox 们), 但「回调」机制本身在超大规模下被轮询+版本向量取代。
- 本章不覆盖分布式系统的另半壁:共识与复制状态机 (Paxos/Raft)——书把它们留给「整门课」;NFS/AFS 只是 分布式文件系统这一对样本。
6. 可带走的¶
- 部分失败是定义性事实:设计分布式协议时,先问「对面随时 失联、且失联与死亡不可区分」会怎样。
- 可靠通信三件套:ack(确认)、超时重试(补救)、序列号(去重)。 从消息队列到数据库复制,处处是它。
- 幂等是重试的通行证:设计会被重试的接口时,把副作用写成 「做几次都一样」(赋值式),而不是「每做一次都叠加」(计数式)。
- 无状态换恢复,有状态换效率——但「状态」从来不会消失, 只是搬家:NFS 把它搬去客户端,缓存一致性因此变难。
- 文件句柄的代际号是句柄复用问题的通用解法: 任何「编号复用」的标识系统(DB 主键、句柄、token)都要想这一步。
- 「语义能讲给用户听」是好协议的重要判据——AFS 的一致性 一句话说清,NFS 的一致性要搬出超时窗口。
7. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 通信不可靠:丢包的根本性 | 48. Distributed Systems | text/50-ch48-48-distributed-systems.txt:30(搜「fundamentally unreliable」) |
| UDP 与校验和 | 48. Distributed Systems | text/50-ch48-48-distributed-systems.txt:113(搜「Unreliable Communication」) |
| ack + 超时重试 | 48. Distributed Systems | text/50-ch48-48-distributed-systems.txt:244(搜「acknowledgment」) |
| 序列号去重 | 48. Distributed Systems | text/50-ch48-48-distributed-systems.txt:291(搜「duplicate」)与:301(搜「sequence counter」) |
| 超时取值与指数退避 | 48. Distributed Systems | text/50-ch48-48-distributed-systems.txt:311(搜「TIMEOUT VALUE」) |
| RPC 语义分级 | 48. Distributed Systems | text/50-ch48-48-distributed-systems.txt:526(搜「exactly once」) |
| 服务器为什么会崩 | 49. Sun’s Network File System (NFS) | text/51-ch49-49-sun-s-network-file-system-nfs.txt:76(搜「WHY SERVERS CRASH」) |
| 无状态设计目标 | 49. Sun’s Network File System (NFS) | text/51-ch49-49-sun-s-network-file-system-nfs.txt:105(搜「crash recovery」) |
| 文件句柄三件套 | 49. Sun’s Network File System (NFS) | text/51-ch49-49-sun-s-network-file-system-nfs.txt:195(搜「file handle」) |
| 协议请求自含信息 | 49. Sun’s Network File System (NFS) | text/51-ch49-49-sun-s-network-file-system-nfs.txt:215(搜「NFSPROC」) |
| 幂等性论证 | 49. Sun’s Network File System (NFS) | text/51-ch49-49-sun-s-network-file-system-nfs.txt:377(搜「idempotent」) |
| AFS 的目标:scale | 50. The Andrew File System (AFS) | text/52-ch50-50-the-andrew-file-system-afs.txt:6(搜「scale」) |
| 全文件缓存 | 50. The Andrew File System (AFS) | text/52-ch50-50-the-andrew-file-system-afs.txt:44(搜「whole-file」) |
| 回调机制 | 50. The Andrew File System (AFS) | text/52-ch50-50-the-andrew-file-system-afs.txt:147(搜「callback」) |
| 回调失效的边界 | 50. The Andrew File System (AFS) | text/52-ch50-50-the-andrew-file-system-afs.txt:341(搜「recall」) |
-
出处:「48. Distributed Systems」第 211–330 段(
text/50-ch48-48-distributed-systems.txt:224,搜「Figure 48.3」)。构造顺序:消息+ack → 超时重试(图 48.4)→ ack 丢失导致重复(图 48.5)→ 序列号。 ↩↩ -
出处:「48. Distributed Systems」第 113–210 段(
text/50-ch48-48-distributed-systems.txt:201,搜「unreliable」)。原文:UDP 丢包不通知发送方,但含校验和可检错。 ↩ -
出处:「48. Distributed Systems」第 242–258 段(
text/50-ch48-48-distributed-systems.txt:250,搜「timeout」)。 ↩ -
出处:「48. Distributed Systems」第 281–306 段(
text/50-ch48-48-distributed-systems.txt:301,搜「sequence counter」)。 ↩ -
出处:「48. Distributed Systems」第 311–329 段(
text/50-ch48-48-distributed-systems.txt:326,搜「exponential」)。原文:Aloha 网与早期以太网采用指数退避,避免重试风暴。 ↩ -
出处:「48. Distributed Systems」第 500–560 段(
text/50-ch48-48-distributed-systems.txt:526,搜「exactly once」)。 ↩ -
出处:「49. Sun’s Network File System (NFS)」第 105–119 段(
text/51-ch49-49-sun-s-network-file-system-nfs.txt:111,搜「main goal」)。 ↩ -
出处:「49. Sun’s Network File System (NFS)」第 120–180 段(
text/51-ch49-49-sun-s-network-file-system-nfs.txt:154,搜「distributed state」)。 ↩ -
出处:「49. Sun’s Network File System (NFS)」第 194–210 段(
text/51-ch49-49-sun-s-network-file-system-nfs.txt:199,搜「generation number」)。 ↩ -
出处:「49. Sun’s Network File System (NFS)」第 215–271 段(
text/51-ch49-49-sun-s-network-file-system-nfs.txt:215,搜「NFSPROC」)。 ↩ -
出处:「49. Sun’s Network File System (NFS)」第 372–448 段(
text/51-ch49-49-sun-s-network-file-system-nfs.txt:377,搜「idempotent」)。原文:WRITE 幂等;CREATE 难做幂等,「why bother」。 ↩↩ -
出处:「49. Sun’s Network File System (NFS)」缓存一致性小节(
text/51-ch49-49-sun-s-network-file-system-nfs.txt:475,搜「cache consistency problem」)。原文:两个子问题——更新可见性(flush-on-close)与陈旧的缓存(客户端先查属性,约 3 秒的属性缓存窗口,见:546)。 ↩ -
出处:「50. The Andrew File System (AFS)」第 1–30 段(
text/52-ch50-50-the-andrew-file-system-afs.txt:6,搜「scale」)。 ↩ -
出处:「50. The Andrew File System (AFS)」第 44–70 段(
text/52-ch50-50-the-andrew-file-system-afs.txt:44,搜「whole-file caching」)。 ↩ -
出处:「50. The Andrew File System (AFS)」第 147–215 段(
text/52-ch50-50-the-andrew-file-system-afs.txt:148,搜「promise」)。原文:回调是服务器「文件变了我会通知你」的承诺。 ↩ -
出处:「50. The Andrew File System (AFS)」第 253–265 段(
text/52-ch50-50-the-andrew-file-system-afs.txt:253,搜「cache consistency」)。 ↩ -
出处:「50. The Andrew File System (AFS)」第 300–360 段(
text/52-ch50-50-the-andrew-file-system-afs.txt:341,搜「recall」)与性能讨论(:455,搜「doubling」)。 ↩↩ -
出处:「49. Sun’s Network File System (NFS)」第 92–104 段(
text/51-ch49-49-sun-s-network-file-system-nfs.txt:97,搜「open protocol」)。 ↩