第三章:池

ZFS池或称 zpools 形成ZFS堆栈的中间,将较低级别的虚拟设备连接到用户可见的文件系统。池是许多文件系统级任务发生的地方,例如分配存储块。在ZFS池级别,您可以增加ZFS数据集的可用空间量,或添加特殊的虚拟设备以提高读写性能。

第三章:池ZFS块(blocks)条带,阵列和池查看池多VDEVS移除VDEVS池对齐和磁盘扇区大小分区对齐ZFS扇区大小FreeBSD 10.1 和更新版本的 Ashift旧版 FreeBSD 的 Ashift创建池和 VDEVs单个驱动器条带池镜像池RAID-Z PoolsMulti-VDEV Pools使用日志和缓存设备不匹配的VDEVs再利用提供者Pool 完整性ZFS 完整性擦洗(Scrubbing) ZFS擦洗频率池的属性查看池属性变更池属性池历史Zpool 维护自动化删除池Zpool 功能标志查看功能标志

ZFS块(blocks)

传统的文件系统,如UFS和extfs,将数据以固定大小的块放置在磁盘上。文件系统有特殊的块,称为 inode,用于索引哪些块属于哪些文件。即使是NTFS和FAT等非Unix文件系统也使用类似的结构。这是整个行业的标准。

ZFS不预先配置特殊索引块。它只使用存储块,也称为条带。每个块都包含索引信息,用于将该块链接到树中磁盘上的其他块。ZFS计算块中所有信息的哈希值,并将信息存储在块和父块中。每个区块本身都是一个完整的单元。文件可能部分缺失,但存在的内容是连贯的。

没有专用的特殊索引块听起来很棒,但ZFS肯定需要从某个地方开始!每个数据树都需要一个根。ZFS使用一个称为 uberblock 的特殊块来存储指向文件系统根的指针。ZFS从不改变磁盘上的数据——相反,当一个块发生变化时,它会用修改后的数据写入该块的全新副本。(我们将在第6章深入讨论这种写时复制行为。)数据池为uberbocks保留128个块,随着底层池的变化按顺序使用。当最后一个uberblock被使用时,ZFS会循环回到开头。

uberblocks并不是唯一的关键块。ZFS将包含文件系统元数据和池数据等重要信息的块复制到多个 ditto blocks 中。如果主块损坏,ZFS会检查ditto块以获取备份副本。ditto块尽可能地彼此分开存储,可以存储在单独的磁盘上,也可以存储在单个磁盘的单独部分上。(ZFS没有查看磁盘硬件布局的特殊能力,但它做出了大胆的猜测。)

ZFS将更改提交到事务组(或称txg)中的存储介质。事务组包含多个批处理更改,并且具有递增的64位数字。每个事务组使用行中的下一个uberblock。ZFS通过查找具有最高事务号的uberblock来识别128组中最新的uberblock。

ZFS确实使用一些块进行索引,但这些znodes和dnodes可以使用池中的任何存储块。它们与创建文件系统时分配的UFS2或extfs索引节点不同。

条带,阵列和池

你肯定听说过与存储有关的单词 stripe,可能很多次了。ZFS池在虚拟设备上“条带化”数据。传统的RAID在物理设备上“条带化”数据。什么是条纹,它是如何进入池的?

条带是写入单个设备的数据块。大多数传统RAID使用128KB的条带大小。当您将文件写入传统RAID设备时,RAID软件通常以128 KB的块并行写入每个驱动器。类似地,从传统RAID阵列的读取以条带大小的增量进行。虽然您可以自定义条带大小以适应服务器的工作负载,但硬件的容量和软件的限制极大地限制了条带大小。

条带不提供任何冗余。传统RAID通过奇偶校验和/或镜像获得冗余。ZFS池从底层VDEV获得任何冗余。

ZFS在火箭驱动的溜冰鞋上涂上条纹。ZFS数据集使用默认的128KB条带大小,但ZFS足够智能,可以动态更改该条带大小以适应设备和工作负载。如果32KB的条带大小对特定数据块有意义,但64KB对另一块数据有意义,ZFS会为每一块数据使用适当的大小。ZFS开发人员已经完成了对最大1 MB条带大小的支持。此功能已在FreeBSD CURRENT中可用,预计将包含在FreeBSD 10.2及更高版本中。

ZFS池比传统RAID具有更大的灵活性。传统的RAID具有固定且不灵活的数据布局(尽管一些硬件供应商拥有自己的专有RAID系统,具有更大的灵活性)。RAID软件以确定的顺序写入每个磁盘。ZFS具有更大的灵活性。如果您有一个五磁盘的传统RAID阵列,则该阵列将始终有五个磁盘。您无法通过添加磁盘来更改阵列。虽然您可以将磁盘更换为更大的磁盘,但这样做不会改变阵列的大小。创建RAID设备会使阵列的基本特性僵化。

ZFS池不仅可以容忍更改,而且还可以轻松接受添加。如果您有一个包含五个VDEV的ZFS池,并且您想添加第六个,那很好。ZFS接受VDEV,并开始对该设备上的数据进行条带化,而不会闪烁。您不能向RAID-Z VDEV添加存储,只能向池中添加VDEV。RAID-Z VDEV中的提供者数量在创建时是固定的。

然而,使用ZFS,该虚拟设备可以是ZFS支持的任何类型的VDEV。取两个镜像对的VDEV。把它们放在一个zpool里。ZFS将数据条带化。在传统RAID中,镜像顶部的条带称为RAID-10。对于大多数使用情况,RAID-10是您可以拥有的最高性能RAID。然而,在传统RAID-10具有固定大小的情况下,您可以向池中添加额外的VDEV。扩展RAID-10意味着备份数据、向RAID阵列添加磁盘以及恢复数据。扩展zpool意味着向池中添加更多VDEV。RAID-10还允许最多两个磁盘的深度,而ZFS允许的深度高达 264

不过,请记住,池不提供任何冗余。所有ZFS冗余都来自底层VDEV。

查看池

运行 zpool list 命令查看系统中所有池:

如果你想知道一个或多个特定池的信息,请在 zpool list 后列出池名称。以下示例仅显示存储池 prodtest 的输出:

如果您想了解有关池的更多详细信息,包括底层驱动器的利用率,请添加 -v 选项。您必须在任何池名称之前提供选项。

-p 标志以字节为单位打印数字,而不是更人性化的格式,-H 消除了列标题。这些选项对于自动化和管理脚本非常有用。

要获得系统池的更详细视图,包括底层VDEV布局,请使用 zpool status 。在创建池时,我们将看到许多 zpool status 的示例。

要简要检查您的池,请运行 zpool status -x

有时,这就是你所需要的。

多VDEVS

一个池可以包含多个VDEV。添加VDEV不仅可以增加池中的可用空间,还可以提高性能。池在VDEV之间拆分所有写入。一个小文件可能只需要一个条带,这将在一个VDEV上进行,但如果你要写一大堆小文件,ZFS会在VDEV之间划分写入。

第2章讨论了各种VDEV类型的性能。这种表现渗透到游泳池层面。如果您从多个VDEV读取大文件,则文件读取将在最后一个(通常是最慢的)驱动器调用完其数据部分后完成。但是,如果您的池中包含多个VDEV,那么最慢的驱动器只包含文件的一小部分,从而在一定程度上减少了访问它所需的时间。请记住,从存储提供商读取数据的最慢部分是将磁头查找到正确的磁盘段以进行调用,因此这并不像将时间除以VDEV的数量那么简单,但池中的其他VDEV确实可以提高性能。

最佳实践要求在池中仅使用相同的存储VDEV。如果您的池中有一堆镜像VDEV,请不要向池中添加RAID-Z3设备。混合存储VDEV严重影响了池性能,并使ZFS在设备之间最佳地传播数据时工作更努力。你可以这样做,但你不应该这样做。


一个池可以包含多个VDEV,添加VDEV不仅可以增加池中的可用空间,还可以提高性能。

同一个池中的多个VDEV会被池条带化。

最佳实践要求在池中仅使用相同的存储VDEV。即:如果池中有若干镜像VDEV,则不应该再向池中添加RAID-Z3设备。

混合存储VDEV会严重影响池性能,并使ZFS在设备之间最佳地传播数据时更加困难。

移除VDEVS

您当前无法从池中删除VDEV。每个VDEV上都有数据。您可以从某些类型的VDEV中删除磁盘,但VDEV作为一个整体上有关键的池数据。例如,您可以从镜像VDEV中移除磁盘,但不能从池中删除整个VDEV。通常,只有当VDEV发生故障时,您才会从VDEV中删除磁盘。强制从池中删除VDEV(例如,通过拉取存储提供商)会破坏池。从条带或镜像池中删除VDEV的能力预计将于2015年底在OpenZFS中实现,但目前还不可能。支持删除RAID-Z设备已在路线图上,但工作尚未开始。

这意味着你不能缩小池。如果你想让池变小,你必须将该池上的数据移动到一个新的、更小的池中,然后从原始池中回收磁盘。

池对齐和磁盘扇区大小

ZFS希望对存储介质有深入的了解,包括底层提供商的扇区大小。如果您的池没有使用正确的扇区大小,或者ZFS的扇区没有与磁盘上的物理扇区对齐,您的存储性能将下降一半或更多。这些都是正交问题,但如果不为其中任何一个做好计划,都会破坏你的系统。

我们将分别讨论分区对齐和ZFS扇区大小。

分区对齐

磁盘报告其扇区大小,所以这不是问题——除非是这样。许多磁盘报告它们有512字节的扇区,但它们实际上有4096字节(4K)的扇区。《FreeBSD Mastery:Storage Essentials》深入讨论了这一点,所以我们在这里不会详细介绍。

旧的分区管理方案,如古老的主引导记录(MBR),包括各种复杂的数学运算,以确保磁盘分区符合磁盘的物理特性。像GUID分区表(GPT)这样的现代分区方案知道物理磁盘使用分叉舌(forked tongue)说话,而那些基于MBR的旧限制是完全虚假的,因此只需要分区填充完整的扇区。

但是,当磁盘对它的扇区大小撒谎时, gpart(8) 允许您创建在物理扇区中途开始或结束的分区。每次读取或写入磁盘都需要接触两个物理扇区。这对性能造成了严重破坏。

某些SSD还希望分区沿着128 KB或1 MB的边界对齐。

避免对齐问题的简单方法是使所有GPT分区在兆字节边界上开始和结束。将 -a 1m 参数添加到 gpart add 命令中。


许多磁盘报告它们有512字节的扇区,但实际上是4K字节的扇区。

某些SSD使用128KB或1MB的边界对齐。

避免对齐问题的简单方法是使所有GPT分区在兆字节边界上开始和结束。在 gpart add 命令后加上 -a 1m 参数来达成此效果。例如:

ZFS扇区大小

ZFS默认采用512字节的扇区大小。在具有512字节物理扇区的磁盘上使用512字节的文件系统扇区大小是完全可以的。在4K扇区磁盘上使用512字节的文件系统扇区会使硬件工作更加困难。假设您想在这样的磁盘上写入4KB的数据。与其告诉硬盘驱动器写入单个物理扇区,不如告诉硬盘驱动器修改扇区的前八分之一,然后是第二八分之一、第三个,依此类推。对4KB扇区进行512字节的写入意味着读取整个4KB,修改小部分,然后将其写回。这比覆盖整个扇区要慢得多。你的表现一落千丈。如果ZFS在具有512字节扇区的磁盘上使用4K扇区大小,则磁盘硬件会将访问请求分解为物理扇区大小,而性能成本非常低。

虽然使用较大的扇区大小不会影响性能,但当您存储许多小文件时,它确实会降低空间效率。如果你有一大堆1KB的文件,每个文件都占用一个扇区。

ZFS扇区大小是池中每个虚拟设备的属性。即使在导出池或更换故障驱动器时,也无法更改虚拟设备的扇区大小。

综合来看,这两个事实意味着,无论底层磁盘报告的扇区大小如何,强制ZFS使用4K扇区几乎总是可取的。使用更大的ZFS扇区大小不会影响性能,除非在某些特定的数据库操作中,即使如此,也只有在使用真正使用512字节扇区的磁盘时才会影响性能。

ZFS使用报告最大扇区大小的设备的扇区大小。如果您的所有设备都声称使用512字节扇区,并且您没有设置更大的扇区大小,则由这些设备构建的虚拟设备将使用512字节的扇区。在VDEV中包含一个具有4096字节扇区的单个设备会强制ZFS使用4096字节扇区。

不要相信你的4K扇区设备会报告它们的扇区大小。告诉ZFS坚持使用4K扇区。

一个名为 ashift 的池变量控制扇区大小。ashift 的值为 9,告诉ZFS使用512字节的扇区;ashift 的值为 12 ,告诉ZFS使用4096字节的扇区。(为什么是9和12?29 是512,而 212 是4096。)你设置 ashift 的方式取决于你的FreeBSD版本。


ZFS默认使用512字节的扇区大小。

在4K扇区磁盘上使用512字节的文件系统扇区会加重硬件工作的困难。

在512字节扇区的磁盘上使用4K文件系统扇区则会降低性能成本。

使用较大的扇区不会影响性能,但在存储大量小文件时会降低空间利用率。

ZFS扇区大小是池中每个虚拟设备的属性。即使在导出池或更换故障驱动器时,也无法更改虚拟设备的扇区大小。

综合来看,无论底层磁盘报告的扇区大小是多少,强制ZFS使用4K扇区都是可取的。

使用更大的ZFS扇区不会影响性能。

在VDEV中包含一个具有4K字节扇区的单个设备,会强制ZFS使用4K扇区。

不要相信4K扇区设备报告的扇区大小,告诉ZFS强制使用4K扇区。

池的ashift变量控制扇区大小,值为9将ZFS设置为512字节扇区(29),值为12则将ZFS设置为4K扇区(212)。

在使用 zpool get ashift zroot 命令查看zroot池的 ashift 属性时会返回 0

使用 zdb zroot 命令则可看到 ashift 的值为 12

FreeBSD 10.1 和更新版本的 Ashift

/etc/sysctl.conf 或命令行中使用 sysctl vfs.zfs.min_auto_ashift 设置系统的默认 ashift

修改/etc/sysctl.conf或使用以下命令可以设置默认的ashift:

(默认就是12,如无特殊需求,不需要更改)

在安装过程中使用命令行,但也要在 /etc/sysctl.conf 中永久设置它,这样在创建新池时就不会忘记。

本书的示例假设您使用的是FreeBSD 10.1或更高版本。对于较旧的FreeBSD版本,每次都需要设置 ashift ,而不是设置 sysctl

旧版 FreeBSD 的 Ashift

早于10.1的FreeBSD版本缺少较新FreeBSD版本中的 ashift sysctl ,因此您必须依赖ZFS的内部扇区大小检测代码。此代码从底层存储介质(即存储提供者)读取扇区大小。

此案例突出了提供者和磁盘之间的关键区别。FreeBSD允许您使用GEOM模块 gnop(8) 创建直通设备。gnop模块允许您在存储设备之间插入任意数据——在本例中,强制执行扇区大小。你创建了一个gnop设备,上面写着:“透明地传递所有内容,但坚持4096字节的扇区大小。”使用该设备创建你的zpool。在下面示例中,我们将一个gnop设备添加到标记为 /dev/gpt/zfs0 的分区中。

这将创建设备 /dev/gpt/zfs0.nop 。将此提供者用作VDEV的一个成员,ZFS将获取该VDEV的扇区大小。本章的其余部分将讨论创建各种ZFS池,但这里有一个在创建镜像池时使用此设备的示例。

使用 gnop(8) 创建的提供者是临时的,在重新启动时消失。然而,当 gnop(8) 将所有内容传递给设备时,ZFS将在底层设备上找到元数据。ZFS将不再尝试检测磁盘的扇区大小,因为它已经设置了扇区大小。

创建池和 VDEVs

使用 zpool(8) 同时创建池和虚拟设备。您还将使用 zpool(8) 将VDEV添加到现有池中并更换故障设备,但我们将在第5章中介绍所有这些。在这里,我们将在每个RAID-Z设备上创建条带池、镜像和池。第2章讨论了每种VDEV类型。

在创建任意数量的池之前,您只需要设置一次 ashift 。您不必每次创建池时都重置它。不过,我们希望大多数读者跳过这本书,直到找到他们创建的池类型的条目,所以我们在所有条目中都列出了“set ashift”。

单个驱动器

第0章建议按物理位置和序列号标记驱动器,以便您可以轻松识别故障硬件。对于生产来说,这非常有用。然而,对于一本书来说,较长的设备名称会使理解变得更加困难。我们的示例使用 zfs 的GPT标签和一个数字。本章使用六个1TB驱动器,每个驱动器都有一个1GB的交换分区和一个大型ZFS分区,使用 gpart(8) 创建。

以上gpart add命令使用的参数含义为:

我们使用GPT标签管理ZFS池,因此示例引用了 gpt/zfs0gpt/zfs5 。在生产中,应使用有意义的标签将磁盘映射到物理位置。

条带池

一些存储池不需要冗余,但确实需要大量空间。工程和物理计算的划痕分区是这种存储的常见用例。使用 zpool create 、池名称并列出池中的设备。在创建池之前,记得设置 ashift

以下,我们创建了一个由五个存储提供者组成的池。

所有五个提供者都出现了。每个提供者都有自己的VDEV。对于如此规模的系统来说,这是一个很大的池。

该池在所有成员VDEV上条带化数据,但VDEV没有冗余。大多数现实世界的应用程序都需要冗余。最简单的冗余是镜像。

镜像池

镜像设备将所有数据复制到多个存储提供者。如果镜像上的任何一个提供者发生故障,池中仍有另一个数据副本。传统的镜像有两个磁盘,当然也可能有更多磁盘。

使用相同的 zpool create 命令和池名称。在列出存储设备之前,请使用 mirror 关键字。在创建池之前,先设置系统的 ashift

zpool命令在这里创建了一个新层(layer),称为 mirror-0mirror-0 条目是VDEV。该VDEV包含两个设备,gpt/zfs0gpt/zfs1

如果这符合您的需求,您当然可以拥有一个带有许多磁盘的镜像。副本太多总比不够好。

然而,这可能是一个走得太远的例子(尽管我们确实在《FreeBSD Mastery:Advanced ZFS》中讨论过将镜像拆分为多个池)。

RAID-Z Pools

从镜像中获得的冗余是快速可靠的,但并不是非常复杂或令人兴奋。RAID-Z以复杂性为代价提供了更大的灵活性,这使它更令人兴奋。创建RAID-Z池就像创建其他zpool一样:运行 zpool create 并给出池名、类型和存储设备。下面我们创建一个RAID-Z(或RAID-Z1)池。新池的状态显示了一个名为 raidz1-0 的新VDEV,有三个提供者。

如果此池中的任何一个磁盘发生故障,数据将保持不变。其他RAID-Z级别甚至有更多的冗余。在这里,我们将六个提供者合并到RAID-Z3中。创建RAID-Z3和RAID-Z1之间的唯一区别是使用 raidz3 和所需的额外设备。

正如你现在可能猜到的,池的状态显示了一个名为 raidz3-0 的新设备。

所有这些池都有一个VDEV。但是,如果你想要多个VDEVs呢?

Multi-VDEV Pools

您可以创建一个包含多个VDEV的池。关键字 mirrorraidzraidz2raidz3 都告诉 zpool(8) 创建一个新的VDEV。在其中一个关键字后列出的任何存储提供者都将用于创建该VDEV的新实例。当其中一个关键字再次出现时,zpool(8) 将以一个新的VDEV开始。

本章的开头介绍了跨多个镜像的条带化,模拟了传统的RAID-10设置。下面这个示例正是这样做的。

前三个单词 zpool create barrel 告诉 zpool(8) 实例化(instantiate)一个名为 barrel 的新池。mirror 关键字表示“创建镜像”。然后我们有两个存储提供者,gpt/zfs0gpt/zfs1 。这些存储提供者进入第一个镜像。单词 mirror 再次出现,告诉 zpool(8) 之前的VDEV已经完成,我们正在开始新的VDEV。第二个VDEV也有两个存储提供者, gpt/zfs2gpt/zfs3 。这个池的状态看起来与我们以前看到的任何东西都不一样。

该池有两个VDEV,mirror-0mirror-1 。每个VDEV包括两个存储设备。我们知道ZFS在所有VDEV上都有条带数据。镜像上的条带是RAID-10。

您还可以以没有通用RAID等效物的方式排列多个VDEV池。虽然像FreeBSD的一些GEOM类这样的软件RAID系统可以让你构建类似的RAID,但你不会在硬件RAID卡上找到它们。在这里,我们创建了一个跨两个RAID-Z1 VDEV条带化数据的池。

每个VDEV都有自己的冗余。

虽然镜像比RAIDZ快,但您可能会发现,拥有多个VDEV的额外速度使这个基于RAIDZ的池对于您的工作负载来说足够快,并为您提供了更多的空间。唯一的方法是创建池并测试工作负载。

记住,池将池中VDEV之间的所有写入请求拆分。一个小文件可能只会访问一个VDEV,但总的来说,写入会在VDEV之间进行分割。使用多个VDEV可以提高IOPS和吞吐量带宽。

使用日志和缓存设备

正如第2章所讨论的,ZFS可以使用专用写缓存设备和/或专用读缓存设备来提高性能。这些专用设备通常是非常快、高耐久性的SSD。zpool(8) 命令将写缓存称为 log ,将读缓存称为 cache

在创建池时,使用 logcache 关键字指定这些设备。在这里,我们创建了一个名为 scratch 的条带池,它同时具有读缓存和写缓存。

在需要高可用性的系统上,您可以镜像这些写缓存。镜像读缓存没有多大意义——如果你丢失了读缓存,ZFS会退回到从实际池中读取。然而,丢失ZIL写日志可能会导致数据丢失,因此镜像是有意义的。在这里,我们使用设备 gpt/zfs0gpt/zfts3 创建了一个由两个镜像组成的条带,其中镜像日志设备为 gpt/zlog0gpt/zlog1

您可以将意图日志和读取缓存设备添加到现有池中,也可以将其删除。如果您不确定是否需要这些设备的性能提升,请尝试在没有它们的情况下运行池。但是,请确保您的硬件有空间稍后添加SSD存储设备!

不匹配的VDEVs

不建议在池中混合使用不同VDEV类型,zpool(8) 会试图防止类似操作。

zpool(8) 命令指出错误,然后告诉您如何坚持。我们通常将这些错误视为表示系统管理员需要更多咖啡因的一种方式,但也许你真的打算这样做。使用指定的VDEV类型和存储提供者运行 zpool create -f 告诉ZFS,是的,你完全打算创建一个格式错误的池。嘿,这是你的系统;你负责(you're in charge)。

如果ZFS不想让你做某事,你可能不应该这样做。当你使用 -f 时,你创建的是ZFS无法处理的东西。您可以轻松创建一个无法正常工作且无法修复的池。

再利用提供者

我们有时会多次创建和销毁池以使其正确。我们可以从一台机器中取出磁盘并将其安装到另一台机器上。有时我们会遇到以前用过的磁盘。

我们在另一个池中使用了此磁盘,稍后导出了该池(请参阅第5章)。该池中使用了问题磁盘,ZFS标签仍保留在磁盘上。当我们擦除并重新创建分区表时,新的分区表恰好与前一个完全相同。在这种情况下,ZFS很容易找到旧的元数据。

如果您绝对确定此提供程序没有任何重要内容,请按照说明操作,并使用 -f 强制创建新池。

ZFS程序对命令行标志的位置可能非常挑剔,因此请确保在 create 之后立即使用 -f


另外,可以使用 zpool labelclear [-f] <vdev> 命令消除掉ZFS标签。

Pool 完整性

关于ZFS的一个常见抱怨是它没有文件系统检查器,比如 fsck(8) 。离线文件检查器不会改善ZFS,因为在线池完整性检查器会验证以前 fsck(8) 检查的所有内容以及更多内容。在线检查器也比传统文件系统 fsck(8) 更有效。让我们谈谈ZFS如何确保文件完整性,然后谈谈池清理如何帮助维护完整性。

ZFS 完整性

存储设备出现故障(screw up,弄糟,扭曲;毁坏,搞乱)。当你在任何类型的磁盘上有数万亿个扇区时,杂散宇宙射线撞击一个扇区的几率会大到足以让它醉醺醺地蹒跚而行,写入错误、电源故障、电缆短路或其他任何问题的几率也会大大增加。任何文件系统都无法阻止底层硬件中的错误。

ZFS几乎在所有地方都使用哈希(hashes)。哈希是一种数学算法,它获取一块数据并从中计算出一个固定长度的字符串。哈希的有趣之处在于,原始数据的微小变化会极大地改变数据的哈希值。每个存储块都包含其父块的哈希,而每个父块都包含其所有子块的哈希。

虽然ZFS无法防止存储提供程序错误,但它使用这些哈希值来检测它们。每当系统访问数据时,它都会验证校验和。ZFS在将更正后的文件提供给操作系统之前,使用池的冗余来修复任何错误。这被称为 self-healing——自我修复。

如果底层VDEV具有冗余,ZFS要么从RAID-Z重建损坏的块,要么从镜像中获取完整的副本。如果镜像的两侧都有错误,只要两个磁盘上的相同数据都不坏,ZFS就可以恢复文件。如果VDEV没有冗余,但数据集有额外的数据副本(见第4章),ZFS会使用这些额外的副本。

如果底层VDEV没有冗余,并且数据集没有保留额外的副本,则池会注意到文件已损坏并返回错误,而不是返回不正确的数据。您可以从备份中还原该文件,也可以将其丢弃。

ZFS在执行文件完整性检查的同时,还验证存储块之间的连接。这是传统文件系统中 fsck(8) 执行的任务。这只是数据验证的一小部分,ZFS作为其正常操作的一部分持续执行此任务。ZFS比 fsck(8) 有一个额外的优势,因为它只检查实际存在的块,而不是已使用和未使用的索引节点。如果要对池中的所有数据执行完整性检查,请对其进行擦除(scrub)。

基于哈希的完整性检查的好处是,它可以捕获各种错误,甚至是意外的错误。记住,快乐的文件系统都是一样的;每个不快乐的文件系统都有自己的不快乐。

擦洗(Scrubbing) ZFS

ZFS池的 scrub 验证池中每个数据块的加密哈希。如果擦洗检测到错误,如果存在足够的弹性,它会修复错误。擦洗发生在池在线和使用时。

如果您的池发现了任何数据错误,它们将显示在zpool的状态中。如果你之前运行过擦洗,你也会在 scan 行上看到这些信息。

此池在访问的数据中没有遇到任何错误。如果它发现了错误,它就会自我修复。然而,池并没有检查所有数据是否有错误,它只检查了被要求的数据。要有条不紊地搜索整个池中的错误,请使用 scrub 。运行 zpool scrub 和池名称。

擦洗在后台进行。您可以通过运行 zpool status 来查看他们的表现。

ZFS池清理其存储运行速度比平时慢。如果您的系统已经达到了性能极限,请仅在非高峰时段使用清理池。如果您必须取消正在进行的擦洗,请运行 zpool scrub -s

一定要记得回去让系统尽快完成擦洗。

擦洗频率

ZFS内置的完整性测试和弹性意味着大多数错误都是可以修复的,只要它们被及早发现,弹性就可以发挥作用。这意味着你的硬件质量决定了你应该多久清理一次主机的池。如果你有可靠的硬件,比如所谓的“服务器级”(server grade)设备,每季度清理一次就足够了。如果你滥用廉价硬件,你应该每个月左右擦洗一次。

FreeBSD可以为您执行定期的擦洗,如本章后面的【ZFS维护自动化】中所述。

池的属性

ZFS使用属性来表示池的特征。虽然zpool属性的外观和工作方式与数据集的属性非常相似,而且许多属性似乎在两者之间重叠,但数据集属性与池属性没有关系。池属性包括池的运行状况、大小、容量和每个池的功能等事实。

池的属性会影响整个池。如果只想为池的一部分设置属性,请检查符合您需求的每个数据集属性。

查看池属性

要查看系统上所有池的所有属性,请运行 zpool get all 。如果只需要特定池上的属性,可以在末尾添加池名称。下面,我们来看看池 zroot 的属性。

前两列给出池名称和属性名称。

第三列列出了属性的值。这可以是 enableddisabledonoffactiveinactive ,也可以是一个值。此池的 size 属性为920G——此池有920GB的空间。

SOURCE 列显示了此属性的设置位置。这可以是单个破折号,也可以是单词 defaultlocal 。破折号表示此属性本身没有设置,而是以某种方式从池中读取。您不能设置池的大小或使用了多少空间的值。FreeBSD从池中计算这些值。 defaultSOURCE 表示此属性设置为默认值,而 local 表示此属性已在此池上专门设置。

要获取单个属性,请使用属性名称运行 zpool get

通过在末尾给出池名可缩小范围。

变更池属性

我们将在本书中设置属性以更改池行为。使用 zpool set 命令更改池的属性。下面我们设置了一个池的 comment 属性。

请注意此处的 SOURCE 列。默认情况下,池没有注释。不过,现在我已经设置了一个注释,SOURCE 更改为 local 。一旦属性的来源从 default 更改为 local ,它将永远保持 local 状态。即使将属性设置为默认值也不会更改源。

我们在本地将注释设置为默认值,因此值的来源仍然是本地的。

您可以在创建时使用 -o 设置池的属性。您可以使用 -o 设置该池上根数据集的属性。

该池的 altroot 属性设置为 /mnt ,此池上的根数据集的 canmount 属性设置为 off 。如果属性更改了数据的写入方式,则只有更改属性后写入的数据才会受到影响。ZFS不会重写现有数据以符合属性更改

池历史

每个zpool都保留了对池所做的所有更改的副本,一直追溯到池的创建。此历史记录不包括系统开机和关机等常规事件,但它确实包括设置属性、池升级和数据集创建。

要访问历史记录,请运行 zpool history 并给出池名称。

经验丰富的FreeBSD人员可能会从FreeBSD文档和论坛中的任何数量的ZFS教程中认识到这一点。

历史以以下内容结束:

我们更改了 comment 属性,因此它已成为历史。永远。

遗憾的是,池历史记录并不能跟踪谁做了每一个更改,但有一个永久的更改记录有助于问题分析。

Zpool 维护自动化

FreeBSD检查每个系统的文件系统,作为 periodic(8) 运行的日常维护作业的一部分。

可以将ZFS池加入到维护中,以便获取到池的健康信息。在periodic.conf中添加以下行:

periodic(8) 的每日输出将包含 zpool status -x 的输出,简单的一行“all pools are healthy”。

如果希望获取更多的信息,可以设置:

如果您想了解有关池的更多详细信息,每日报告还可以包括 zpool list 输出。将 daily_status_zfs_zpool_list 设置为 YES 以获取列表。如果要修剪该输出,仅显示特定池的状态,请在daily_status_zpool periodic.conf 变量中列出所需的池。

你也可以让FreeBSD执行你的池 scrub 。在设置了清理选项后,FreeBSD每天都会检查池是否需要清理,但只会在配置的时间间隔内进行清理。要每35天自动清理一次每个池,请在 periodic.conf 中将 daily_scrub_zfs_enable 设置为 YES

FreeBSD默认清理所有池。您不能明确地将特定池排除在每日清理检查之外。但是,您可以在 daily_scrub_zfs_pools 中明确列出要检查的池。任何未列出的池都不会被擦洗。

要更改scrub的间隔天数,请将 daily_scrub_zfs_default_threshold 设置为所需的天数。

如果要按照不同的计划清理特定池,请设置 daily_scrub_zfs_${poolname}_threshold 达到所需的天数。在这里,我们每7天擦洗一次池。

任何没有被指定阈值的池,都使用默认阈值。

删除池

要删除池,请使用 zpool destroy 命令和池名称。

销毁将池的底层提供程序标记为已销毁池的一部分,以便它们可以重新用于其他池。它不会擦除磁盘,任何阅读过第5章的人都可以恢复池并访问数据。

如果你必须安全地擦除或覆盖提供者上的数据,你需要一个磁盘覆写或粉碎程序。

Zpool 功能标志

ZFS和池最初都有一个版本号,表示池支持的功能。系统可能会看到一个不熟悉的存储提供者,然后说:“哦,这个池是ZFS版本20,所以它不支持重复数据删除或本机加密。”您可以将池升级到您的版本支持的最新版本,也可以不升级。

然后,Oracle关闭了ZFS源代码,让不同的人自行获取最后一个开源ZFS版本并对其进行维护。最终的开源Oracle ZFS版本是28。随着各个组实现了自己的池功能,来自不同组的版本号有可能变得相互不兼容。不同的ZFS团队可以实现他们选择的任何新功能,这意味着,比如说,FooZFS版本30将与BarZFS版本20不兼容。ZFS的一个主要目标是互操作性。

OpenZFS团队决定,最好的前进方向是摆脱使用版本号跟踪功能。他们将OpenZFS版本提高到5000,为Oracle留下了足够的空间来添加新版本。为了适应所有不同平台上所有不同的OpenZFS开发人员,开发人员选择用功能标志(feature flags)有效地替换版本号。

每个运行OpenZFS的平台,包括FreeBSD,都应该包含一个 zpool-features(7) 手册页,其中列出了此特定安装支持的池功能。FreeBSD的较新版本可能会支持新的池功能。

使用功能通常会以某种方式更改磁盘上的格式。例如,添加快照支持意味着添加新的字段和元数据来表示“这是一个快照”。不支持该功能的系统会查看此池并说“哎呀,我不认识那个数据结构。我不会碰这个!”如果你经常在系统之间交换磁盘,在升级或启用新的功能标志之前,你需要仔细检查各种主机上支持的功能标志。

查看功能标志

要查看池支持的功能标志及其设置,请查找包含单词 feature 的池属性

enabled 的池功能可供使用,但尚未实际使用。您的系统可能支持一种新型压缩,但实际上并没有使用新算法将任何数据写入池中。此池可以在不支持该功能的系统上导入,因为磁盘上的格式尚未更改以适应该功能。新主机不会看到任何让它发疯的东西。

disabled 的池功能在操作系统中可用,但未启用。池中没有任何内容表明这些功能可用——禁用功能的存在意味着它们在操作系统中可用。此池绝对可以在不支持此功能的主机上使用。

如果该功能是 active ,则磁盘上的格式已更改,因为该功能正在使用中。最常见的情况是,此池无法导入到不支持此功能的系统中。如果该功能处于活动状态,但使用该功能的所有数据集都已销毁,则池会将功能设置恢复为 enabled

一些功能是“只读兼容的”。如果该功能正在使用中,则池可能会部分导入到不支持该功能的系统中。新主机可能看不到池上的某些数据集,也无法向池中写入任何数据,但它可能能够从数据集中提取一些数据。

创建池可以启用该操作系统ZFS实现支持的所有功能。您可以在 zpool create 中使用 -d 标志来禁用新池中的所有功能,然后更有选择性地启用功能。

现在您已经了解了池的工作原理,让我们对它们进行一些实际的数据分析。