摘要
- ONC RPC 在每个 CALL 开头放置 32 位 XID,并让 REPLY 原样带回。客户端由此找到等待该结果的本地调用,服务器也可以按相等性识别一次可能的重传;但 XID 不是序列号,更不是“只执行一次”的证明。
- NFS 把这条界线变成可见的运行风险。NFSv3 的易失重复请求缓存只能提供有限时间的记忆;NFSv4.1 用有界会话槽位、逐槽 sequence ID 和缓存回复,明确承担更强执行语义的存储与恢复成本。
一次超时分裂出两段同样合理的历史
客户端请求服务器删除一个名字。服务器可能已经完成删除,却在返回成功结果时丢了回复;也可能请求根本没有到达。客户端看到的都只是等待超时。
重发是恢复通信最自然的动作,却不是恢复事实。第二份 CALL 到达时,服务器面对两个问题:这是新的删除命令,还是第一次命令的复制品?如果重新执行,可能把一个副作用做两遍;如果一律压掉相同请求,又可能拒绝稍后确实需要发生的新操作。
1988 年 4 月发布的 RFC 1050 已经把可靠性排除在 RPC 消息格式之外。两个月后取代它的 RFC 1057 把后果写得很克制:在 UDP 上超时重传而仍无回复,调用者无法判断过程执行了多少次;收到回复,也只能推断至少执行过一次。
这里缺少的不是更多等待时间,而是执行边界的历史。请求可能在到达前丢失,回复可能在执行后丢失,连接也可能在副作用发生之后断开。三种情况在客户端都表现为沉默。
XID 只承担了较小、也更可靠的职责
RPC version 2 的每条消息都以一个无符号 32 位 XID 开头。REPLY 复制触发它的 CALL 的 XID。客户端同时发出多个调用时,可以据此把每份返回结果交给正确的等待者。
这是一种消息相关性,不是时间顺序。RFC 5531 明确允许服务端比较两个 XID 是否相等,用来发现可能的重传,同时禁止把它当成序列号解释。数值较大不表示更晚,更相邻也不表示过程连续。
基础规范留下的是协作选项。客户端重传时可以复用此前的 XID;服务器执行后可以记住该值,下一次见到相同值时不再执行。两项选择加上尚未丢失的服务器记忆,可以实现“一定程度的至多一次”。四个字节本身没有这份能力。
客户端若给重试换一个 XID,服务器无法按相等性把两份调用连接起来。服务器若清掉旧记录,同一个 XID 也查不到历史。编号以后被重新使用时,如果没有调用方、程序、版本、过程与存续期,相等甚至可能把两件不同的事错认成一件。
收到可靠回复也不是跨故障提交凭证
RFC 1831 延续早期规则,RFC 5531 又把这套协议整理为 Standards Track。它们说,在可靠传输上收到回复,可以在该模型内推断过程恰好执行一次;但如果没有收到回复,调用者仍不能断定过程没有执行,即便使用 TCP,也要为服务器崩溃准备超时与重连。
这句话的边界是已经完成的交换。TCP 能维护一条连接内的字节顺序,不能替已经消失的应用进程保存执行账本。重连以后,新连接也不知道旧过程是否在断开前完成了不可逆的状态改变。
XID 同样不是认证。RPC 为 credential 与 verifier 留有其他字段。相同编号只说明本地状态把回复归到某个调用;它不签名内容,不证明发起者身份,也不说明结果已经写入稳定存储。
NFS 的无状态选择把模糊性变成文件风险
早期 NFS 希望服务器尽量不保留客户协议状态。RFC 1094 给出的收益很直接:服务器或网络恢复后,客户端只需继续重试,无须重建复杂会话。
为了承受重复,操作要尽可能设计成幂等。同一区间的读,或者写入同样的内容,重复执行常能得到等价效果。但 RFC 1094 仍把 REMOVE、RENAME、LINK、MKDIR 等操作标为可能非幂等。一个名字只能被第一次删除;第一次成功的重命名,第二次可能因为源名字已不存在而失败。
幂等是对副作用的约束,不是对历史的发现。RFC 1813 在 NFSv3 中进一步指出,重复执行非幂等请求可能造成破坏,例如再次截断可能抹掉第一次调用之后产生的写入。即便 RPC 跑在面向连接的传输上,连接断开并重建后,客户端仍可能重发一份结果未知的调用。
因此,“同一个请求”必须成为服务器可执行的判断,而不能停留在客户端的愿望。判断至少要包含请求方、具体过程、XID、参数上下文以及此前结果。
重复请求缓存购买的是一段时间
许多 NFSv3 服务器维护近期请求缓存。第一次操作完成后,服务器保存结束状态;在记录仍然存在时收到重复请求,就返回原结果,而不是再做一次副作用。
这份缓存不只是性能优化。它让执行者而不是猜测者掌握重复的裁决依据。但 RFC 1813 同时说明它的失败窗口:缓存通常放在 RAM,服务器崩溃就会消失;容量有限,长期网络分区可能让旧记录在客户端收到回复前被淘汰。重试稍后抵达,看起来又像新请求。
所以,XID 不能单独叫作持久幂等键。真正控制承诺的是查找范围、缓存大小、淘汰规则、故障转移时是否共享,以及结果能否跨重启保存。编号没有失败;保存编号及其后果的环境先失效了。
NFSv3 的 exclusive CREATE 反而说明了更强证据应放在哪里。它把 verifier 与所创建对象的状态关联,因为普通易失缓存不足以守住这项操作的语义。该机制没有把所有 XID 升格,只为一个需要更强承诺的过程增加了贴近对象的记忆。
会话槽位把无限历史改造成有限责任
NFSv4.1 换了一种结构。RFC 5661 引入 session;现行 RFC 8881 为每个会话定义有限的 slot table,每个槽位保留自己的 sequence ID 和当前请求的缓存回复。
请求方先选一个没有在途调用的槽位。第一次使用有规定的序列值;在该槽位发起新请求时序列递增;重传当前请求时则重复当前序列。应答方据此区分三种状态:下一份新请求、当前请求的副本,或者乱序值。若原请求已经完成,相同序列的重试直接得到缓存回复。
槽位和序列同样重要。槽位上限约束并发,也约束服务器最多要保管多少份结果。下一序列到来,又向服务器表明请求方已经越过上一结果,旧缓存可以安全替换。
RFC 8881 专门对比了原始 XID 的困难。XID 对服务器是不透明的,RPC 调用可以乱序完成,也没有天然限制尚未完成的编号数量。若要为 32 位空间中的所有结果永久留档,成本不可接受。有界槽位把无限命名空间改成双方明确知道的当前保管义务。
更强的“一次”仍然有持久化边界
如果 slot table 与 reply cache 随重启消失,服务器仍无法为丢掉的记录作证。RFC 8881 因而指出,要把完整 EOS 延伸到服务器故障与恢复,相关回复缓存和恢复状态必须持久化。易失实现仍能改善正常重试,却不能宣称自己记得已经遗失的历史。
NFSv4.1 也没有取消 RPC XID。XID 继续负责 CALL 与 REPLY 的关联,包括不走普通 SEQUENCE 路径的交换。session ID、slot ID 与 sequence ID 增加的是执行记忆的范围,不是建立一个统管所有调用的全局编号。
这段演进没有神奇字段。XID 提供比较点;重复缓存为比较暂时保存后果;会话槽位限定需保存的并发结果;持久化再决定这项声明能跨越哪种故障。
一个相等值从来不能独自证明什么
相同 XID 不证明参数相同、身份相同、服务器实例相同或仍处在同一启动世代。缓存命中不证明副作用已经持久落地;缓存未命中也不证明请求是新的。所谓幂等操作还会遇到排序:RFC 8881 用延迟的旧 WRITE 覆盖较新 WRITE 的例子说明,重复相同写入本身无害,不代表两份并发副本可以任意完成。
合理结论要窄得多。XID 让运行中的端点拥有一个共同可比的标记。要说“只执行一次”,执行者还必须在重试不确定性存在期间,保存这个标记与具体工作、结果、调用范围和恢复世代之间的联系。
来源与证据边界
RPC 规则沿革见 RFC 1050、1057、1831 与 5531。NFS 的无状态目标来自 RFC 1094,重复请求缓存及其局限来自 RFC 1813。NFSv4.1 session 首见 RFC 5661,当前规则见 RFC 8881。这些资料能证明设计与记载的故障模型,不能证明当今每个产品的缓存容量、持久化策略、部署比例或合规程度。
会员简报
档案背景详情
使用相应会员等级登录,即可解锁完整简报与来源注释。
仅限 Strategic Circle
Strategic Circle
所有读者均可浏览。加入并登录后可解锁档案简报。
加入 Strategic Circle仅限 Leadership Alliance
Leadership Alliance
符合条件的 IP 资产所有者和管理层可登录查看 Leadership Alliance 简报。
加入 Leadership Alliance
