跳转到内容

AFS 1988 — 客户端缓存 + 回调失效让分布式文件系统真正能扩展

是什么

AFS(Andrew File System,安德鲁文件系统)是 CMU 和 IBM 在 1980 年代联合开发的分布式文件系统。这篇 1988 年的论文记录了它从第一版到第二版的重设计——核心目标是让一个文件系统能撑住整个校园上千台工作站的访问,而不是 NFS 那样只够一个实验室用。

日常类比:你想象成一个超大型连锁书店。NFS 像每次都要回总部确认”这本书还在不在书架上”——单店没事,但 5000 家分店每天打几亿个电话总部直接瘫痪。AFS 改成”你拿一本书走的时候,总部承诺:等这本书改了我会主动给你打电话”——平时所有分店都靠自己手里那本走,电话量直降到几乎为零。

技术上,AFS 给出三个关键设计:

  • 整文件缓存到客户端本地磁盘(不是 RAM、也不是按块)
  • 回调协议(callback):服务器记录”哪个客户端缓存了这个文件”,文件改了时主动通知失效
  • 卷(volume):把一个目录子树打包成可整体迁移、配额、复制的最小管理单元

为什么重要

  • 不理解 AFS,看 Coda / DCE-DFS / Ceph / 现代对象存储的”客户端缓存 + 失效”思路会找不到源头
  • “服务器主动推失效” vs “客户端定期问”——这一刀切了过去 40 年所有缓存系统的设计
  • 1988 年这篇论文给出明确数字:服务器 CPU 占用从 40% 掉到 7%,单台服务器服务客户端数从约 20 提升到约 200——10 倍扩展性来自一个协议改动
  • AFS 后来商业化成 Transarc,被 IBM 收购,2000 年开源成 OpenAFS——CERN、MIT、CMU 内部直到今天还在跑
  • 它和 NFS(同年代、同样目标)的对比,是分布式系统课程”无状态 vs 有状态”的标准教学案例

核心要点

1. 整文件缓存(whole-file caching)

客户端第一次打开 /afs/cs.cmu.edu/user/satya/paper.tex,AFS 把整个文件下载到本地磁盘的 cache 目录里。后续 read / write 全在本地 cache 上跑,零网络往返。

代价:打开 1GB 文件读 1 字节也得先把 1GB 拖下来。所以 AFS 适合学术 / 办公 / 编译这类”一份文件反复读”的场景,不适合数据库或随机读大文件。

收益:之后所有 I/O 系统调用都不上网络,性能直接接近本地磁盘。

2. 回调协议(callback)

客户端拿到文件时,服务器在自己的表里记一条:“客户端 C 缓存了文件 F”。这就是回调承诺。之后:

  • 别人改了 F → 服务器扫表 → 给所有持有 F 缓存的客户端发”失效”通知
  • 客户端收到通知就把本地 cache 标记作废,下次访问时重新拉
  • 平时客户端读 F 完全不联系服务器

对比 NFS:NFS 客户端每次打开文件都要 getattr 问服务器”你最近改过吗”——10 倍 RPC 量的差距就在这里。

3. 卷(volume)

一个 volume 是一个目录子树(比如某用户的 home),是 AFS 的最小管理单位。管理员可以:

  • 给一个 volume 设配额:“satya 最多用 5GB”
  • 把整个 volume 从满的服务器迁到空的服务器,用户路径不变
  • 做只读副本(比如 /afs/.../bin/ 系统二进制),分发到多台服务器分担读负载

迁移过程中客户端正在读?AFS 会把请求重定向到新服务器,旧 handle 自动失效——用户感觉不到。

4. Vice / Virtue 分层

  • Vice:可信服务器集群,跑文件存储、callback 表、认证
  • Virtue:每台客户端,跑 cache manager 进程

这条边界让安全模型干净:客户端机器再多再不可信,攻击面都在 Virtue 那一层;Vice 自己只信另一台 Vice。

实践案例

案例 1:第一次打开文件

client: open("/afs/cs.cmu.edu/user/satya/paper.tex")
→ cache manager 检查本地 cache:没有
→ 联系服务器:拉整文件 + 拿 callback 承诺
→ 写到本地磁盘 /usr/vice/cache/V1234
→ 把后续 read 重定向到这个本地文件

之后再 open 同一个文件:cache manager 看本地有缓存、callback 还有效 → 零网络

案例 2:回调失效

[T0] 客户端 A、B、C 都缓存了 paper.tex
[T1] A 改了 paper.tex 并 close
[T2] A 把新版本上传到服务器
[T3] 服务器扫 callback 表 → 给 B、C 各发一条 "paper.tex 失效"
[T4] B、C 把本地缓存标记作废
[T5] B 下次 open paper.tex → 发现失效 → 重新下载

失效是异步的,所以 AFS 给的一致性保证只到”close 之后再 open 才能看到新版本”——和 NFS 的 close-to-open 一样的弱一致。

案例 3:服务器崩了怎么恢复

服务器掉电重启后,callback 表丢了——这时它做最保守的事:通知所有客户端”我崩过,你们手里所有 callback 都作废”。客户端收到后下次访问任何文件都重新去服务器问。

这是 AFS 比 NFS 复杂的地方:有状态服务器(callback 表)必须考虑崩溃恢复。NFS 因为无状态根本不需要这一步。

踩过的坑

  1. 回调风暴(callback storm):一个被 10000 个客户端缓存的热文件被改了一次 → 服务器要发 10000 条失效消息 → 网络瞬时风暴。AFS 通过批量 + 限流缓解,但热点文件依然要小心。

  2. 整文件缓存对大文件不友好:你想读 1GB 视频的最后 4KB → AFS 必须先把 1GB 完整拖下来。这个限制让 AFS 不适合多媒体。

  3. last-writer-wins:两个客户端同时编辑同一文件 → 后 close 的版本覆盖前一个,前面那个的修改直接消失。需要应用层自己加锁。

  4. stateful 服务器迁移成本高:NFS 服务器扩容是接一台机器、客户端 mount 过去就行;AFS 扩容要把 volume 整体迁、callback 表保持一致——运维门槛明显更高。

  5. uid 翻译:AFS 内部用自己的认证(早期 Kerberos v4),uid 不直接对应 Unix uid——挂载到 Unix 后权限语义有微妙差异,新人容易踩。

适用 vs 不适用场景

适用

  • 校园 / 公司 / 实验室级别的只读多 / 只写少共享存储(用户 home、共享代码、文档库)
  • 客户端机器数 > 100、希望服务器不被打爆
  • 需要”文件路径长期稳定”——AFS 全局命名空间 /afs/<cell>/... 跨年代不变

不适用

  • 数据库 / 高并发写场景 → 用 GFS / HDFS / Ceph
  • 大文件随机读(视频 / 模型权重) → 整文件缓存浪费带宽
  • 需要 POSIX 强一致 → AFS 只给 close-to-open
  • 客户端硬盘容量小(嵌入式 / 手机) → 缓存放不下

历史小故事(可跳过)

  • 1983:CMU 和 IBM 启动 Andrew Project,目标是给整个校园(10000 工作站)做计算环境。文件系统是核心组件。
  • 1985:AFS-1 上线。每次访问都要服务器验路径,单服务器只能撑 20 客户端——撞到瓶颈。
  • 1988 (这篇):AFS-2 引入回调 + 整文件缓存 + volume。同样负载下服务器 CPU 从 40% 掉到 7%,单服务器扩到 200 客户端。Andrew benchmark 测试体系也在此论文里第一次定义。
  • 1989:AFS 商业化为 Transarc 公司。
  • 1994:IBM 收购 Transarc。AFS 进入 OSF DCE,演化成 DCE/DFS。
  • 2000:IBM 把代码捐给开源社区,成为 OpenAFS——直到今天 CERN、MIT、CMU、Morgan Stanley 还在生产环境跑。
  • 后续影响:Coda(断网操作)、Ceph(FID 寻址)、现代 CDN 缓存协议——都能在 AFS 1988 这篇里看到 DNA。

学到什么

  1. 状态不是债,是资本——和 NFS 走相反路:把”服务器记得谁缓存了什么”当成可换 10 倍扩展性的本钱
  2. 被动询问 → 主动推送,是几乎所有缓存系统升级时都要走的一步:浏览器、CDN、CPU 缓存、数据库 view——同一个剧本
  3. 一致性可以弱,前提是语义说清楚——AFS 明确给 close-to-open,应用层就知道怎么写;模糊保证才是灾难
  4. 管理单元(volume)和性能单元(file)可以分离——这个解耦让运维独立于用户行为,是真正能”上千机器”的关键
  5. 学术原型和工业系统的区别在于扩展性测试——AFS-1 挺好的设计,到 50 客户端就崩了;AFS-2 同样的接口加一层协议优化就够了。先量化瓶颈再改

延伸阅读

  • 论文 PDF:Howard et al. 1988(约 28 页,但 1/3 是数据图,正文好读)
  • OpenAFS 官网 —— 开源版本,今天还在维护
  • 对比阅读:nfs-1985 —— 同年代相反思路,必须配套读
  • 后续工作:Coda 论文(Satyanarayanan 1990)—— AFS 的断网操作扩展
  • 教学视频:MIT 6.824 分布式系统课的 AFS 章节

关联

  • nfs-1985 —— 同时代竞品;NFS 选无状态、AFS 选有状态,两条路都活到今天
  • unix-1974 —— AFS 把 Unix 文件接口扩到全球命名空间
  • fielding-rest-2000 —— REST 也讨论无状态 vs 状态,但层次更高(HTTP)
  • lamport-tla-1994 —— 形式化分布式协议的工具,可用来验证 AFS 这类有状态系统

反向链接

  • amoeba-1990 —— Amoeba — 把整个机房当一台操作系统
  • bvt-1999 —— BVT 1999 — 让一份调度器同时照顾”急性子”和”老黄牛”
  • coda-1990 —— Coda 1990 — 笔记本拔网线照样写文件,重连后自动合并
  • disco-1997 —— Disco — 让没改过的商用 OS 在 64 核大机器上一起跑
  • farsite-2002 —— Farsite — 把一群不可信桌面 PC 拼成一台可信文件服务器
  • frangipani-1997 —— Frangipani — 把分布式文件系统盖在共享虚拟磁盘上
  • lamport-tla-1994 —— TLA — 把状态机和时序逻辑捏成一个公式
  • locus-1980 —— LOCUS 1980 — 让一群机器看起来像同一台机器
  • mcs-locks-1991 —— MCS 锁 — 让每个线程自旋在自己的缓存行上
  • nfs-1985 —— NFS 1985 — 让远程磁盘看起来像本地磁盘
  • sctp-multipath-2006 —— CMT-SCTP 2006 — 让两条网络路径同时干活而不打架
  • slab-1994 —— Slab Allocator 1994 — 内核按对象类型开缓存,不是按字节切
  • sprite-1988 —— Sprite 1988 — 把一屋子工作站伪装成一台大主机