前言
之前我在 fnOS 上把一块只用于 Steam 游戏库的存储空间做成了 iSCSI。旧方案是:
- 5×6TB 机械硬盘组成 RAID0
- 2×约 250GB 消费级 NVMe 作为 LVM dm-cache writeback
- ext4 文件系统上放 10TB FILEIO 镜像文件
- Windows 通过 100G 网络挂载为 NTFS 游戏盘
读取跑分尚可,但 FILEIO 的同步写入长期只有约 125 MiB/s。最后我把这一块存储空间重建为原生 ZFS,使用 ZVOL + LIO IBLOCK 导出 iSCSI。重启后实测顺序写入约 962 MiB/s,读取约 1.65 GiB/s。
这篇不是“照抄命令即可”的无脑教程,而是把实际迁移过程、踩坑和数据安全边界整理出来,供类似需求参考。
重要警告:本文涉及销毁存储池和重建 iSCSI。我的这个盘只存可重新下载的 Steam 游戏,所以接受清空。任何有重要数据的存储空间都不要照抄,务必先做离线备份。
一、最终拓扑
5×6TB HDD
**─ ZFS stripe(5 个顶层单盘 vdev,无冗余)
**─ 10TB thick ZVOL,volblocksize=16K
**─ LIO /backstores/block(IBLOCK)
**─ iSCSI
**─ Windows GPT + NTFS 64K
2×消费级 NVMe
**─ ZFS L2ARC cache(只做读缓存)
关键参数:
- ZFS:
ashift=12、compression=lz4、atime=off
- ZVOL:
volblocksize=16K、sync=standard、厚置备
- LIO:IBLOCK,逻辑扇区保持 512B,队列深度 128
- Windows:GPT、NTFS 64K 分配单元
- iSCSI:两条 MCS 连接,走专用高速网络
两块消费级 NVMe 只作为 L2ARC。它们损坏或拔掉不会破坏主池数据,因此我没有把它们做成 SLOG 或 special vdev。
二、为什么 FILEIO 写入慢
旧 LUN 是“文件型 LUN”:
iSCSI → LIO FILEIO → 10TB 镜像文件 → ext4 → LVM/dm-cache → md RAID0
同步写入需要穿过文件系统和块层,路径长,O_DSYNC 下的稳定 QD32 顺序写只有约 124.90 MiB/s。
我也测试过 FILEIO 的 aio=true / Async1:短时间写入会明显变快,但在当前 fnOS 内核上出现过 LIO 内核线程长期 D 状态,关机也会卡住。最终已经回滚。
避坑:不要只看 Async FILEIO 的短时跑分。我这台机器上它出现了内核线程卡死,不适合作为稳定方案。
IBLOCK 直接把 ZVOL 块设备交给 LIO:
iSCSI → LIO IBLOCK → ZVOL → ZFS vdev
少了一层大文件和 ext4,同步写路径更直接,这才是本次写入提升的核心。
三、迁移前必须做的事情
我实际执行时先做了以下检查:
- Windows 停止 Steam,卸载/脱机 iSCSI 磁盘并断开会话。
- NAS 停止对应 iSCSI target,确认没有进程继续访问旧 LUN。
- 备份 targetcli 配置、fnOS iSCSI 配置以及系统数据库相关记录。
- 用 UUID、序列号和设备拓扑逐个核对目标硬盘。
- 再次确认其他重要存储池均为 ONLINE,且不在待删除设备列表中。
- 只删除目标存储空间对应的 VG、PV、md 和成员盘。
不要依赖 /dev/sdX 的字母顺序,重启后它可能变化。涉及销毁操作时应使用 /dev/disk/by-id/ 并逐盘核对。
四、ZFS 与 ZVOL 的关键配置
下面仅展示结构,POOL、DISK1 等都应替换成自己核对过的 by-id 路径。
1. 创建 ZFS stripe
zpool create -f \
-o ashift=12 \
POOL \
/dev/disk/by-id/DISK1 \
/dev/disk/by-id/DISK2 \
/dev/disk/by-id/DISK3 \
/dev/disk/by-id/DISK4 \
/dev/disk/by-id/DISK5
zfs set atime=off POOL
zfs set compression=lz4 POOL
zfs set xattr=sa POOL
zfs set acltype=posix POOL
这里是 5 个独立顶层 vdev,也就是条带:性能优先、没有冗余,任何一块硬盘故障都会丢失整个池。风险与原先的 md RAID0 一样,只适用于可重新下载的数据。
2. 创建 ZVOL
zfs create -o mountpoint=none POOL/iscsi
zfs create \
-V 10T \
-b 16K \
-o compression=lz4 \
-o sync=standard \
POOL/iscsi/steam
在我使用的 OpenZFS 2.4.1 上,创建时显式加 refreservation=auto 反而会报 ENOSPC;省略它以后,ZVOL 可以正常创建,并自动得到与卷大小对应的 reservation。这个现象可能与版本有关,建议创建后用下面的命令核对:
zfs get volsize,volblocksize,compression,sync,refreservation POOL/iscsi/steam
ls -l /dev/zvol/POOL/iscsi/steam
五、关于“大于 2TB 必须改 4K”的误区
本次迁移参考并感谢社区这篇文章:
〖教程〗ZFS存储空间开 iSCSI UI 正常以及大于 2T 的解决
但我实测后有两点不同结论:
- 大于 2TB 的磁盘并不要求 iSCSI 逻辑扇区改成 4096B。 Windows 使用 GPT 时,512B 逻辑扇区一样支持 10TB。
- 现代 OpenZFS 的 ZVOL 默认 volblocksize 也不应假定为 4096B。 我这个版本实际使用 16K,并在创建时明确指定。
所以我保持 LIO 的逻辑块大小为 512B,ZVOL 的 volblocksize=16K。这两个“块大小”不是同一个概念,不要混为一谈,更不要对系统配置 JSON 做不加区分的全局替换。
六、创建 IBLOCK iSCSI
结构示例:
targetcli /backstores/block create \
name=steam \
dev=/dev/zvol/POOL/iscsi/steam
targetcli /iscsi create iqn.YYYY-MM.example:nas.steam
targetcli /iscsi/iqn.YYYY-MM.example:nas.steam/tpg1/luns create \
/backstores/block/steam
随后应按自己的环境配置 initiator ACL、CHAP、portal 和 MCS。不要把真实 IQN、密码或内网地址直接复制到公开帖子或脚本。
我保留的安全基线是:
block_size=512
queue_depth=128
is_nonrot=1
emulate_write_cache=0
sync=standard
没有把 ZFS 的 sync 关闭,也没有用消费级 NVMe 做 SLOG。对游戏库可以偏性能,但我仍希望一次普通掉电不至于把风险无限放大。
fnOS 的内部数据库和 iSCSI JSON 会随版本变化。我的做法是先完整备份,只精确更新对应存储空间和单个 LUN 的记录,并增加 systemd 服务在启动时等待 ZVOL 出现后恢复 target 参数。不建议未经核对直接复制 SQL,也不要全局替换配置文件。能通过 UI 完成的部分优先使用 UI。
七、实测结果
测试环境是 100G 专用网络;数值是 MiB/s。
| 场景 |
顺序读 1M QD32 |
随机读 64K QD32 |
顺序写 1M QD32 |
| 旧 FILEIO(稳定基线) |
1530.95 |
1545.85 |
124.90 |
| 新 ZVOL + IBLOCK,重启后 |
1653.60 |
1714.30 |
961.54 |
写入从约 125 MiB/s 提升到约 962 MiB/s,约 7.7 倍。
为了区分缓存影响,我还做了几组测试:
| 测试状态 |
顺序读 |
随机读 64K |
| HDD 冷数据、ARC 仅 metadata |
751.46 |
67.99 |
| L2ARC 部分预热、ARC data 关闭 |
1466.00 |
1438.88 |
| 正常热缓存状态 |
1653.60 |
1714.30 |
这说明高随机读取明显依赖 L2ARC;机械盘本身不可能提供 1.4 GiB/s 的 64K 随机读取。
另外,8GiB 不可压缩数据的 WriteThrough 填充约为 278.52 MiB/s。上面的 961.54 MiB/s 是 QD32 短时基准,不应理解成任意数据量下都能一直保持近 1 GiB/s。Steam 下载、解压和补丁更新还会受到客户端 CPU、网络源、NTFS 元数据和游戏文件结构影响。
八、L2ARC 的取舍
加入读缓存示例:
zpool add POOL cache \
/dev/disk/by-id/NVME1 \
/dev/disk/by-id/NVME2
我用计数器验证过 L2ARC hit/read 会随重复读取增长,说明缓存确实命中,而不是只看了一次漂亮跑分。
注意:
- L2ARC 主要改善重复读取,首次冷读仍由 HDD 决定。
- L2ARC 不负责提高同步写入。
- 消费级、无断 电保护的 NVMe 不适合贸然拿来做 SLOG。
- special vdev 是池结构的一部分,损坏可能导致整个池不可用,不能把它当成“可随时拔掉的缓存”。
九、重启验证
真正迁移完成的标准不是“当时能挂载”,而是 NAS 和 Windows 都重启后仍能自动恢复。
我最终确认:
- 三个存储池全部 ONLINE,读/写/校验错误为 0。
- 只重建了 Steam 所在的目标池,另外两个重要池未改动。
- ZVOL 自动出现,LIO 只保留一个 IBLOCK backstore,没有遗留 FILEIO。
- iSCSI target 和两条 MCS 连接自动恢复。
- Windows 10TB GPT/NTFS 盘符正常,文件系统无告警。
- NAS 内核日志没有新的存储、ZFS 或 iSCSI 相关错误。
- 重启后的跑分与迁移后预期一致。
十、结论
如果 iSCSI 只是大容量文件系统上的 FILEIO 镜像,且同步写入明显偏低,迁移到 ZFS ZVOL + LIO IBLOCK 确实可能带来很大提升。本次真正有效的改动是缩短写入路径,而不是简单地把 writethrough 改成不安全的缓存模式。
我的建议是:
- 重要数据优先考虑 mirror 或 RAIDZ,不要使用本文的五盘 stripe。
- ZVOL 根据负载选择 volblocksize,Steam/NTFS 场景可从 16K 开始实测。
- 逻辑扇区 512B 配合 GPT 可以正常使用 10TB,不要为了“大于 2TB”盲改 4K。
- 消费级 NVMe 适合做可丢失的 L2ARC,不要未经评估就做 SLOG/special vdev。
- 每次只改一个变量,改前后做同一套测试,并进行真实重启验证。
- 所有内部配置变更先备份,精确修改,避免全局替换。
以上是我这套“Steam 游戏库专用、高性能优先、允许重下”的实测经验。不同硬盘、内存、网络和 fnOS/OpenZFS 版本会有差异,欢迎大家补充更稳妥的做法。