分布式文件系统基础
分布式文件系统允许客户端通过网络访问服务器磁盘上的数据,实现数据共享和集中管理。系统主要由两部分组成:
- 客户端文件系统:接收应用程序的系统调用(如
open()、read()),并将其转换为网络协议消息。 - 文件服务器:接收网络请求,读取或写入本地磁盘(或缓存),并将结果返回给客户端。
无状态协议设计
Sun 开发的 NFS (Network File System) 采用了开放协议。NFSv2 的主要设计目标是简单快速的服务器故障恢复。
为实现此目标,NFS 采用了无状态 (Stateless) 协议:服务器不记录客户端的状态(如打开的文件、文件指针位置等)。每个客户端请求必须包含完成该请求所需的全部信息。相比之下,有状态协议(如使用文件描述符)在服务器崩溃后会丢失状态,需要复杂的恢复机制。
协议操作与文件句柄
无状态操作的核心是文件句柄 (File Handle),用于唯一标识要操作的文件或目录。文件句柄包含 3 个组件:
- 卷标识符 (Volume identifier):指示目标文件系统。
- inode 号 (Inode number):指示具体文件。
- 生成号 (Generation number):防止复用 inode 号时发生冲突。
关键协议操作包括:
LOOKUP:传入目录句柄和文件名,返回目标的文件句柄和属性。READ/WRITE:传入文件句柄、偏移量 (offset) 和字节数,直接进行读写,无需维护文件指针。GETATTR:获取文件属性(如最后修改时间)。
幂等操作与故障恢复
NFS 客户端通过重试 (Retry) 统一处理网络丢包或服务器崩溃等故障。如果请求超时未收到响应,客户端直接重新发送请求。
这种重试机制依赖于操作的幂等性 (Idempotency):多次执行同一操作的效果与执行一次相同。
LOOKUP和READ仅读取数据,天然幂等。WRITE请求包含具体的数据、字节数和精确的偏移量,因此也是幂等操作。多次写入同一偏移量不会改变最终结果。
(注:某些操作如 MKDIR 难以实现完全幂等,但 NFS 接受这种不完美以换取整体设计的简洁。)
客户端缓存与一致性
为提升性能,NFS 客户端会将服务器数据和元数据缓存在本地内存中,并作为写入的临时缓冲区。但这引入了缓存一致性 (Cache Consistency) 问题:
- 更新可见性 (Update visibility):一个客户端的写入何时对其他客户端可见?
- 解决方案:NFS 采用关闭时刷新 (Flush-on-close / Close-to-open) 语义。当客户端关闭文件时,将所有脏页刷新到服务器。
- 陈旧缓存 (Stale cache):客户端如何知道本地缓存已过期?
- 解决方案:客户端在使用缓存前,向服务器发送
GETATTR请求检查文件的最后修改时间。如果服务器上的文件更新,则使本地缓存失效。 - 优化:为避免
GETATTR请求淹没服务器,NFS 引入了属性缓存 (Attribute cache),在设定的超时时间(如 3 秒)内直接使用本地属性,但这可能导致短时间内读取到旧版本数据。
- 解决方案:客户端在使用缓存前,向服务器发送
服务端写入缓冲
NFS 服务器在处理 WRITE 请求时,必须将数据提交到稳定存储(持久化设备)后,才能向客户端返回成功。
如果服务器将数据缓存在内存中就返回成功,一旦服务器崩溃并在重启后接收后续写入,可能会导致文件内容出现新旧数据交错的损坏。因此,快速写入持久化存储是 NFS 服务器性能优化的关键。