通过将 ZFS 池构建为镜像结构,消除异常数据的影响
通过将 ZFS 池构建为镜像结构,消除异常数据的影响
作者:みんみん
上次更新于 2022-12-26
开始之前
在上一篇文章 试着破坏了 ZFS 池 中,我们确认了 ZFS 能够正确地检测出磁盘上的非法数据。
能够检测出非法数据这一点,比起那些无法检测到非法数据而可能在读取时引发错误行为的文件系统,要好得多。但既然能检测到非法数据,自然而然就会想到:那是否可以将其恢复呢?
在 ZFS 中,如果构建了具备冗余性的池,即使存在非法数据,也可以获取到正确的数据。具体来说,可以使用镜像(mirror)、RAID-Z 或 RAID-Z2 等池结构。
本文将基于与 试着破坏了 ZFS 池 相同的实验,在镜像结构的 ZFS 池上进行测试。
2022 年 12 月 26 日补充了执行 scrub 后的磁盘状态
准备 ZFS 镜像池
为了实验,准备了 2 台通过 USB 连接的 HDD,并在每台硬盘上创建了 4GB 的分区,用于构建镜像结构的 ZFS 池。
这两块 HDD 分别是 /dev/da1 和 /dev/da2,使用 GPT 创建分区表,并在每块硬盘上创建了带有 tt1 和 tt2 标签的分区。
$ gpart create -s gpt da1
da1 created
$ gpart create -s gpt da2
da2 created
$ gpart add -t freebsd-zfs -a 4k -s 4g -l tt1 da1
da1p1 added
$ gpart add -t freebsd-zfs -a 4k -s 4g -l tt2 da2
da2p1 added
$
$ gpart show da1
=> 40 488397088 da1 GPT (233G)
40 8388608 1 freebsd-zfs (4.0G)
8388648 480008480 - free - (229G)
$ gpart show da2
=> 40 312581728 da2 GPT (149G)
40 8388608 1 freebsd-zfs (4.0G)
8388648 304193120 - free - (145G)
$ gpart show -l da1
=> 40 488397088 da1 GPT (233G)
40 8388608 1 tt1 (4.0G)
8388648 480008480 - free - (229G)
$ gpart show -l da2
=> 40 312581728 da2 GPT (149G)
40 8388608 1 tt2 (4.0G)
8388648 304193120 - free - (145G)
$至此,镜像所需的两个 HDD 各自的分区已创建完成。
接下来,将这两个分区组合成一个 ZFS 镜像池。当然和 上次的实验 一样,不启用 ZFS 压缩。
如下所示,镜像结构的 ZFS 池已创建完成。
写入伪数据
那么就像上次实验一样,使用 dd 命令用零数据填满文件系统。
破坏写入的数据
接着像上次一样,强行写入垃圾数据,使其在 ZFS 中触发错误。由于是为了验证镜像冗余性和 ZFS 的修复功能,因此只向 da1 这一侧写入垃圾数据。
通常,从构成镜像的磁盘中读取数据时,为了分散负载和提升性能,会交替访问两个磁盘。ZFS 的镜像结构也有类似行为。因此,如果像上次一样仅破坏一个区块,读取时可能会正好绕过被破坏的区块,访问正常的磁盘。因此,这次将写入几十 MB 的数据,以制造更大范围的错误。刚好内核有将近 30MB,就用它来破坏数据。
读取被破坏的数据部分
为了确认是否发生错误,仅使用 da1 这一侧进行导入。因为是 USB 接口的 HDD,把 da2 实体移除是最简单的方法。
如上所示,无法访问 da2 侧的 gpt/tt2。就这样将 ztest 导入。
虽然只有一侧的驱动器,但 /ztest/dummy 确实存在。那么现在尝试实际读取它的内容。为了确认数据内容,这里使用 hd 命令进行读取。
无法读取文件,发生了错误。
镜像结构下的读取
现在重新恢复镜像结构,再次进行同样的操作。
此时,重新连接之前拔下的 da2 侧 USB 硬盘。
可以看到,gpt/tt2 一侧的状态已经是 ONLINE。那么就将 ztest 导入。
成功地导入了 ztest。接下来就像之前一样,尝试读取 /ztest/dummy 文件。
正如所见,文件已被正常读取,并且可以确认其内容全部为 0。
接着确认一下 ztest 的状态,可以看到一切正常,没有问题。
通过 scrub 检查磁盘一致性
ZFS 理论上应该存在无法读取的损坏块,因此执行了 scrub 操作来进行一致性检查。
经过一段时间后,确认 scrub 完成后的状态如下:
从该结果可以看出,gpt/tt1(da1 一侧)发生了 130 次校验和错误。但由于镜像(mirror)结构,从健康的 gpt/tt2 一侧获取了正确的数据,并在 scrub 过程中修复了损坏的数据。
重新读取数据
为了排除操作系统磁盘缓存的影响,先将池导出再重新导入,并重新进行读取操作:
如上所示,尽管存在发生错误的磁盘,但由于镜像结构的作用,依然成功读取到了正确的数据。
已经成功导出,因此现在物理上移除 da2 一侧,只使用 da1 一侧进行导入并尝试读取数据。
成功读取了损坏侧 da1 上的所有数据,完全没有问题。也就是说,通过 scrub 操作,数据被写回并得到了修复。而且实际在导入之后通过 status 确认,CKSUM 的值也确实是 0(可惜没来得及截图……)。
接下来,再次为已经导出的 ztest 连接上 da2,并尝试导入。
如上所示,错误已经被清除。
总结
通过本次实验可以确认,在 ZFS 中将磁盘配置为镜像或 RAID-Z 结构,可以避免部分数据错误的影响。同时也验证了,对于可以修复的数据,执行 scrub 后能够恢复为原来的状态。
不仅仅是 ZFS,其他的卷管理器或 RAID 系统,如果正确配置了镜像或 RAID,也同样能够避免部分数据错误。而是否能够实现自动修复,则取决于各个系统的具体实现。
最后更新于
这有帮助吗?

