ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

NFS与SMB对比:Linux和Windows文件共享的选型与最佳实践

NFS与SMB对比:Linux和Windows文件共享的选型与最佳实践 不少人跟我反映过这样一个现象公司里明明有 Linux 服务器也有 Windows 桌面结果共享文件的时候反而最痛苦。有人图省事让 Linux 服务器去挂 Windows 的 SMB 共享结果小文件排队写入慢到怀疑人生数据库文件更是隔三差五出现锁异常。也有人反过来非要在 Windows 上挂 NFS结果映射过来全是 nobody权限栏一看全是问号最后只能靠 chmod 777 强行凑合。这份纠结的背后其实是 NFS 和 SMB 两套协议从设计基因上就走的完全不同的路。NFS 生下来是给 Unix/Linux 用的SMB 生下来是给 Windows 用的。这不是夸张而是底层协议、权限模型、锁语义、缓存机制共同决定的事实。本文就基于这几年的实际运维和搭建经验把这些差异拆开讲清楚为什么建议 Linux 用 NFS、Windows 用 SMB混用会出现哪些“活见鬼”的问题以及混合环境里到底该怎么做才靠谱。1. NFS 和 SMB 的设计起点这俩协议本就不是一个物种很多人在选型的时候只看功能清单总觉得“共享文件嘛能挂上能用不就行了”结果挂载是挂上了用起来全是坑。要理解这些坑最好先花几分钟搞清楚 NFS 和 SMB 是从哪个年代、为哪种场景设计出来的。1.1 底层通信模型差距无状态 RPC 与有状态会话NFS 诞生于上世纪八十年代的 Unix 环境它的通信基础是 Sun RPC。RPC 协议本身是无状态的意味着客户端每次请求服务端都当成一个全新的调用中间断开了、超时了只要网络恢复就能继续服务端不需要维护“客户端的当前状态”。这种设计在现代局域网里看起来有点原始但在那个网络不稳定、主机频繁重启的年代是一种非常务实的选择。NFS v3 时代挂载全靠 portmapper/rpcbind 动态分配端口NFS 服务端一般要固定 111、2049 等端口否则搞个防火墙都会让你怀疑人生。SMB 的出身则是另一套逻辑。它最早源于 DOS 和 OS/2 时代的网络重定向器由微软一路改进到今天的 SMB 3.1.1。SMB 是典型的“有状态会话”协议客户端和服务端要先建立会话然后在会话内建立树连接tree connect再针对具体文件做打开、读、写、关闭。每一步都依赖服务端记住上下文。这个模型的好处是控制力强能够精准地管理每个文件的打开状态、访问权限和字节范围锁但也意味着对会话的稳定性要求高。一旦会话中断很多应用会直接报错因为文件句柄已经在服务端被清掉了。1.2 权限模型POSIX 世界观与 ACL 世界观的冲突除了通信模型权限模型的差异才是最让人头疼的。NFS 天然对齐 POSIX 权限也就是 Unix 风格的用户 IDUID、用户组 IDGID和 rwx 权限位。Linux 服务端导出一个目录客户端用什么 UID/GID 访问服务端就按这个 ID 去判定权限。比如你在客户端用 UID 1000 打开了文件那么服务端看到的就是 UID 1000 的用户在访问。这种方式的优点是简单直观特别适合账号体系统一由 LDAP、NIS 或 SSSD 管理的 Linux 集群。但到了 Windows 环境这套逻辑就完全不对劲了。Windows 本身用的是安全标识符SID、访问控制列表ACL和共享权限和 POSIX UID/GID 之间没有天然映射。Windows 自带的“NFS 客户端”功能虽然能用但默认情况下如果服务器返回的 UID/GID 无法和本地用户对应Windows 客户端往往会把所有文件全部映射成匿名用户或者统一映射成一个指定 UID。最直接的后果就是你在 Windows 上改文件权限Linux 端看到的权限位可能是错的你在 Linux 上把目录设成 755Windows 端可能压根连写都写不了。SMB 则反过来。SMB 从设计上就围绕 ACL、用户凭据和域控认证Windows 的 NTFS 权限可以精确到单个用户的读、写、修改、删除设置界面里那一大堆勾选项不是摆设。Linux 端的 Samba 也可以配置成使用 LDAP/AD 后端把 SID 映射成 Unix UID实现 ACL 兼容但这套配置比纯 NFS 导出复杂得多而且很多默认配置下权限映射经常出错。1.3 在自家平台是“原生”跨了平台就是“翻译”归根结底NFS 和 SMB 都做了大量针对自家操作系统的优化也都通过不同程度的技术手段“翻译”对方的语义。但翻译这件事永远是会丢信息的。NFS 在 Linux 上由内核原生支持客户端直接走内核的 VFS 层缓存、并发、读写路径都很简洁。SMB 在 Linux 上也能通过内核 CIFS 模块挂载底层也是内核态但处理文件锁、ACL、文件属性时仍然要走 SMB 协议那一套表现总归会打折扣。Windows 同理Windows 对 SMB 的支持已经深度集成到系统服务、组策略、故障转移集群和硬件卸载里而它对 NFS 的支持则更像一个功能阉割的“外来户”很多高级特性完全无法直接使用。我自己的感受是选协议本质上就是在选“哪个系统更舒服”。既然 Linux 和 Windows 都是业界的常见系统与其让双方硬凑在同一套协议上互相翻译不如各自用自己最擅长的方式访问同一份数据。2. 实际环境里混用出现的“玄学”问题与排查链路协议差异不是理论书上的概念是会在线上环境直接给你制造麻烦的故障源。我举几个真实遇到过的场景这些问题的排查链路写得尽量完整你们以后遇到类似的可以直接照着走。2.1 Windows 挂 NFS权限全变 nobody 的经典现场曾经有一套 Ubuntu 服务器作为文件服务器导出了共享目录给虚拟机集群用一切正常。后来有个同事临时想从 Windows 电脑直接访问其中的一个目录我图省事给 Windows 10 添加了“NFS 客户端”功能然后用mount -o anon 192.168.1.10:/srv/share Z:挂载。挂载本身没有报错但打开目录一看文件所有者全部变成了 nfsnobody权限全是 777 或者 655任何修改操作都被拒绝。排查链路是这样的先看 Windows 端挂载参数。mount命令不带uid和gid参数时Windows NFS 客户端默认会尝试把 Windows 当前用户映射成 UNIX 用户但如果服务器端启用了 root_squash而且客户端映射不到具体用户就全部落入匿名账户。再到 Ubuntu 服务端看/etc/exports确认导出参数是不是带了no_root_squash、insecure之类的选项。最后发现根本问题在于 Windows 用户账户没有对应的 UID/GID 映射表。除非你在服务器上手动配置匿名 UID/GID或者在 Windows 端用/map参数指定映射否则权限一定乱。这个问题在十几台机器上当玩具玩玩还能忍一旦规模上到几十号人你会发现自己所有时间全部浪费在“给张四改权限”上。我说句实话Windows 上真不适合用 NFS 做正经共享够折腾但收益极低。2.2 Linux 挂 SMB锁与元数据“水土不服”反过来Linux 挂载 SMB 同样会出问题。项目里有一个需求是让 Linux 构建服务器从 Windows 文件服务器拉取代码还要往 Windows 共享目录里写构建产物。于是我用 CIFS 挂载mount -t cifs //192.168.1.20/share /mnt/share -o usernamebuild,passwordxxx,vers3.0,uid1000,gid1000挂载和读写都能通但很快发现几个现象代码目录里.git仓库经常出现.git/index.lock残留偶尔还会报“无法创建临时文件”之类的错误。这是因为 Git 操作大量依赖原子性文件重命名而 SMB 协议在处理 rename/unlink 时的语义和本地 POSIX 文件系统存在差异加上文件锁的粒度和方式不同导致并发操作时冲突率明显上升。使用rsync同步大量小文件时速度忽快忽慢有时候直接卡住几秒才继续。这通常是 Windows 服务端侧的 SMB opportunistic lockoplock机制造成的缓存延迟。用df -h看到的总空间和剩余空间偶尔不对因为 CIFS 对空间属性的查询依赖服务端返回Windows Servidor 的 SMB 实现和 Linux 端预期存在偏差。排查这类问题不能一上来就调参数而是按这个顺序来看确认挂载协议版本SMB 1.0 在跨平台场景下基本可以排除至少要 2.1 或 3.0。检查是否启用了nobrl或noserverino这些参数会影响文件锁和 inode 编号行为。看服务端缓存设置Windows 共享的“脱机文件”策略也会影响 Linux 客户端的写入延时。这些问题的本质不是“某个参数调坏了”而是协议语义不一致。你在 Linux 上想当然地认为rename是原子操作但 SMB 流经 Windows 服务端后实际的实现可能和你预期的完全不同。2.3 一体机扫描到 SMB 共享失败必须重视协议版本搜索记录里不少人遇到“mf6100扫描文件smb传输失败”这其实就是一支典型的“设备混用 SMB 共享”问题。一体机本身不是 Windows 也不是 Linux但它通过 SMB 协议把扫描文件推送到你的共享目录。很多老款设备出厂时只实现了 SMB 1.0而现代 Windows 10/11 默认已经禁用 SMB 1.0于是扫描结果要么失败要么一直提示“服务器无响应”。排查链路先确认 Windows 是否已禁用 SMB 1.0。PowerShell 里执行Get-SmbServerConfiguration查看EnableSMB1Protocol是否为 False。如果设备只支持 SMB 1.0最简单的方式不是重新打开 SMB 1.0那玩意有严重安全隐患而是检查设备是否有最新固件很多品牌更新后就能支持 SMB 2.0/3.0。实在不能升级固件的可以考虑换 FTP 或 WebDAV 通道来接收扫描文件避免直接依赖 SMB 1.0。设备侧还要检查“使用存储的凭据”是否有特殊字符问题有些一体机对密码里的逗号、竖线处理不好会导致认证成功但目录枚举失败。这个例子说清楚了SMB 的“版本兼容”在跨平台、跨设备时非常重要。别默认所有支持 SMB 的设备都支持同一个 SMB 版本。2.4 排查思路先判断是协议层还是应用层我处理共享问题多了以后形成了一个固定的排查习惯先判断故障发生在协议层还是应用层。协议层面的症状通常有挂载不上、权限全是匿名、目录列表为空但用 net use 能看到盘符、复制大文件中途断开。这类问题优先去查网络端口NFS 查 111/2049SMB 查 445、防火墙规则、服务端是否启用了对应服务、协议版本匹配。应用层面的症状则更像文件能正常读写但某个应用打不开文件、日志文件不能平滑轮转、数据库锁冲突、Git 操作失败。这类问题优先查锁语义、缓存、文件属性映射。一个文件共享问题90% 能在协议层找到根因剩下 10% 才是应用层代码写得有问题。分清楚这一点你排错的时候就不会像无头苍蝇一样乱抓。3. 标准做法实操Ubuntu 挂 NFS 与 Windows 挂 SMB 的完整配置既然结论是 Linux 用 NFS、Windows 用 SMB那这里就给出最常用的两套配置模板。配置是死的但参数为什么这样设是活的我会在关键参数上解释原因。3.1 Ubuntu 服务端 NFS 导出配置假设你的 Linux 服务器要导出一个工作目录比如/srv/sharesudo apt install nfs-kernel-server sudo mkdir -p /srv/share sudo chown nobody:nogroup /srv/share # 或者根据业务指定 UID/GID然后编辑/etc/exports/srv/share 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)这几个参数的解释很关键rw表示可读写。如果不加默认是只读。sync强制服务端在响应客户端写请求前先落盘。NFS 默认可能用异步模式性能更好但一旦服务器宕机容易丢数据。我建议数据敏感场景必须加 sync。no_subtree_check禁用子树检查。子目录重命名频繁的场景里开启子树检查会提高出错概率直接关掉。no_root_squash允许客户端 root 用户保留 root 权限。这个很危险只在互相信任的内网环境使用。默认root_squash会把 root 压制成 nobody反而更安全。应用配置sudo exportfs -ra sudo systemctl enable --now nfs-server sudo systemctl status nfs-server之后在 Linux 客户端上挂载sudo apt install nfs-common sudo mkdir -p /mnt/nfs sudo mount -t nfs 192.168.1.10:/srv/share /mnt/nfs如果需要开机自动挂载写入/etc/fstab192.168.1.10:/srv/share /mnt/nfs nfs rw,sync,noatime,rsize1048576,wsize1048576 0 0rsize和wsize默认已经很大一般不用手调但如果你的网络设备 MTU 不是 1500 而是 9000Jumbo Frame可以适当调整以获得更好的吞吐。3.2 Windows 访问 SMB 共享的配置Windows 访问 SMB 共享基本就是“开箱即用”不需要额外安装服务。推荐用 UNC 路径直接访问\\192.168.1.20\share如果需要把它映射成盘符在文件资源管理器地址栏输入共享路径右键“映射网络驱动器”记得勾选“使用其他凭据连接”然后输入一个有权限的账户。如果想每次开机自动连接不建议随便用net use配合批处理因为凭据很容易失效。最好在“凭据管理器”里把目标服务器的凭据保存好这样 Windows 会自动尝试连接并完成认证。如果想要持久化和稳定性更可控可以用 PowerShellNew-SmbMapping -LocalPath Z: -RemotePath \\192.168.1.20\share -Persistent $truePersistent参数表示持久连接这是 Windows 10/11 上实现“开机自动恢复网络驱动器”更可靠的方式。传统批处理net use在接入网络比较晚的情况下经常失灵而New-SmbMapping配合系统服务启动顺序能好很多。另外如果这台 Windows 是服务器且要对外提供 SMB 服务顺手做好三件事在“启用或关闭 Windows 功能”里确保 SMB 1.0/CIFS 文件共享支持是关闭的。启用 SMB 加密或签名在组策略或 PowerShell 里设置。不要把自己服务器直接暴露在不信任的网络上445 端口是黑客重点照顾对象必须加访问控制。3.3 同一份数据两种协议给不同系统看到这里可能有读者要问我公司里既有 Linux 也有 Windows难道要维护两份共享目录吗当然不用。更合适的做法是让一台存储服务器Linux 或 NAS 网关同时提供 NFS 和 SMB 导出但导出的可能是同一物理目录。Linux 上安装 Samba 之后一方面把目录通过 NFS 导给 Linux 客户端另一方面通过 Samba 的 SMB 导出给 Windows 客户端。每个客户端都用自己最亲的协议去访问而文件锁、权限等方面的“翻译”由服务端集中处理。诚然这并非零成本Samba 的权限映射、文件锁兼容性也需要调优但总比你让 Windows 直挂 NFS、或者让 Linux 直挂 SMB 要稳得多。这里特别提醒不要让两份协议同时毫无隔离地往同一个数据库文件或代码仓库目录里写。跨协议的并发写入是最容易触发文件锁问题的场景。如果一定要共用建议做好目录级的职责划分比如 NFS 管理代码目录、SMB 管理办公文档目录互不交叉。这也是“不要混用”这句话的真实含义——不是不让你同时启用两种协议而是让每个客户端各走各的、能不同时就不同时。3.4 虚拟化与容器场景里的“顺路抄近道”现在很多环境都涉及 Docker、虚拟机和 WSL这些场景共享文件的方式更值得注意。WSL2 是一个典型例子。WSL2 里的 Linux 发行版本身是跑在虚拟化层上的它的文件系统在 Windows 里通过\\wsl$访问。很多人图方便在 WSL2 里挂了 Windows 的 SMB 盘或者反过来在 Windows 里直接操作 WSL2 的 ext4 目录结果发现大文件处理起来极慢。原因在于跨虚拟化边界的文件访问性能本来就不如本地文件系统。正确做法是大型仓库和数据文件放在 WSL2 内部或 Linux 远程服务器上用 NFS 访问只需要在 Windows 和 WSL2 之间传送少量文件时才走\\wsl$或跨协议挂载。Docker 的情景也类似如果是 Linux 主机上的 Docker需要共享存储时优先考虑 NFS 卷插件如果是 Windows 容器优先用 SMB 全局挂载。项目的文件存储不要搞成“容器里是 Linux数据盘却挂在 Windows SMB 上再映射给容器”这种路径一长锁和权限就会出现各种难以解释的问题。4. 性能、锁与缓存高性能共享时不能犯的错文件共享如果只传几个文档什么协议都差不多但一旦涉及数据库、代码仓库、虚拟机镜像这些高性能业务协议选型就成了一道生死题。4.1 文件锁语义数据库类的“重灾区”数据库文件是不能放在普通 NFS/SMB 共享上的至少不能随便让多个客户端并发打开同一个文件。很多人不理解为什么这里说明白数据库需要字节范围锁、强制锁能力而且对锁的可靠性要求极高。SMB 在 Windows 上的字节范围锁做得比较成熟Windows 文件服务器配合 SQL Server 的集群部署是有成熟方案的。但 Linux 客户端的 CIFS 挂载在应对某些复杂锁语义时仍然存在差异。NFS v3 时代的锁通过 NLM 协议实现如果一个客户端崩溃锁可能需要等超时才能释放这会导致数据库误以为锁还在。NFS v4 引入了基于租约的锁模型比 v3 好很多但依然不能等同于本地文件系统的锁。如果你必须把 MySQL/PostgreSQL 的数据文件放到网络共享上我建议优先选择 NFS v4.2 服务端并且客户端挂载参数里不要乱开nocto或actimeo0。更稳妥的实践是数据库数据文件放本地盘网络共享只放备份、归档或静态资源。这样做不是某套协议不行而是网络文件系统本身的延迟和锁语义决定了它不适合承载高并发数据库写入。4.2 小文件与大文件的表现差异很多性能问题不是“慢”而是“不均匀”。IMAP、代码仓库、照片库这类小文件密集型负载网络共享的性能受元数据操作影响很大。NFS 在 Linux 内核里对元数据缓存做得很成熟attr缓存配合actimeo参数小文件操作可以做到接近本地盘。SMB 在 Windows 上的表现也有类似的缓存机制Windows 对 SMB 网盘的“文件夹视图刷新”做了大量优化。可要是交叉使用就开始错位了。比如你在 Windows 上用 NFS 打开一个含几万个文件的目录资源管理器会卡到让人以为系统死机了因为 Windows Explorer 习惯于扫描大量文件属性、生成缩略图而 NFS 上的元数据查询延迟比 NTFS 高得多。反过来Linux 上用 CIFS 跑find或grep -r面对大目录树同样会显得格外吃力。所以你会发现这个问题的关键不在于谁绝对快而在于原生协议能更充分地利用客户端本地操作系统的缓存特性。4.3 缓存与延迟为什么代码仓库更适合 NFS 而不是 SMB代码仓库的场景比较特殊。Linux 的构建机、CI 节点访问共享代码库时大量操作是“小文件 rename 锁检查”典型的如git pull、npm ci、mvn compile。我个人实测下来的体验是Linux 构建机挂 NFS 的稳定性明显高于挂 CIFS 的 SMB 共享。不是因为 CIFS 挂载速度慢而是因为 Git 这类工具对 POSIX 文件语义硬链接、重命名、inode 稳定性的依赖太深SMB 在这方面的适配层会额外引入不确定性。举个例子同一个 Git 仓库我用 NFS 挂载到 Linux 上执行git checkout几十个分支切换基本没有大问题但同样的仓库放到 SMB 挂载上偶尔就会出现fatal: Unable to create xxx/.git/index.lock: File exists.这种问题。原因可能是上一次操作还没来得及释放锁文件或者客户端缓存引发的时间窗错位。虽然可以通过增大 oplock 超时、禁止索引锁竞争来缓解但根治方法还是别让 Git 仓库走 SMB。代码编译缓存如node_modules、.m2仓库也是同理。这些目录里成千上万的小文件NFS 在 Linux 的缓存和目录项缓存能显著提高效率而 SMB 跨协议访问时客户端必须频繁和服务端确认目录变更信息慢就是必然的。4.4 关于“Windows 无法安装到 NFS 分区”的误传搜索词里有一句特别有意思“windos必须安装在格式化为nfs的分区windows无法安装到这个硬盘空间分区是一个”。我一看就知道这不是 NFS而是NTFS。Windows 安装程序要求启动分区必须是 NTFS 格式如果你拿一个 ext4、XFS 或未格式化分区去装 Windows它就会提示“Windows 无法安装到这个硬盘空间。分区必须是 NTFS”。这其实是文件系统不匹配不是 NFS 协议的问题。这个误传之所以常见是因为“NFS”和“NTFS”读音太像、拼写太像。遇到这种情况解决问题的方式不是安装什么 NFS 驱动而是重新分区、把目标分区格式化成 NTFS或者用 Windows 安装程序自带的“删除分区-新建分区”操作。反过来说如果你要给 Windows 装在一块已经格式化成 Linux 文件系统的磁盘上Windows 安装器不会帮你自动转换你必须主动删除或格式化该分区这个过程会清空数据操作前一定做好备份。这个乌龙也提醒我们文件系统、协议、分区格式是三件不同的事。NTFS 是 Windows 本地文件系统NFS 是跨网络文件共享协议SMB 也是网络共享协议。有的朋友会把“Windows 访问不了 Linux 的 ext4 分区”归咎于“没有装 NFS”其实它们之间根本没有直接关系。5. 我的选型结论和一套可复制的决策清单写到最后我把这些年形成的选型逻辑总结成一套可以直接套用的决策方式。5.1 三种典型环境的协议选择环境类型共享服务端客户端推荐协议原因纯 Linux 集群Linux/NASLinux 服务器、容器节点NFS v4内核原生支持、POSIX 语义完整、权限映射简单纯 Windows 网络Windows Server/NASWindows 桌面、服务器SMB 3.x与 AD 域控、ACL 权限、组策略无缝集成异构环境Linux 或 NAS 网关Linux Windows 共存服务端同时导出 NFS 和 SMB客户端各用各的避免跨协议语义翻译损耗权限和锁由服务端统一管理5.2 哪些场景可以“例外”说了这么多别混用也不是一棍子打死。有些场景确实只能“凑合”临时传几个文件无所谓用哪个都行。Linux 服务器要从 Windows 共享目录里拉取发布包属于一次性操作用 CIFS 挂载解决没问题。Windows 电脑想访问家里 Linux 服务器上的媒体文件这时候 SMBSamba反而比 NFS 好用因为 Windows 媒体播放器对 SMB 的支持更完善还支持 DLNA。打印机、扫描仪、摄像头这类设备很多只实现了 SMB那就按照设备的要求走但务必确认协议版本不是陈旧的 SMB 1.0。这些场景的共同点是“低强度、短生命周期”。一旦共享变成核心基础设施承载业务数据、代码仓库、构建工件就不该抱着“能把文件传过去就行”的心态凑合了。5.3 一个排错与后备方案清单如果你已经把 NFS 和 SMB 混用了而且已经出现了各种诡异现象按这个清单来处理先定位是谁的问题到出问题的客户端上用另一个协议挂一次同目录如果问题消失基本可以确定是协议选型问题。确认协议版本Linux 上mount命令加vers4.2或vers3.0Windows 上检查Get-SmbConnection。检查权限映射NFS 看 UID/GID 是否一致SMB 看用户名是否有效、ACL 是否继承。关闭不必要的缓存NFS 挂载加noac或调低actimeo测试SMB 挂载加noserverino测试。如果关掉缓存后问题消失说明是缓存一致性问题至少知道往哪个方向调。拆分职责目录给每种协议划定独立的子目录避免同一个文件被两种协议并发操作。最后才考虑重新架构如果问题反复出现且无法通过参数调整规避那么狠下心把文件服务器改造成同时提供 NFS 和 SMB 导出的网关强制各客户端使用自己的原生协议。这套方案绝大多数情况都能兜住底。最怕的反而是“明知道协议不适配仍然用各种玄学参数去补”结果参数越调越多故障却依然随机出现。协议选型是地基参数优化是装修地基歪了刷多少层漆都站不稳。我个人在这些年的实践中最大的体会一直是文件共享这件事不是非此即彼而是各归其位。NFS 和 SMB 都是优秀且成熟的协议真正出问题的永远是它们的错配。让 Linux 干 Linux 擅长的事让 Windows 干 Windows 擅长的事共享存储会老实很多。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进