Andrew 文件系统 (AFS)
Andrew 文件系统 (AFS) 的核心设计目标是扩展性 (Scale),即让单个服务器支持尽可能多的客户端。协议设计直接影响扩展性:例如 NFS 强制客户端定期检查缓存一致性,频繁的检查会消耗服务器 CPU 和网络带宽,从而限制扩展性。相比之下,AFS 的缓存一致性简单易懂:客户端在打开文件时,通常会从服务器接收最新的副本。
AFSv1 及其问题
AFS 所有版本的基本原则是整文件缓存 (Whole-file caching)。文件被缓存在访问该文件的客户端本地磁盘上。
open(): 从服务器获取整个文件并存储在本地磁盘。read()和write(): 重定向到本地文件系统,无需网络通信。close(): 如果文件被修改,则将新版本刷新回服务器。
与 NFS 在客户端内存中缓存数据块不同,AFS 在本地磁盘缓存整个文件。当再次访问该文件时,客户端首先通过 TestAuth 协议消息联系服务器,确认文件是否更改。如果未更改,则使用本地缓存,避免网络传输。
通过测量原型系统,设计者发现了 AFSv1 的两个主要问题:
- 路径遍历成本过高: 客户端在执行获取或存储请求时传递完整路径名。服务器必须执行完整的路径遍历,消耗大量 CPU 时间。
TestAuth消息过多: 客户端发送大量流量检查本地文件是否有效。服务器将大量时间用于告诉客户端缓存副本未更改。
AFSv2 协议改进
为解决上述问题,AFSv2 引入了两个关键机制:
- 回调 (Callback): 服务器向客户端承诺,当客户端缓存的文件被修改时,服务器会通知客户端。客户端无需再联系服务器查询有效性,而是假设文件有效,直到服务器另行通知(类似于中断替代轮询)。
- 文件标识符 (FID): 替代完整路径名。FID 包含卷标识符、文件标识符和唯一标识符。客户端逐层遍历路径并缓存目录结果,减少服务器负载。
每次获取目录或文件时,AFS 客户端都会与服务器建立回调。后续访问完全在本地进行,无需服务器交互。
缓存一致性
由于回调和整文件缓存,AFS 的缓存一致性分为两种情况:
- 不同机器间的进程: AFS 在更新后的文件被关闭时,使更新在服务器可见,并同时使缓存副本失效。服务器通过联系每个拥有缓存的客户端并“打破 (Break)”其回调来实现。这确保客户端不会读取陈旧副本,后续打开操作将重新获取新版本并重建回调。
- 同一机器上的进程: 对文件的写入立即对其他本地进程可见,符合典型的 UNIX 语义。
在不同机器上的进程同时修改文件的罕见情况下,AFS 采用最后写入者胜出 (Last writer wins / Last closer wins) 策略。最后调用 close() 的客户端将最后更新服务器上的整个文件。这与 NFS 的块级协议不同,NFS 可能会将两个客户端的更新混合。
崩溃恢复
AFS 的崩溃恢复比 NFS 更复杂:
- 客户端崩溃: 客户端重启期间可能错过服务器发送的回调撤销消息。因此,客户端重新加入系统时必须将所有缓存内容视为可疑,并在下次访问时通过
TestAuth消息向服务器验证有效性。 - 服务器崩溃: 回调状态保存在内存中。服务器重启后不知道哪个客户端拥有哪些文件。因此,每个客户端必须意识到服务器已崩溃,并将所有缓存内容视为可疑。服务器可以通过重启后向客户端发送消息或客户端定期发送心跳消息来处理。
扩展性与性能
AFSv2 的扩展性显著提升,每个服务器可支持约 50 个客户端。在常见情况下,文件访问都在本地进行,性能接近本地文件系统。
与 NFS 的性能对比:
- 首次读取和顺序写入: 性能相近。AFS 受益于客户端操作系统的内存缓存。
- 大文件顺序重读: AFS 优于 NFS。AFS 拥有本地磁盘缓存,而 NFS 只能在内存中缓存块。重读大于内存的文件时,NFS 必须重新从服务器获取。
- 顺序覆盖写: AFS 表现较差。客户端必须先获取整个旧文件,然后再覆盖它。NFS 只是覆盖数据块,避免了初始的无效读取。
- 大文件中的小规模访问: NFS 表现更好。AFS 在打开文件时获取整个文件,修改后写回整个文件。NFS 仅执行与读写大小成比例的 I/O。
其他改进
AFS 还引入了其他特性:
- 提供真正的全局命名空间,确保所有客户端上的文件命名一致。
- 包含用户身份验证机制和灵活的用户管理访问控制。
- 提供简化服务器管理的工具。