通过将 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 压缩。
$ zpool create -O atime=off ztest mirror gpt/tt1 gpt/tt2
$
如下所示,镜像结构的 ZFS 池已创建完成。
$ zpool status ztest
pool: ztest
state: ONLINE
config:
NAME STATE READ WRITE CKSUM
ztest ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
gpt/tt1 ONLINE 0 0 0
gpt/tt2 ONLINE 0 0 0
errors: No known data errors
$ zpool list -v ztest
NAME SIZE ALLOC FREE CKPOINT EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT
ztest 3.75G 3.62G 128M - - 72% 96% 1.00x ONLINE -
mirror-0 3.75G 3.62G 128M - - 72% 96.7% - ONLINE
gpt/tt1 - - - - - - - - ONLINE
gpt/tt2 - - - - - - - - ONLINE
$ zfs list ztest
NAME USED AVAIL REFER MOUNTPOINT
ztest 420K 3.62G 96K /ztest
$ df /ztest/
Filesystem 512-blocks Used Avail Capacity Mounted on
ztest 7601576 192 7601384 0% /ztest
写入伪数据
那么就像上次实验一样,使用 dd
命令用零数据填满文件系统。
$ dd if=/dev/zero of=/ztest/dummy bs=1m
dd: /ztest/dummy: No space left on device
3711+0 records in
3710+1 records out
3891134464 bytes transferred in 151.249101 secs (25726662 bytes/sec)
$ df -h /ztest/
Filesystem Size Used Avail Capacity Mounted on
ztest 3.6G 3.6G 0B 100% /ztest
$ zfs list -o space ztest
NAME AVAIL USED USEDSNAP USEDDS USEDREFRESERV USEDCHILD
ztest 0B 3.63G 0B 3.62G 0B 672K
$ ls -l /ztest/dummy
-rw-r--r-- 1 root wheel 3891134464 Dec 22 17:43 /ztest/dummy
破坏写入的数据
接着像上次一样,强行写入垃圾数据,使其在 ZFS 中触发错误。由于是为了验证镜像冗余性和 ZFS 的修复功能,因此只向 da1 这一侧写入垃圾数据。
通常,从构成镜像的磁盘中读取数据时,为了分散负载和提升性能,会交替访问两个磁盘。ZFS 的镜像结构也有类似行为。因此,如果像上次一样仅破坏一个区块,读取时可能会正好绕过被破坏的区块,访问正常的磁盘。因此,这次将写入几十 MB 的数据,以制造更大范围的错误。刚好内核有将近 30MB,就用它来破坏数据。
$ zpool export ztest
$ ls -l /boot/kernel/kernel
-r-xr-xr-x 2 root wheel 29343392 Nov 4 10:27 /boot/kernel/kernel
$ dd if=/boot/kernel/kernel of=/dev/gpt/tt1 oseek=1000000
dd: /dev/gpt/tt1: Invalid argument
57311+1 records in
57311+0 records out
29343232 bytes transferred in 21.647581 secs (1355497 bytes/sec)
$
读取被破坏的数据部分
为了确认是否发生错误,仅使用 da1 这一侧进行导入。因为是 USB 接口的 HDD,把 da2 实体移除是最简单的方法。
$ zpool import
pool: ztest
id: 6304409293823647838
state: DEGRADED
status: One or more devices are missing from the system.
action: The pool can be imported despite missing or damaged devices. The
fault tolerance of the pool may be compromised if imported.
see: https://openzfs.github.io/openzfs-docs/msg/ZFS-8000-2Q
config:
ztest DEGRADED
mirror-0 DEGRADED
gpt/tt1 ONLINE
gpt/tt2 UNAVAIL cannot open
如上所示,无法访问 da2 侧的 gpt/tt2
。就这样将 ztest 导入。
$ zpool import ztest
$ zpool status ztest
pool: ztest
state: DEGRADED
status: One or more devices could not be opened. Sufficient replicas exist for
the pool to continue functioning in a degraded state.
action: Attach the missing device and online it using 'zpool online'.
see: https://openzfs.github.io/openzfs-docs/msg/ZFS-8000-2Q
config:
NAME STATE READ WRITE CKSUM
ztest DEGRADED 0 0 0
mirror-0 DEGRADED 0 0 0
gpt/tt1 ONLINE 0 0 0
13953643250226400519 UNAVAIL 0 0 0 was /dev/gpt/tt2
errors: No known data errors
$ df /ztest
Filesystem 512-blocks Used Avail Capacity Mounted on
ztest 7601264 7601024 240 100% /ztest
$ ls /ztest
dummy
虽然只有一侧的驱动器,但 /ztest/dummy
确实存在。那么现在尝试实际读取它的内容。为了确认数据内容,这里使用 hd
命令进行读取。
$ hd /ztest/dummy
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
hd: /ztest/dummy: Input/output error
9e1a0000
$
无法读取文件,发生了错误。
镜像结构下的读取
现在重新恢复镜像结构,再次进行同样的操作。
$ zpool export ztest
此时,重新连接之前拔下的 da2 侧 USB 硬盘。
$ zpool import
pool: ztest
id: 6304409293823647838
state: ONLINE
action: The pool can be imported using its name or numeric identifier.
config:
ztest ONLINE
mirror-0 ONLINE
gpt/tt1 ONLINE
gpt/tt2 ONLINE
可以看到,gpt/tt2
一侧的状态已经是 ONLINE。那么就将 ztest 导入。
$ zpool import ztest
$ zpool status ztest
pool: ztest
state: ONLINE
config:
NAME STATE READ WRITE CKSUM
ztest ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
gpt/tt1 ONLINE 0 0 0
gpt/tt2 ONLINE 0 0 0
errors: No known data errors
$
成功地导入了 ztest。接下来就像之前一样,尝试读取 /ztest/dummy
文件。
$ hd /ztest/dummy
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
e7ee0000
$
正如所见,文件已被正常读取,并且可以确认其内容全部为 0。
接着确认一下 ztest 的状态,可以看到一切正常,没有问题。
$ zpool status ztest
pool: ztest
state: ONLINE
scan: resilvered 432K in 00:00:01 with 0 errors on Fri Dec 23 11:14:23 2022
config:
NAME STATE READ WRITE CKSUM
ztest ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
gpt/tt1 ONLINE 0 0 0
gpt/tt2 ONLINE 0 0 0
errors: No known data errors
$
通过 scrub 检查磁盘一致性
ZFS 理论上应该存在无法读取的损坏块,因此执行了 scrub
操作来进行一致性检查。
$ zpool scrub ztest
$ zpool status ztest
pool: ztest
state: ONLINE
scan: scrub in progress since Fri Dec 23 11:18:02 2022
3.62G scanned at 530M/s, 378M issued at 54.0M/s, 3.62G total
0B repaired, 10.18% done, 00:01:01 to go
config:
NAME STATE READ WRITE CKSUM
ztest ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
gpt/tt1 ONLINE 0 0 0
gpt/tt2 ONLINE 0 0 0
errors: No known data errors
经过一段时间后,确认 scrub 完成后的状态如下:
$ zpool status ztest
pool: ztest
state: ONLINE
status: One or more devices has experienced an unrecoverable error. An
attempt was made to correct the error. Applications are unaffected.
action: Determine if the device needs to be replaced, and clear the errors
using 'zpool clear' or replace the device with 'zpool replace'.
see: https://openzfs.github.io/openzfs-docs/msg/ZFS-8000-9P
scan: scrub repaired 15.8M in 00:01:06 with 0 errors on Fri Dec 23 11:19:08 2022
config:
NAME STATE READ WRITE CKSUM
ztest ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
gpt/tt1 ONLINE 0 0 130
gpt/tt2 ONLINE 0 0 0
errors: No known data errors
从该结果可以看出,gpt/tt1
(da1 一侧)发生了 130 次校验和错误。但由于镜像(mirror)结构,从健康的 gpt/tt2
一侧获取了正确的数据,并在 scrub 过程中修复了损坏的数据。
重新读取数据
为了排除操作系统磁盘缓存的影响,先将池导出再重新导入,并重新进行读取操作:
$ zpool export ztest
$ zpool import ztest
$ hd /ztest/dummy
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
e7ee0000
$
如上所示,尽管存在发生错误的磁盘,但由于镜像结构的作用,依然成功读取到了正确的数据。
$ zpool create -O atime=off ztest mirror gpt/tt1 gpt/tt2
$ dd if=/dev/zero of=/ztest/dummy bs=1m
dd: /ztest/dummy: No space left on device
3711+0 records in
3710+1 records out
3891134464 bytes transferred in 153.170356 secs (25403966 bytes/sec)
$ zpool export ztest
$ dd if=/boot/kernel/kernel of=/dev/gpt/tt1 oseek=1000000
dd: /dev/gpt/tt1: Invalid argument
57311+1 records in
57311+0 records out
29343232 bytes transferred in 21.643263 secs (1355767 bytes/sec)
$ zpool import ztest
$ zpool scrub ztest
$ zpool status ztest
pool: ztest
state: ONLINE
status: One or more devices has experienced an unrecoverable error. An
attempt was made to correct the error. Applications are unaffected.
action: Determine if the device needs to be replaced, and clear the errors
using 'zpool clear' or replace the device with 'zpool replace'.
see: https://openzfs.github.io/openzfs-docs/msg/ZFS-8000-9P
scan: scrub repaired 27.9M in 00:01:06 with 0 errors on Mon Dec 26 10:55:49 2022
config:
NAME STATE READ WRITE CKSUM
ztest ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
gpt/tt1 ONLINE 0 0 227
gpt/tt2 ONLINE 0 0 0
errors: No known data errors
$ zpool export ztest
已经成功导出,因此现在物理上移除 da2 一侧,只使用 da1 一侧进行导入并尝试读取数据。
$ zpool import
pool: ztest
id: 11275383091719095959
state: DEGRADED
status: One or more devices are missing from the system.
action: The pool can be imported despite missing or damaged devices. The
fault tolerance of the pool may be compromised if imported.
see: https://openzfs.github.io/openzfs-docs/msg/ZFS-8000-2Q
config:
ztest DEGRADED
mirror-0 DEGRADED
gpt/tt1 ONLINE
gpt/tt2 UNAVAIL cannot open
$ zpool import ztest
$ hd /ztest/dummy
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
e7ee0000
$ zpool export ztest
成功读取了损坏侧 da1 上的所有数据,完全没有问题。也就是说,通过 scrub 操作,数据被写回并得到了修复。而且实际在导入之后通过 status
确认,CKSUM 的值也确实是 0(可惜没来得及截图……)。
接下来,再次为已经导出的 ztest 连接上 da2,并尝试导入。
$ zpool import ztest
$ zpool status ztest
pool: ztest
state: ONLINE
scan: scrub repaired 27.9M in 00:01:06 with 0 errors on Mon Dec 26 10:55:49 2022
config:
NAME STATE READ WRITE CKSUM
ztest ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
gpt/tt1 ONLINE 0 0 0
gpt/tt2 ONLINE 0 0 0
errors: No known data errors
$
如上所示,错误已经被清除。
总结
通过本次实验可以确认,在 ZFS 中将磁盘配置为镜像或 RAID-Z 结构,可以避免部分数据错误的影响。同时也验证了,对于可以修复的数据,执行 scrub 后能够恢复为原来的状态。
不仅仅是 ZFS,其他的卷管理器或 RAID 系统,如果正确配置了镜像或 RAID,也同样能够避免部分数据错误。而是否能够实现自动修复,则取决于各个系统的具体实现。
最后更新于
这有帮助吗?