摘要

  • NFSv2 要求修改操作在稳定存储后返回,客户端因而可以丢弃副本;这有利于无状态服务器恢复,却把持久介质延迟放进每次写入。
  • NFSv3 把请求和结果分成 UNSTABLE、DATA_SYNC、FILE_SYNC 三档,回复还带实际写入长度与不透明的 write verifier。
  • 客户端要保留未稳定数据,直到 COMMIT 或更强回复完成责任;若 verifier 改变,就必须把旧实例下的不稳定数据视为可能丢失并重传。

成功之后,缓冲区为何还不能清空

设想客户端向远端文件写入一段数据,收到 NFS3_OK。若只看最外层状态,这次操作已经结束。但回复里的 committed 可能是 UNSTABLE。网络调用成功、服务器接收了某些字节,与这些字节已经能熬过服务器故障,是三件不同的事。

1989 年的 RFC 1094 描述 NFSv2 时,修改操作采取同步语义。WRITE 返回后,客户端可以假定数据已经进入稳定存储,并丢弃自己保存的内容。这与无状态服务器理念相配:服务器崩溃后,客户端重试请求即可,不必恢复一套复杂会话状态。

代价也很直接。每次写入都要等待持久路径。1995 年的 RFC 1813 把这称作 NFSv2 的写吞吐瓶颈,并把“安全的异步写入”列为 NFSv3 的主要性能变化之一。所谓安全,不是提前宣布持久,而是保留足以完成或重做写入的证据和副本。

三档结果把一句 OK 拆开

WRITE 请求的 stable 字段可取 UNSTABLE、DATA_SYNC 或 FILE_SYNC。FILE_SYNC 要求数据和全部相关文件系统元数据在返回前进入稳定存储;DATA_SYNC 要求数据及足以重新取回数据的元数据完成持久化;UNSTABLE 则允许服务器在回复前持久化全部、部分或零字节。

回复不会机械复述请求。它用 committed 报告实际达到的级别。服务器若有快速非易失介质,可以在客户端只要求 UNSTABLE 时回报 FILE_SYNC,让客户端更早结束保管;但不能低于请求要求。判断后续责任时,真正有证据意义的是返回值。

回复里还有 count。服务器可以只写入请求的一部分,只要报告实际数量;客户端必须另发 WRITE 补齐。因此,成功状态首先只覆盖已计数的范围。若把请求长度当成完成长度,再正确理解持久级别也救不了缺失的尾部。

协议由此把容易混淆的命题逐项编号:调用有没有成功;多少字节被写入;这些字节达到哪一级持久性;剩余责任由谁持有。速度不是靠删掉问题取得,而是靠允许答案分时到达。

verifier 证明的是时期,不是文件

成功回复最后带有 verf,即 write verifier。它是一段不透明 cookie,在同一相关服务器实例中应保持一致;若进入可能丢失未提交数据的新实例,就应改变。重启是最常见原因,但规范关心的是保管易失数据的那段服务器状态是否中断,而非某个特定开关动作。

它不是内容哈希。两个完全不同的文件、多个写入范围,都可能在同一实例内共享 verifier。相同值不能证明文件没变、写入内容正确、没有并发写者,甚至不能代替 count 说明写了多少。它只回答一个狭窄问题:后续动作是否仍可依赖曾保管那些未提交字节的同一服务实例。

客户端因而新增一种缓冲状态。RFC 1813 把三种状态概括为 dirty、done but needs to be committed、done。中间那一格最能说明设计变化:传输已经做完,持久责任仍未结清。客户端必须继续保存可以重发的数据。

COMMIT 把延迟的责任拉回前台

COMMIT 要求服务器把此前以不稳定方式写入的修改数据推入稳定存储。它可以指定范围;offset 为零且 count 为零时,从文件开头一直处理到末尾。它类似远端、可按范围执行的 fsync,但这个类比不能扩张成内容正确、备份完成或应用已消费的证明。

成功的 COMMIT 也返回 verifier。客户端把它同原先 WRITE 的值比较。相同值让两阶段仍可归属于同一实例;不同值则撤销旧实例对易失数据的保管证据。客户端必须把旧 verifier 下、以不稳定级别返回的数据当作可能已经丢失,并重新发送。

“可能”二字很重要。服务器故障前,也许已有部分数据被后台刷新到磁盘。verifier 改变并不证明全部丢失,只是客户端无法再安全分辨哪些仍在。用已知 offset 重写,是在证据不足时选择可恢复行动,而不是伪造精确观察。

反过来也一样:值没有改变,不等于文件内容被验证。还需检查 COMMIT 状态、实际长度、覆盖范围,以及多客户端协调。实例连续性只是证据链的一环。

稳定存储并非永不失效

RFC 1813 给稳定存储划出一个实际边界:它应经受反复断电、若干硬件故障以及包含重启的反复软件崩溃。但文本紧接着排除稳定存储模块自身的失效。因此,这个词不是副本、异地灾备、逻辑一致性或绝对永久性的代称。

同一规范还明确不提供客户端与服务器缓存之间、或多个客户端缓存之间的严格一致性。某一范围被 COMMIT 持久化,仍不能说明应用选择了正确版本,或另一写者没有覆盖。持久性解决“故障后还在不在”,一致性解决“大家看到的是不是同一个合适状态”,两者不能共用一个绿灯。

这个机制后来继续存在。RFC 3530 把它带入 NFSv4;RFC 7530 保留请求级别、返回级别和后续提交;RFC 8881 更明确规定,write verifier 改变时,客户端必须假定旧值下返回为 UNSTABLE4 的数据已经丢失并恢复。其他状态机制增加,并没有取代这一实例边界。

性能收益其实是一笔责任转移

NFSv3 缩短正常写入延迟,是因为服务器可以把物理持久化合并到稍后的批次中。但被移出的工作并未消失。客户端用内存保存数据,用范围表追踪责任,并在实例变化后承担重传。一次故障可能把长期积累的不稳定队列同时变成恢复流量。

这说明异步不是免费。它把每次写入的等待换成条件性债务。若只按平稳期吞吐配置系统,却不给客户端内存、网络和后端预留恢复余量,性能提升便是向故障时刻借来的容量。

还要注意,客户端自身也会故障。规范承认它可能在发出后续 COMMIT 前崩溃。服务器能够提供可恢复路径,不等于每个调用者都会走完该路径。因此,应用若把未提交回复当成最终业务完成,便跨过了协议没有替它跨过的边界。设计需要明确哪一层负责等待、刷新,以及在关闭文件或交付结果前要求哪一种证据。