深蓝黑背景封面:左侧示意图——上方左右两张客户端卡:左边青色「Linux 计算节点 · NFS 带写缓存」,右边紫色「Windows 办公侧 · SMB 持有 oplock」,两卡之间一条虚线标注「互不可见」;两张卡各自箭头向下写入中间的「共享文件系统」框,框内一个文件图标标注 data.txt(同一文件);两条写入箭头交汇处有感叹号警示圈,下方标注「锁不互通 · 缓存脏读」。右侧大标题「同一个文件系统,挂两套协议」,副标题「NFS × SMB 混挂的一致性陷阱」;左上标注「面试经典 · 系统设计」,右上圆角胶囊标注「Day 08 · 11-15」 图 1:本文封面,两套协议的写入在同一个文件上交汇。

先说明属性:这是一篇经典题解析,题目出自本地历史面试资料,与任何公司的当前招聘无关。场景很常见:一台存储服务器上放着同一份数据盘,既用 NFS 挂给 Linux 计算节点,又用 SMB 共享给办公侧访问。问题是:同一个文件被两边同时读写,会出什么事?要答好这题,得先搞清楚两套协议各自怎么管锁、怎么管缓存。

NFS 一侧:锁是租约化的状态,缓存靠委托下放。

NFSv4(RFC 7530)把文件锁收进了协议本体:客户端可以对文件的字节范围加锁(byte-range lock),而且锁是有租约的——服务器为客户端持有的全部状态定义一个统一的租约期,客户端需要周期续租,租约过期不续,锁就可能被收回。这套设计是为了网络分区下的确定性:客户端掉线,锁不会永远挂死在那里。

更有意思的是委托(delegation):服务器可以把一个文件的读委托或写委托下放给某个客户端。拿到写委托的客户端被保证此刻没有其他客户端在读写该文件,于是可以放心地在本地缓存读写操作;一旦有别的客户端要做冲突操作,服务器通过回调路径通知原客户端收回委托。委托的本质,是把缓存一致性做成了协议内的一等公民。

SMB 一侧:锁和缓存更是绑在一起的。

SMB 的对应机制叫机会锁(oplock)。按 SMB2 协议文档,客户端在打开文件的 CREATE 请求里申明想要的 oplock 级别:二级(level II)、独占(exclusive)、批量(batch),新版本方言里还可以申请租约(lease)。机会锁不是客户端对服务器的命令,而是请求,由服务器视情况授予;它存在的意义,就是让客户端可以把数据缓存在本地,减少网络往返。当另一个客户端要打开同一文件时,服务器会「折断」(break)已有的 oplock:持锁客户端必须把缓存里的脏数据刷回服务器、降级自己的缓存级别,对方的打开才被放行。在 SMB 的世界里,「我能缓存什么」完全由「我持有什么锁」决定。

混挂的真正问题:两套机制互相看不见。

现在把两边拼到同一个后端文件系统上。NFS 服务进程维护的是 NFS 的锁表与委托,SMB 服务进程维护的是 SMB 的 oplock 表——它们是两个守护进程里的两套状态,底层文件系统通常并不负责把二者翻译成同一份视图。于是三个盲区依次出现。

第一,锁不互通。Linux 侧通过 NFS 拿到的字节范围锁,挡不住 SMB 侧客户端的写入,反过来也一样。两边都以为「我锁住了」,实际上谁也没锁住谁,两段写入的字节交错落盘,文件内容被撕成两半。

第二,缓存脏读。SMB 侧客户端拿着 oplock 在本地缓存了还没刷回的修改,NFS 侧读到的就是旧数据;反过来,NFS 侧拿到写委托后的本地修改,SMB 侧同样看不见。因为「收回委托」「折断 oplock」这类通知只在各自协议内部流转,跨协议的失效信号根本不存在。

第三,恢复语义断裂。断线重连、租约超时回收、oplock 降级,这些善后流程都是按单协议假设设计的;混挂之下,一边的恢复动作可能正踩着另一边未落盘的数据,破坏被放大。

深蓝黑背景,分两层。上层是混挂架构:左边青色卡「Linux 计算节点 · NFSv4 租约与委托」,右边紫色卡「Windows 办公侧 · SMB oplock 缓存」,中间圆角框「共享文件系统 · 数据盘同一文件」,左右双向箭头都标注「读写 · 缓存」,架构下方一行警示「两套协议的锁与缓存互不感知」。下层是三行对比表,列为「对比项 / NFS / SMB」:锁机制一行——NFS 用字节范围锁加租约(lease)、委托(delegation)下放客户端,SMB 用 oplock/租约加字节范围锁、缓存与锁绑定;缓存一致性一行——NFS 是 close-to-open(打开时与服务器对时、关闭才刷回),SMB 是拿到 oplock 可本地缓存读写、失效通知时才回写;混挂盲区一行——NFS 侧看不到 SMB 的 oplock 缓存照旧,SMB 侧感知不到 NFS 写入、可能读到旧副本。底部总结条:双协议客户端互不可见,锁与缓存各管各的,混挂写入必须有外部互斥或只读隔离 图 2:双协议混挂架构与锁、缓存语义对比。

工程上怎么落地才不出事。

结论不是「不能混挂」,而是「不能让两边对同一文件同时写」。常见姿势有三种。其一,角色隔离:一侧只读(比如办公侧只看产出的报表文件),写路径只走一种协议。其二,目录切分:NFS 写的目录和 SMB 写的目录物理分开,互不染指。其三,应用层互斥:必须双写时,由上层应用用自己的分布式锁串行化,并让每次写入都直通落盘,绕过双方客户端缓存。评审混挂方案时先问三个问题:锁谁管、缓存谁失效、恢复谁兜底——答不上来的设计就是定时炸弹。

回到面试现场,建议的答题结构:先分别讲清 NFS 的字节范围锁、租约与委托,以及 SMB 的 oplock 级别与折断机制;再指出根因是两套状态互不可见;最后给出锁不互通、缓存脏读、恢复断裂三类后果与对应的工程规避方案。你们的数据盘有没有类似的双协议共享场景?是靠只读隔离还是应用层锁扛住的?欢迎说说你们的做法。