MENU

【歪门邪道】Unraid上的ZFS初体验

April 1, 2025 • 瞎折腾

原本打算组完 NAS 之后慢慢扩容,这正好也是 Unraid 的优势所在。可是计划永远赶不上变化,我朋友扩容了他的 NAS,换下来了 3 块 8TB 和 2 块 6TB 的硬盘,我想着与其去网上买来路不明的二手硬盘,不如就把我朋友换下来的硬盘收了。于是乎掐指一算,我之前自己买的 2 块,加上收来的 5 块,正好 7 块,马上就要把我的 8 盘位吃满了。想来想去,不如折腾个 ZFS 吧!

前言

先前我在文章里提过没有选择 TrueNAS 是因为 DDR5 ECC 平台太贵了,而且没有小主板。后来我也仔细查阅了不少资料,最终得出的结论是 ECC 会让内存变得更安全,但是没有 ECC 并不意味着 ZFS 会变得脆弱。正相反,其实 ZFS 从设计上就是为了尽最大努力保证它读出来的数据要和你当初交给它时一致。

顺带一提,我今天才知道 ZFS 也是 Sun 公司制作的。我越发觉得这个公司真的很酷,没有坚持到现在真的很遗憾。没错,不要怀疑自己,Java 也是这个公司搞的。虽然 Java 本身的语法和其衍生品相比并不酷,但 JVM 很酷,它作为一个平台支持了其他更酷的东西,比如 kotlin、scala 这些语言。而 ZFS 最初于 2001 年开始研发,它的目的就是不相信任何存储设备,因此 ZFS 也包含了大量措施来检测数据完整性,如果 ZFS 没有 100% 的把握确定它交给你的数据是正确的,它就不会稀里糊涂的把数据给你,而是通过报错让你知道系统出问题了。太酷了!

尽管 Unraid 的阵列在扩展性和易用性上都比 ZFS 好,可最大的问题就是它无法甄别出现问题时哪块盘或者哪个文件出问题了,这也是我自己编写文件校验工具的动机。虽然大部分情况下硬盘要坏都是有征兆的,比如频繁报错、smart 自检失败、掉盘等,但有一种错误是静悄悄的:bit rot。折腾 NAS 的玩家应该对这个词并不陌生,它描述了一种存储介质的一种物理翻转,也就是说硬盘在没人动它的情况下,可能某一位就会自己从 0 翻转到 1,或者反过来。原因嘛,可能是生产瑕疵,或者是宇宙射线恰好命中了你的硬盘,又或者过年的时候亲戚家的熊孩子拿着强力磁铁吸你的 NAS 机箱。总之这种物理的问题完全无法避免,而这种又不算硬盘故障,诸多自检都对此无能为力。当你访问到了包含 bit rot 的区块,硬盘控制器自己也不知道这里的数据出问题了,它只能忠实地将它从磁盘上读出来的数据写到总线上,进而由操作系统呈现给用户,直到用户发现他珍藏多年的家庭合影再也打不开了。

对于 Unraid 来说,定期计算磁盘校验有助于发现这种错误,比如当 bit rot 发生时,unraid 可能会注意到阵列读出来的数据计算出的校验位和校验盘中存储的不一致,这个时候用户有两种选择:查找问题,或者将新的校验位写入校验盘。即便有两块校验盘也无法确定到底是哪块盘的数据出问题了,甚至你都无法得知是哪个文件出问题了 —— 这就是我自己写文件校验工具的目的,虽然校验和本身也存在硬盘上,也有遭受 bit rot 的风险,但密码学领域的哈希计算确保了任何一点微小的改动(哪怕只有 1bit)也会导致计算出和原来完全不同的哈希。因此在很大概率上可以通过文件级别的哈希来发现错误,然后从备份中找到完好的文件然后恢复。

是的,无论是 Unraid、RAID 还是 ZFS 都不能替代备份。尽管 ZFS 有更强大的数据完整性校验和自恢复功能,如果数据损坏超过一定程度,ZFS 也无力回天,而且它还会拒绝给你不完整的数据,因此拥有一个备份是十分必要的。

当然,很大概率并不意味着万无一失。如果恰好出现多个 bit rot 使得奇偶校验没变,而其中一些 bit rot 又出现在一个同一个文件,恰好导致了哈希冲突,那么恭喜你:这虽然不是什么露脸的事儿,但确实非常稀有,值得你向那些搞 IT 运维的人还有用 NAS 不做备份的人炫耀,坏事是会发生的。为了确保万无一失,我还是要考虑 ZFS。ECC 内存的问题解决了,下面就要考虑扩展和易用的问题了。易用性在 Unraid 支持了 ZFS 之后变得好了许多,你可以直接通过网页面板来扩容和管理 ZFS 存储池,配合插件 ZFS Master for Unraid 来管理 ZFS 的 dataset 和快照。扩容嘛,正好我是 3 块 8TB,2 块 6TB,再去买一块 6TB,刚好做两个 3 盘 raidz1 的 vdev。

什么?你问 vdev 是什么?简单地说,在 ZFS 里面,最顶层的结构是 zpool,也就是一个存储池,但是和群辉以及 lvm 中存储池的概念不同,ZFS 的 zpool 是由 vdev 组成的。所谓 vdev 就是 virtual device,虚拟设备。这些虚拟设备是由多个硬盘在不同配置下组成的。比如说你可以用两块硬盘按照 mirror 模式组成 vdev,这和传统的 raid1 差不多。你也可以用 stripe 模式组成类似 raid0 效果的 vdev,或者用 ZFS 独家的 raidz(1,2,3)来确保 vdev 在丢失 1、2、3 块硬盘的情况下还能够读出数据。当然,ZFS 中 vdev 的种类也有所不同,比如数据 vdev 用来存储你的文件,而元数据 vdev 则可以用来记录 ZFS 这个文件系统的数据。我的使用场景没有这么复杂,况且 unraid 也没有支持那么复杂的场景,因此我就只用数据 vdev。

那么,话不多说,我们直接开始操作。

创建 zpool

创建 zpool 其实挺简单的,首先要停掉阵列,然后创建新的 pool。起名是最难的,我想来想去,就叫它 zfs-pool 吧。添加好我的 3 块 8TB 硬盘之后,点击左边的 Zfs-pool 就可以进入池的设定页面。在文件系统里选择 zfs 或者 zfs - encrypted,我选择的后者,和我的阵列一样,硬盘要先加密再使用。注意这里的 zfs - encrypted 和 ZFS 内置的加密还不一样。Unraid 的加密文件系统是使用的 LUKS,对设备进行块级加密之后再交给文件系统使用。而 ZFS 内置的加密则是将数据写入磁盘之前进行加密。这两种加密互不冲突,但同时启用会影响性能。鉴于我已经使用了加密阵列,因此我选择了让 unraid 帮我处理加密。选择完文件系统,我们可以选择分配方式了,我这里选了 raidz1,这样启动阵列之后就会创建一个 3 盘的 vdev,使用一块盘的容量进行冗余,读取速度则相当于是两块盘的总和。由于我后来购买的 6TB 盘还没到,因此他们留作后续扩容。

启动阵列,会发现 unraid 提示 zfs pool 不可用,因为你还没有格式化。去到页面最底下格式化一下就好了。至此,我们算是有一个 zfs 存储池可以用了。

但是还没完!确保 ZFS 能对所有所有数据都能够自恢复,我们需要配置一个叫做 scrub 的东西,它会定期检查所有数据,然后修复错误的地方,有点类似于 unraid 的校验。在阵列启动的时候点进 Zfs-pool,可以找到 scrub 设置,可以设置每周或每月定期运行。

此外,为了节省一些空间,我还决定打开 zfs 的压缩和 dedup 功能。压缩可以直接在网页端设置,但 dedup 需要使用命令行:

  • zfs set dedup=blake3,verify zfs-pool

这个命令会在 zfs-pool 这个存储池启用去重,算法使用 blake3 计算哈希,而 verify 标志则是告诉 zfs 当两个块哈希相同的时候,逐个字节对比块内数据。

完成设置之后就可以在 share 里面使用新创建的存储池了。在新版本 unraid 里面,zfs 存储池可以用作一级或者二级缓存,这里我选择还是沿用 NVME 的 cache 作为写入缓存,然后使用 mover 移动到 zfs-pool,因为我的内存不是很大,所以写缓存还是要靠 SSD。

需要注意的是更新的 share 的选项之后,原本的文件并不会从阵列自动复制过去。如果要手动复制的话,建议先复制到 SSD 上,然后再调用 mover,这样 mover 会自动按照 share 创建 dataset。否则直接复制上去就是文件夹,而后续通过 ZFS Master 创建 dataset 就会被挂载点覆盖。不过万一被覆盖了也不要慌,因为数据没有丢。使用 zfs unmount /mnt/zfs-pool/<mount-point> 来卸载对应的挂载点,然后把被覆盖的文件夹重命名,然后再使用 zfs mount zfs-pool/<mount-point> 来重新挂载对应的 dataset,之后用 rsync 把文件复制过去就好了。

扩容(新增 vdev)

由于是开始折腾 ZFS 当晚就下单了新的硬盘,因此没几天就到了,正好试一下给 ZFS 扩容。一般来说给 ZFS 扩容有两种方法,第一种就是新增数据 vdev,因为 vdev 之间是以 stripe 的方式组合的,所以新的 vdev 容量是按照加法计算进存储池的。第二种就是原地扩容 vdev,但是你需要把 vdev 里所有的硬盘都扩大才行,并且只能扩容,不能缩小。后者也是为什么我选了两个 3 盘 z1,而不是一个 6 盘 z2。三盘在升级的时候带来的经济压力会稍微小一些,虽然 vdev 可以接受大小不一的盘,但是对于容量超出其他盘的部分就完全浪费了。

扩容的操作也相对简单,停掉阵列,将原本 Zfs-pool 的 slots 从 3 改成 6,然后选中新的硬盘,启动阵列后便会自动创建一个新的 vdev。在命令行中可以确认:

  • # zpool list -Pv zfs-pool
  • NAME SIZE ALLOC FREE CKPOINT EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT
  • zfs-pool 38.2T 4.88T 33.3T - - 1% 12% 1.00x ONLINE -
  • raidz1-0 21.8T 4.88T 17.0T - - 2% 22.3% - ONLINE
  • /dev/mapper/sdf1 7.28T - - - - - - - ONLINE
  • /dev/mapper/sdh1 7.28T - - - - - - - ONLINE
  • /dev/mapper/sdc1 7.28T - - - - - - - ONLINE
  • raidz1-1 16.4T 6.05M 16.4T - - 0% 0.00% - ONLINE
  • /dev/mapper/sdb1 5.46T - - - - - - - ONLINE
  • /dev/mapper/sdd1 5.46T - - - - - - - ONLINE
  • /dev/mapper/sdi1 5.46T - - - - - - - ONLINE

这里由于我使用了 unriad 的 luks 加密,所以设备全都是解密后的 mapper。但这里可以看到多了一个 raidz1-1 的 vdev,里面就是新添加的三块 6TB 的硬盘。

至此,在 unraid 上建立 zfs 的过程就到此结束了。

- 全文完 -


知识共享许可协议
【歪门邪道】Unraid 上的 ZFS 初体验天空 Blond 采用 知识共享 署名 - 非商业性使用 - 相同方式共享 4.0 国际 许可协议进行许可。
本许可协议授权之外的使用权限可以从 https://skyblond.info/about.html 处获得。

Archives QR Code
QR Code for this page
Tipping QR Code
Leave a Comment

2 Comments
  1. 不知道你的硬盘是机械还是固态,我使用硬盘盒子接入两个机械硬盘做 RAID 之后,创建 zfs 性能和 ext4 区别很大,尤其是小文件读写。。。
    你有做过相关的性能测试吗?

    1. @x1nt 我的硬盘都是机械的,有的是从淘宝买的二手,有的是从朋友家 NAS 上扣下来的(他升级硬盘容量来着,不是抢的)。关于性能方面,ext4 我好久没用了,因为 openSUSE 那边默认要么是 BtrFS 要么是 XFS。你说的 ZFS 性能区别很大,不知道是单盘性能还是阵列性能?ZFS 在读写数据上都会有额外的 overhead,会有一定程度的性能损耗,但是如果是多盘 vdev 的话,多个盘之间协作能够提升整个 vdev 的性能,这个要看具体的阵列。另外 ZFS 本身也有 ARC 读缓存,如果是小文件读的话可能直接被缓存了。ext4 的话只是依赖 linux 本身的页缓存,可能比 zfs 自己的缓存略逊一筹。

      我倒是没做过性能测试,不过从我用 unraid 阵列和 zfs pool 的体验来说,unraid 阵列一个文件的读上限就是一块硬盘的上限,写上限看数据盘和校验盘,但总体来说性能一般。换成 ZFS 之后,写入性能一般,可能是因为后台有在 bt 做种,导致同时读写,不过读性能确实好了不少,因为可以几块硬盘一起读,还可以通过校验数据计算出对应的数据块。