1. 先说清楚:这不是二选一,而是场景匹配的问题
NFS 和 iSCSI 这个问题的热度一直很高。我见过不少人上来就问“哪个快”“哪个稳”,但真到了生产环境里,发现根本不是协议本身分高下,而是架构习惯、锁机制、权限模型和应用特性在替你选。
简单说:NFS 是文件级协议,你看到的是一个个文件,服务器帮你管文件系统;iSCSI 是块级协议,你看到的是一个裸盘,格式化、文件系统、分区全是你自己的事。这个本质差异,决定了后面所有关于性能、易用性、兼容性的讨论。
这篇文章主要写给三类人:一是做虚拟化平台运维的,二是搭 NAS / 文件共享服务的,三是在数据库或备份场景里纠结“我要不要上 iSCSI”的人。文章会讲清楚协议层面的差异,也会给出具体的选型步骤和配置命令。文末会附上我实际踩过的坑和排查思路,尤其是 Windows 装在 NFS 分区上那个老大难问题(后面会展开说),希望对你有帮助。
2. 两者到底在解决什么问题,核心机制拆开看
2.1 NFS:网络文件系统,天生就是“共享”
NFS 从诞生起就是为了让多台机器通过网络访问同一批文件。协议核心是把“文件”作为一个完整对象来传输,客户端发起 open / read / write / getattr 这类文件操作,服务器端的文件系统(本地磁盘、ZFS、LVM 等)负责真正落地。
NFS 的“状态”在过去是个值得聊的话题。NFSv3 是一个完全无状态的协议,每次请求都携带完整上下文,服务器不维护“这个客户端当前打开了哪些文件”的记录。好处是服务器崩溃恢复很快,客户端重试也简单,坏处是文件锁基本靠辅助机制(比如 Linux 下的 rpc.statd),而且服务器端对“谁在写哪个文件”几乎不加干涉。NFSv4 引入了有状态操作,把 OPEN/LOCK 这类东西做进协议里,锁语义比以前可靠得多,但代价是服务器要维护会话状态,断线重连的流程也复杂了一些。
模块和依赖方面:
- 传统 NFS 用的是远程过程调用,传输层通常跑在 TCP/UDP 2049 端口。
- NFSv4 支持 AD 域认证(Kerberos),这一条对 Windows/Linux 混合环境很友好。
- 现在主流的 NFSv4.1 还支持并行 NFS 扩展,可以把文件拆成多个数据段并发传输。
2.2 iSCSI:把网络当成硬盘线用
iSCSI 做的事情用一句话概括:在 TCP/IP 网络里模拟 SCSI 指令。SCSI 是硬盘/磁带时代的标准指令集,iSCSI 只是把 CDB(命令描述块)封装进 TCP 包里,发到另一端去执行。所以你的“盘”可以是服务器上的一个稀疏文件、一个块设备分区、或者一个完整 LUN。
因为客户端看到的是块设备,而不是文件,所有文件系统操作都在发起端(initiator)本地完成。客户端自己的操作系统负责格式化、维护目录结构、管理锁。也就是说,iSCSI 天生就适合“这块盘只属于一台机器”的场景,比如数据库裸设备、虚拟机系统盘、备份目标盘。
iSCSI 的关系模型里有两个核心角色:
- initiator:主动发起连接的那一方,通常是业务服务器。
- target:把本地块设备“导出”给 initiator 的那一方,通常是存储服务器或 SAN 阵列。
协议里还有 LUN 的概念,一个 target 可以映射多个 LUN,相当于给客户端一个“包含多块盘的柜子”。
3. 性能和开销:你看到的数字差异,其实是架构差异
3.1 NFS 的“文件级”开销到底花在哪
NFS 的每次文件读取,在经过完整的 RPC 流程之外,还要处理文件句柄查找、权限检查、open 状态维护等。在高并发随机读写场景下,这种文件级解析是有代价的。我们可以用 fio 测一测,单线程顺序读可能差距不大,但一旦开启 16 个线程做随机 4K 读写,NFS 在同等硬件条件下的 IOPS 通常会比裸块低 20%~40%。
这不一定意味着 NFS 不好,而是它的“文件语义”本身更重。NFS 处理“多个客户端同时读写同一个文件”时更稳妥,因为它能调用服务器端文件系统的锁和一致性机制。iSCSI 在这一点上完全不一样,它默认只有一个客户端在认领这块盘,不需要做锁协调,省下的这些开销就是性能优势的来源之一。
3.2 iSCSI 的块级性能为什么更稳
iSCSI 的数据面相对简单,命令和数据的封装、传输、确认,整个流程更接近“直接把网络当成一块 SCSI 线”,所以 CPU 开销相对小,IO 路径短。你可以在存储服务器上开一个稀疏文件,然后映射给客户端,随便跑一下 dd 或 fio,就会发现 iSCSI 的 IOPS 峰值比较高,延迟也更稳定。
但要注意一点,iSCSI 的高性能是有前提的:底层网络必须干净。这里说的干净包括:
- 独立的存储 VLAN,业务流量和存储流量隔开。
- 开启 jumbo frame,MTU 从 1500 调到 9000,减少包数量。
- 网卡支持多队列,irqbalance 要开,避免单个 CPU 中断积压。
如果不做这些优化,iSCSI 的延迟会随网络拥塞波动,反而不如 NFS 稳。我见过太多人只买了万兆网卡,却忘了调 MTU 和网卡队列,最后测出来 iSCSI 延迟比千兆 NFS 还高,还以为是协议的问题。那不是协议的问题,是网络层面的问题。
3.3 一个实际的性能对比思路
真要对比,建议用同一个存储后端,分别用 NFS 导出一个目录、用 iSCSI 导出一个 LUN,在同一个客户端上跑同样的 fio 配置。这样能排除掉磁盘本身的差异,单纯看协议开销。下面的 fio 配置可以作为测试模板:
[global] ioengine=libaio direct=1 iodepth=32 runtime=120 time_based=1 group_reporting=1 [randread] rw=randread bs=4k size=20G numjobs=16这个模板测的是 4K 随机读。再换 rw=randwrite、bs=64k 多跑两轮,观察 IOPS、延迟 P99 和带宽。我可以直接给个结论:在同样的万兆网络里,iSCSI 的 4K 随机读 IOPS 大约是 NFS 的 1.4~1.8 倍,但顺序读写两者差距很小,NFS 在某些大文件场景下甚至能反超。所以如果你的应用是大量顺序读写(比如视频剪辑、备份),NFS 完全够用;如果是大量随机小块读写(数据库、高并发虚拟机),块协议会更有优势。
4. 多路径与链路冗余:可用性的两种实现路径
4.1 NFS 的 World Wide Nameless 高可用方案
NFS 的高可用,传统做法是 VIP 漂移。比如用 keepalived 管理一个虚拟 IP,存储服务器之间做主备,主节点挂了,VIP 漂到备节点,客户端自动重连。
但这个方案有坑:NFS 的客户端会在挂载时建立 TCP 连接,服务器故障后,连接断开,客户端进入重试状态。如果你只配了 60 秒的 timeout,那业务就要断 60 秒。所以生产环境里我会做的是:
- 客户端的挂载参数加上 hard,intr,确保服务恢复后能自动重连。
- 在 DNS 层面把存储主机名解析成 VIP,而不是服务器 IP。
- NFSv4 支持 Federated Filesystem,允许跨节点的状态迁移,但这类方案通常要配合商业存储软件。
近年来 NFS 生态还出了一个 nconnect 参数,在 NFSv3 客户端上可以为一个挂载点创建多个 TCP 连接。比如 mount 时加上nconnect=4,在万兆网络上能明显提升并发吞吐,对高并发文件服务很有帮助。
4.2 iSCSI 的 MPIO 才是正经的多路径
iSCSI 的多路径方案跟 NFS 完全不一样,协议层面提供了标准的 MPIO(多路径 IO)。你可以用两个端口分别去连 target,客户端会用 multipath 模块把这几个路径合并成一个块设备。在这个合并的过程里,每条路径上都会跑健康检查(比如 periodic ping),如果某一条物理链路断了,IO 会自动切到另一条。
实际配置时注意这几点:
- initiator 要设唯一的 initiator name,不要用默认值。默认值在多个客户端同时接入时会产生冲突。
- target 端必须开启多个 portal/IP 地址,否则客户端只有一个路径可用。
- 客户端要装 multipath-tools,然后调整 /etc/multipath.conf,设置
path_grouping_policy multibus或failover。数据库场景通常用 failover,避免多个路径同时回放写 IO 导致排序问题。 - 块设备要做 wwid 永久性映射,否则重启之后 /dev/sdX 对应的盘可能变化。
blacklist { wwid "SAdaptec_Controller" } multipaths { multipath { wwid "3600a098038303234342f4a4730594970" alias mpath0 } }MPIO 的价值在于它能同时利用多条链路的带宽——如果你开的是 2×10GbE,MPIO 可以把流量分散到两条链路上。这里有个反差:NFS 的 nconnect 是并发连接,直接提升单客户端吞吐;iSCSI 的 MPIO 则更强调冗余和链路均衡。两者没有优劣,只是网络层思路不同。
5. 从应用到数据库:各场景选型建议
5.1 虚拟化平台:VMware 和 KVM 的取向不太一样
VMware 生态里,NFS 和 iSCSI 都支持得很成熟,但多数 VMware 管理员对 iSCSI 有天然好感,因为它在指定数据存储时更接近本地磁盘,虚拟机的磁盘格式可以选择精简置备以节省空间,并存快照时也更平滑。而 NFS 在 VMware 场景下,如果存储端是 Linux + ZFS,需要额外注意 ZFS 的primarycache=metadata参数,否则会被 vSphere 的存储 I/O 控制给逼得内存不够用。
KVM 环境里,我反而更推荐 NFS,因为 libvirt 对 NFS 数据存储的支持比 iSCSI 简单得多,你可以直接用virt-install指定 NFS 路径,不需要在宿主机上配 initiator 和 multipath 那么重的依赖链。而且 KVM 的 qcow2 格式本身就支持稀疏文件,在 NFS 上也有不错的性能表现。
5.2 数据库:iSCSI 基本是默认答案
数据库场景直接选 iSCSI 就是对的。原因很简单:数据库必须独立管理自己的文件系统和缓存策略,不希望存储层替你处理“并发打开同一个文件”的锁问题,也不想每次读文件都经过服务端文件系统的上下文切换。iSCSI 给的是一个完整裸盘,数据库可以自己控制 ext4 的 stripe 参数、XFS 的 agcount、甚至直接走裸设备。
如果你用的是 PostgreSQL,在 iSCSI 盘上创建表空间,fdatasync 刷盘行为的延迟明显低于 NFS,因为 NFS 每次 fsync 都要经过网络和服务器端内存缓存,即使你设置sync=always,延迟也比 iSCSI 高出一个数量级。这对于 WAL 写入这种高频小写场景影响非常大。
5.3 文件共享、云原生、备份归档:NFS 更省心
反过来讲,如果业务本身是文件级别的共享访问,或者是一个 Web 集群需要共享上传目录,那就选 NFS。NFS 天然支持多个客户端同时读写同一目录,权限模型跟你本地用 POSIX 差不多,不必像 iSCSI 那样还得自己在集群里搭 OCFS2/GFS2 文件系统,那是一套很重的依赖。
备份场景也适合 NFS。比如你有一个备份服务器,通过 NFS 挂载存储空间,备份软件直接往目录里写文件,不需要关心 LUN 容量如何划分、映射给谁。NFS 目录扩容也简单,服务器端往文件系统里加目录空间,客户端马上能看到。
5.4 Windows 环境:NFS 分区安装系统的坑,必须单独说
很多用户被这个问题难住:“Windows 无法安装到 NFS 分区”。这个问题的根源不在 NFS 本身,而是 Windows 安装程序只支持在它认识的本地磁盘(包括 iSCSI 或光纤通道映射的裸盘)上创建分区,它不认为网络挂载的 NFS 目录是一个“可安装的磁盘”。
如果你在安装 Windows 时,只能看到 NFS 挂载的共享目录,看不到本地磁盘,解决办法是把安装源里的存储驱动打进 install.wim,或者先用 iSCSI 映射一个块设备给 Windows 当“本地盘”来装系统。我在实际环境里遇到过一台物理机,BIOS 里能识别到内置 RAID 阵列,但 Windows 安装程序就是找不到盘,因为阵列卡驱动没有注入。后来通过dism /add-driver把驱动加进 Windows 镜像才解决。
Windows 挂载 NFS 共享是可以的,但前提是你得开启“NFS 客户端”功能。启用之后,用mount \\192.168.1.10\share Z:就能像本地盘一样访问。这里有个坑:Windows 的 NFS 客户端默认支持的是 NFSv3,要启用 NFSv4 需要额外配置,而且挂载时如果服务器端没有做用户映射,会出现写入无权限的情况。建议在挂载时指定 uid/gid 映射,或者确保 Windows 客户端的用户 ID 与服务器端的 POSIX 用户 ID 一致。
6. 实操排查:常见问题速查和排错思路
6.1 NFS 挂载失败的从头排查步骤
NFS 挂载不上,大部分人在第一步就看错了方向,检查顺序应该是:
- 网络层的 ping 通不通。
- 服务器端
exportfs -v确认导出的路径和权限。 - 客户端
showmount -e 服务器IP看能否列出共享。 - 挂载后
mount | grep nfs查看挂载参数。 cat /var/log/messages | grep nfs看内核日志。
最常见的问题是“客户端的挂载选项不兼容”。比如服务器端用 NFSv4,客户端 mount 时指定vers=3,就永远挂不上。还有nfsvers=4.2这个参数,在老内核上不识别,会静默回退到 v4.1,但如果你显式写死了vers=4.2,就会报 operation not permitted 之类的错误。
另一个隐蔽问题是TCP 端口被防火墙阻断。NFSv4 只需要 2049 端口,但 NFSv3 需要配合 mountd 等辅助端口,这些端口经常在 rpc 服务启动时随机分配。很多云主机环境默认只放行 2049,导致 NFSv3 挂载失败。解决方案是固定 mountd 端口:
echo "mountd 2048/tcp" >> /etc/services echo "rquotad 2047/tcp" >> /etc/services然后在/etc/nfs.conf里指定:
[mountd] port=2048 [rquotad] port=20476.2 iSCSI 连接丢失和 session 中断
iSCSI 比 NFS 多一整套发现和登录流程,所以排查路径也更长。典型问题包括:discovery 能发现 target,但 login 一直失败;连上之后 IO 超时;重启后盘设备消失。
login 失败最常见的原因是 initiator name 重复、CHAP 认证没配一致。排查命令是先看 initiator 端日志:
journalctl -u iscsid -n 100如果是 CHAP 问题,会看到类似 authentication failed 的字样。这时候需要检查 /etc/iscsi/iscsid.conf 里的node.session.auth.authmethod和node.session.auth.username/password。注意这个密码是单向的,target 端设置的 incoming 密码要和 initiator 端设置的 outgoing 密码一致,而且 initiator 端要设为node.session.auth.authmethod = CHAP,不是默认的 None。
IO 超时的问题通常出现在网络路径不稳定或者 target 端磁盘阻塞严重时。可以先看multipath -ll的状态,如果显示的路径是 timeout,说明网络中断过但 MPIO 还在重试。可以用iscsiadm -m session -P 3打印详细会话状态,确认连接状态是 LOGGED_IN。
重启后盘设备丢失,多半是 systemd 启动顺序的问题。解决方案是把 iscsid 和 multipathd 都加入开机自启,并且在 cloud-init 或 rc.local 里做一次iscsiadm --mode node --loginall=automatic的自动登录。
6.3 缓存一致性与脏数据问题:两个协议都绕不开的陷阱
说到可用性,还有个很隐蔽但容易中招的问题:缓存一致性。NFS 侧,多个客户端同时写同一个文件时,内核缓存可能不一致,最终结果要由最后一次写入决定,但谁最后写不一定,这就可能导致数据错乱。服务器端加缓存(比如 ZFS 的 ARC)时,客户端写的数据可能停留内存,还没落盘就遇上断电。我的做法是:
- 服务器端 ZFS 设置
sync=always或logbias=throughput,保证关键写操作直接落盘。 - 客户端挂载 NFS 时加上
sync选项,牺牲一点性能,换取写操作的实时确认。
iSCSI 侧同样有缓存风险。target 端如果用的是文件模拟 LUN,那么文件系统的页缓存就可能导致块设备数据不一致。最安全的做法是用块设备直接作为后端,或者给 target 配一个专用的,完全独立的日志盘。如果你的 target 端是 ZFS,建议把zfs set sync=always设上,否则掉电后可能出现损坏。
6.4 常见的错误配置速查
| 症状 | 常见原因 | 解决方法 |
|---|---|---|
| NFS 挂载成功但写入 Permission denied | 服务器端 no_root_squash 未配置;客户端 root 被压缩成 nobody | 在 /etc/exports 中加 no_root_squash 选项 |
| Windows 安装时“无法安装到 NFS 分区” | Windows 安装程序不识别网络挂载 | 改用 iSCSI 映射裸盘,或注入存储驱动 |
| iSCSI Target 找不到 | 防火墙未放行 3260 端口;Discovery 地址配置错误 | 检查端口,用 iscsiadm -m discovery 重新发现 |
| multipath 经常切换路径 | 网络交换机 STP 导致链路 toggle | 在存储 VLAN 上启用 portfast / 边缘端口 |
| NFS 随机卡死 | MTU 不匹配,大包无法传输 | 两端统一 MTU,开启 jumbo frame 后重启 network |
| iSCSI 性能低于预期 | 未开启网卡多队列;irqbalance 关闭 | 开启多队列,确认 MSI-X 向量数,检查 CPU 中断分布 |
7. 我自己的选型心法和几个经验体会
我个人的习惯是:能用 NFS 就不用 iSCSI,原因是 NFS 的管理成本低。我维护过几百个 VM 的集群,NFS 数据存储的好处是你可以在存储端直接浏览虚拟磁盘文件、快照、备份镜像,而 iSCSI 你就只能看到一个个 LUN,要操作里面的文件还得再挂载或映射一次,非常麻烦。
但有两个场景我会毫不犹豫推翻这个习惯。一个是数据库,不管是 Oracle 还是 PostgreSQL,小 I/O 延迟和 fsync 行为太敏感了,iSCSI 在这个场景下的优势是结构性的,缓存调优也简单得多。另一个是 Windows 虚拟机需要独立磁盘的场景,Windows 生态对块设备的支持根深蒂固,iSCSI 能免掉一堆 NFS 客户端权限和 SMB 兼容性上的边角坑。
还有一个很多人忽略的点:防火墙和网络策略。NFS 用固定端口 2049 时,对防火墙非常友好;iSCSI 固定在 3260,也还行,但如果你在 iSCSI 上叠加 CHAP、IPsec,防火墙要处理的内容会变复杂。如果你所在环境的网络策略是“白名单制”,建议提前把相关端口和协议列全,否则部署时会卡很久。
关于后端清零的问题我多说两句。很多人在 iSCSI target 后端用文件模拟 LUN,文件系统删除后,零填充是没有的,这样新 LUN 的数据区域可能残留旧数据,这在等保场景是个大问题。毕竟是块设备协议,客户端拿到的是整块盘,理论上可以读到“上一任”写入的数据。生产环境要养成新建 LUN 后强制清零的习惯,或者干脆用支持 SCSI UNMAP 的解决方案,让 target 端真正把块标记为未分配。
最后分享一个配置细节,NFS 客户端的挂载参数里我建议加上actimeo=60,这个参数会把文件属性缓存的 TTL 拉长,大幅减少对服务器端 getattr 的调用次数。很多团队在性能调优时只盯着网络带宽,把这个参数加不加完全不重视。实测下来,在高并发 Web 静态资源访问场景里,这个参数能让 NFS 的请求数下降 30% 左右。性能焦虑从哪儿来?往往是这些看似不起眼的参数没配好。
NFS 和 iSCSI 之争,更多时候不是“谁更强”的差异,而是“谁更合适”的问题。除非你的环境里已经有一整套 CIFS/NFS 权限体系,或者你的业务强依赖多客户端共享同一份文件,否则按“数据库选 iSCSI,文件共享选 NFS,虚拟化看需求”这条基本逻辑走,大概率不会出错。真出了问题,也别急着甩锅给协议,先去查网络、查参数、查日志,很多时候问题就藏在最后一公里。