第三章:池

ZFS池(或zpools)位于ZFS堆栈中间,连接较低级别的虚拟设备与用户可见的文件系统。

池是许多文件系统级任务发生的地方,比如分配存储块。

在ZFS池级别,可以增加ZFS数据集的可用空间,或添加特殊的虚拟设备以提高读写性能。

ZFS块(blocks)

传统文件系统将数据以固定大小的块放置在磁盘上。文件系统使用特殊块——inode——检索哪些块属于哪些文件。

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

ZFS使用一个成为uberblock的特殊块来存储指向文件系统根的指针。

当一个块发生变化时,它会用修改后的数据写入该块的全新副本。数据池为uberblocks保留128个块,随着底层池的变化按顺序使用。当最后一个uberblock被使用时,ZFS循环回到开头。

ZFS将包含文件系统元数据和池数据等重要信息的块复制到多个ditto块中。如果主块损坏,ZFS会检查ditto块以获取备份副本。ditto块会尽可能的分开存储。

 

条带,阵列和池

ZFS池在虚拟设备上条带化数据,传统的RAID在物理设备上条带化数据。

条带是写入单个设备的数据库。大多数传统RAID使用128KB的条带大小。当文件写入传统RAID设备时,RAID软件通常以128KB的块并行写入每个驱动器。

硬件的容量和软件的限制极大地限制了条带的大小。

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

ZFS数据集使用默认的128KB条带大小,但可以动态更改该条带大小以适应设备和工作复制。

如果32KB的条带大小对特定的数据块有意义,而64KB对另一块数据有意义,ZFS会为每一块数据使用适当的大小。ZFS开发人员已经完成最大1MB条带大小的支持,此功能包含在FreeBSD10.2以及更高版本中。

扩充ZFS池时,不能向RAID-Z VDEV添加存储设备,只能向池中添加VDEV。 RAID-Z VDEV中的提供者数量在创建时就固定下来了。

可以通过更换更大的提供者扩容。扩展zpool意味着向池中添加更多的VDEV。

注意,池不提供任何冗余,所有ZFS冗余都来自底层的VDEV。

查看池

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

第一列给出池名称。 接下来三列给出了每个池的大小、使用空间量和可用空间量。 (FreeBSD14中,此处还有一列名为CKPOINT,表示checkpoint,检查点) EXPANDSZ列显示基础存储提供者是否有可用空间。可以扩展此池中的空间量。 FRAG列显示池中的碎片量。碎片化会降低文件系统性能。 CAP列显示可用空间的百分比。 DEDUP显示文件系统上发生的重复数据删除量。 HEALTH列反映底层的VDEV的状态。 ALTROOT显示此池的装载位置,或其“备用根”。

-v选项可以显示更多详细信息,包括底层驱动器的利用率:

-p选项以字节为单位显示数字,-H选项不显示列标题。 这两个选项对自动化和管理脚本非常有用。

zpool status -x命令可以简要检查池状态。

多VDEVS

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

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

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

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

移除VDEVS

当前无法从池中删除VDEV,因为每个VDEV中都有数据。

可以从某些类型地VDEV中删除磁盘,但作为一个整体,VDEV不能被删除。

强制从池中删除VDEV会破坏池,从条带或镜像池中删除VDEV的能力预计将在2015年底的OpenZFS中实现。

支持删除RAID-Z设备的工作尚未开始。

若要缩小池,必须将该池上的数据移动到一个新的、更小的池中,然后从原始池中回收磁盘。

池对齐和磁盘扇区大小

如果池没有使用正确的扇区大小,或者ZFS的扇区没有与磁盘上的物理扇区对齐,存储的性能将下降一半或更多。

分区对齐

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

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

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

ZFS扇区大小

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

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

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

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

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

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

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

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

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

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

FreeBSD 10.1 和更新版本的 Ashift

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

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

旧版 FreeBSD 的 Ashift

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

(此节掠过)

创建池和 VDEVs

使用zpool命令创建池的时候同时也创建虚拟设备。

使用zpool命令可以将VDEV添加到现有池并替换失效的设备。

本节在每个RAID-Z设备上创建条带池、镜像和每个RAID-Z设备的池。

以下示例中使用6个1TB驱动器,每个驱动器都使用gpart创建了一个1GB的交换分区和一个大型的ZFS分区,

单个驱动器

创建分区:

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

 

条带池

有些存储池不需要冗余,而是需要大容量空间。例如工程和物理计算的划痕分区。

以下示例创建一个包含5个存储提供者的池:

以上创建的池包含五个提供者,每个提供者都有自己的VDEV。

该池在所有成员VDEV上条带化数据,但VDEV没有冗余。

镜像池

在使用zpool create命令时,在指定存储设备前使用mirror关键字可以创建镜像池:

mirror-0是一个VDEV,这个VDEV中有两个设备:gpt/zfs0和gpt/zfs1。

也可以创建含有多个磁盘的镜像池:

不过,四个硬盘组成一个镜像,似乎有点过分了。

RAID-Z Pools

RAID-Z以复杂性为代价提供了更大的灵活性。

创建RAID-Z池就像创建其他池一样:使用zpool create命令,并给出池名、类型和存储设备。例如:

以下示例创建一个RAID-3池,池中包含5个磁盘:

 

Multi-VDEV Pools

zpool create命令可以接受多个关键字,每个关键字后面列出的任何存储提供者都将用于创建该VDEV的新实力。比如:

以上,zpool create barrel告诉zpool创建一个新池,名称为barrel;关键词mirror用于创建一个镜像VDEV,使用gpt/zfs0和gpt/zfs1。关键词mirror再次出现,则表示用后面两个磁盘再创建一个镜像VDEV。

池barrel就包含了两个VDEVs,mirror-0和mirror-1。每个VDEV包含两个存储设备。

ZFS会自动对池中所有VDEV进行条带化。镜像后条带化就相当于RAID-10。

以下示例创建一个跨两个RAID-Z1 VDEV条带化数据的池:

第一个RAID-Z1 VDEV包含三个存储提供者gpt/zfs0、gpt/zfs1、gpt/zfs2; 第二个RAID-Z1 VDEV包含三个存储提供者gpt/zfs3、gpt/zfs4、gpt/zfs5; 池vat条带化以上两个VDEV,组成包括两个RAID-Z设备的池。

每个VDEV都有自己的冗余。

虽然镜像比RAIDZ快,但多个VDEV的额外速度使这个基于RAIDZ的池足够快,并可提供更多的空间。

使用多个VDEV可以提高IOPS和吞吐量带宽。

使用日志和缓存设备

使用读缓存设备和写缓存设备可以提高池的性能。

zpool命令中将写缓存叫做log,将读缓存叫做cache。例如:

在需要高可用性的系统上,可以镜像写缓存以防止数据丢失。

镜像读缓存没有太大意义,而可以用条带化来提高读取速度。

如果不确定是否需要读/写缓存来提高性能,可以在没有缓存的情况下运行池,以评估是否实际需要缓存。但要确保硬件有空间添加SSD存储设备。

日志和缓存可以随时添加到池中,也可以将其删除。

不匹配的VDEVs

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

zpool命令指出了错误,但是使用-f参数可以强制创建。

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

再利用提供者

若一个磁盘已经被某个池使用过,ZFS标签还保留在磁盘上,当擦除并重建分区表时,新的分区表恰好与前一个完全相同,这种情况下,ZFS很容易找到旧的元数据。

若再次使用它创建池时会收到提示,例如:

如果确认这个磁盘没有任何重要数据,可以使用-f参数强制创建:

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

ZFS程序对命令行标志的位置非常挑剔,各参数的位置不能随意调换。

Pool 完整性

fsck属于离线文件检查器,不会改善ZFS。

ZFS 完整性

任何文件系统都无法阻止底层硬件中的错误。比如写入错误、电源故障、电缆短路等等。

ZFS几乎在所有地方都使用哈希(hash)

哈希是一种数学算法,它获取一块数据并从中计算出一个固定长度的字符串。

哈希的有趣之处在于,原始数据的微小变化会极大改变数据的哈希值。

每个存储块都包含其父块的哈希,而每个父块都包含其所有子块的哈希。

虽然ZFS无法防止存储提供者发生错误,但它可以使用这些哈希值来检测它们。

每当系统访问数据时,它都会验证校验和(checksum)。

如果数据出现错误,ZFS将使用池的冗余来修复此错误,然后将更正后的文件提供给操作系统。这被称为自我修复。

如果底层VDEV具有冗余,ZFS要么从RAID-Z重建损坏的块,要么从镜像中获取完整的副本。如果镜像的两侧都有错误,只要两个磁盘上相同数据都不坏,ZFS就可以恢复文件。

如果VDEV没有冗余,但数据集有额外的数据副本,ZFS会使用这些额外的副本。

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

ZFS在执行文件完整性检查的同时,还验证存储块之间的连接。ZFS只检查实际存在的块。

要对池中所有数据执行完整性检查,可使用zpool的scrub子命令。

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

擦洗(Scrubbing) ZFS

ZFS池的擦洗验证池中每个数据块的加密哈希。如果擦洗检测到错误,它会尝试修复错误。

擦洗是在线进行的,会影响存储运行速度,应在非高峰时段擦洗池。

可以使用 zpool scrub -s [poolname] 停止正在运行的擦洗。

擦洗频率

擦洗频率由硬件质量决定。比如服务器级的硬盘,每季度擦洗一次就足够了。如果是廉价的硬件,建议每月擦洗一次。

池的属性

ZFS使用属性来表示池的特征。包括池的运行状态、大小、容量、功能等。

数据集的属性和池的属性无关。

池的属性会影响整个池。

 

查看池属性

zpool get all命令可以查看所有池的所有属性。后面加上池名称,则可以查看指定池的所有属性。

前两列是池名称和属性名称。

第三列列出属性的值。这些值可能是enabled或disabled,on或off,active或inactive,也可以是某个值。

第四列显示了此属性的设置位置。可以是个破折号,也可以是default或local。 破折号表示此属性本身没有设置,而是以某种方式从池中读取; default表示此属性设置为默认值; local表示此属性已再此池上专门设置。

也可以获取某个单独的属性:

变更池属性

使用zpool命令的set子命令可以设置池的属性。

注意这里的SOURCE列,默认情况下池没有comment。现在设置了comment,SOURCE就变成了local。

一旦SOURCE从default变成了local,它就永远都是local了,即使将其属性值设置回默认的值也无法改变SOURCE。

可以在创建池的时候使用-o选项来设置某个属性的值。

此例中新建的池altroot属性设置为/mnt,此池的根数据集的canmount属性设置off。

如果属性更改了数据的写入方式,则只有更改后写入的数据才会受到影响,已有数据不会被套用到新属性。

池操作历史

每个zpool都保留了对池所做的所有更改的副本,一直可以追溯到池创建的时候。

此历史不包括系统开关机等常规事件。

遗憾的是,池历史并不能跟踪是谁做了每一个更改。

Zpool 自动维护

FreeBSD使用periodic命令对每个系统的文件系统进行日常维护。

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

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

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

可以让FreeBSD对池进行擦洗。以下设置为没35天自动擦洗每个池:

FreeBSD默认擦洗所有池,可以使用以下设置来指定要擦洗的池,而未被指定的池将不会被擦洗:

要修改擦洗间隔天数,可以做以下设置(每10天):

要按照不同的计划擦洗特定的池,可设置daily_scrub_zfs_<池名称> _threshold来实现:

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

删除池

以下命令删除池test:

销毁池的操作不会擦除磁盘上的资料。销毁的池可能被恢复。

如果需要安全擦除或覆盖磁盘上的数据,则需要一个磁盘覆盖或粉碎程序。

Zpool 功能标志

ZFS和池都有一个版本号,表示池支持的功能。不同版本的ZFS存在一些功能差异。

自从Oracle闭源了ZFS之后,不同组织做出的版本号可能变得互相不兼容。

为了适应不同平台上所有不同的OpenZFS开发人员,选择用“功能标志”(feature flag)替代版本号。

每个运行OpenZFS的平台都包含一个zpool-features手册页。

使用功能通常会以某种方式更改磁盘上的格式。例如添加快照支持意味着添加新的字段和元数据来表示“这是一个快照”。

如果经常在不同系统间交换磁盘,在升级或启用新功能标志之前,应该仔细检查各种主机上支持的功能标志。

查看功能标志

以下操作查看功能标志:

enabled表示此功能可用,但尚未实际使用。此池可以在不支持该功能的系统上导入。

disabled表示此功能在操作系统层面可用,但被禁用了。此池绝对可以在不支持该功能的系统上导入。

active则表示磁盘上的格式已经更改,该功能已经在使用中。此池无法被导入到不支持该功能的系统上。强行导入将销毁所有设用了该功能的数据集,然后该功能被设置为enabled。

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

创建池可以启用该操作系统ZFS实现支持的所有功能。使用-d参数来禁用所有的功能,然后再选择性地启用功能。