第十章:杂项

本章涵盖了其他地方不太适合的小主题。

第十章:杂项分离镜像制作更深的镜像分离池SnapSpec快照范围按年龄指定快照屠杀恢复销毁的池可恢复池不可恢复池恢复时重命名池克隆机器大小写不敏感文件系统ZFS 深潜 : zdb(8)块统计详细块统计ZFS 配置数据集信息数据集基本信息数据集细节检查特定对象检查特定文件Metaslabs 和自由空间直方图Uberblock后记

分离镜像

将存储组合到池中是ZFS的核心功能之一。但是ZFS允许您反向执行相同的操作——将镜像池拆分为多个相同的池。如果你想更准确地克隆一台机器,或者提取镜像的副本来运行备份,或者执行其他疯狂的计算机科学,zpool split 是你的朋友。

我们将演示向镜像池中添加磁盘,然后将池拆分为多个副本。您可以反向执行相同的任务:将镜像中的磁盘池化,然后在完成复制后将其添加回来。但是,我们建议在每个镜像VDEV中始终至少维护两个提供程序。

制作更深的镜像

镜像的深度描述了镜像包含的数据副本数量。以下池是一个典型的条带镜像,包含两个镜像,每个镜像有两个磁盘:

我想让这个池更深,为每个VDEV添加一个额外的磁盘。使用带有池名称、目标VDEV中已有的设备和新设备的 zpool attach 命令。

在运行这两个命令之间,池的两个镜像中,一个镜像有三个设备、另一个镜像有两个设备。最后,镜像看起来是这样的:

当池完成数据同步后就可以分离池了。

分离池

使用 zpool split 命令从每个VDEV中提取设备一创建新池。该命令需要两个必需参数:要拆分的池名称和新创建的池的名称。以下示例将 db 池拆出一个 db2 池,这两个池完全相同:

ZFS删除最近添加到每个镜像中的设备以创建新池。拆分池不会自动导入新池。导入后,分割池看起来像这样:

这个池包含两个条带化的磁盘。

在原始池中,条带化的一对三向(三磁盘)镜像已经变成了条带化的一对双磁盘镜像。

如果要持续使用这个分离出来的池,应添加更多的磁盘以创建适当的镜像。如果分离出来的池仅仅是为了提取备份,则可能不需要添加另一组磁盘。

SnapSpec

快照很棒,但它们像tribbes™(毛球族,《星际迷航》中虚构的外星生物)一样繁殖。一旦你习惯了使用快照来处理系统管理问题,你会发现自己因为拥有所有快照而面临磁盘空间短缺的问题。删除单个快照很容易。

但是,如果你想一次销毁10个快照呢?识别每个快照并在命令行上指定每个快照是极其繁琐的。

ZFS允许您使用 snapspec 识别多个相邻快照。你不能使用 snapspec 指定多个不同的快照,但如果你想在 @monday@saturday 取消每日快照,snapspec 是你的朋友。

无论何时使用 snapspec ,我们都建议首先使用 -n-v 运行命令。

结合起来,他们说“给我更多关于这个命令到底会做什么的细节。”由于你可以分配任意的快照名称,最好验证一个需要但被遗忘的快照不会被快照屠杀(massacre)。一旦您确切地知道该命令将销毁哪些快照,请在不使用 –n 的情况下重新运行该命令。

快照范围

最基本的 snapspecfromsnap%tosnap 。它会销毁所指示的两个对象之间的每个快照,包括它们自己:

您是否在编号的快照之间插入了 @beforeUpgrade 快照?这就是为什么你首先使用 -n

按年龄指定

可以使用 @%foo 销毁 @foo 和比它更老的快照:

使用 @foo% 销毁 @foo 和比它更新的快照:

这比重新输入每个快照名称要容易得多。

快照屠杀

有时,你希望销毁所有快照。可使用 @%

现在,你可以重新开始累积快照了。

如果你拍摄了递归拍照,你可能像销毁整个快照树。添加 -R 选项以从数据集及其子数据集中删除快照。例如,我们完成了升级,并对结果感到满意,可以销毁所有以前的快照:

但是,建议把备份多保留一段时间。

恢复销毁的池

人生的高光时刻:等等,那个命令是一个可怕的错误(a horrible mistake)。虽然你意识到了错误,但是,在你那该死的右手小拇指按下回车键之前,神经冲动没有时间从你的大脑跑到你的手上。

一个可能发生的情况是销毁ZFS池。ZFS的设计者非常清楚这种综合症。

zpool destroy 命令实际上不会损坏底层磁盘上的任何数据。相反,它将池标记为已被销毁。zpool list 命令会跳过标记为已销毁的池。如果尚未写入或物理移除已销毁的池的底层磁盘,那就可以恢复池。使用 zpool import -D 命令可以查看已销毁的池:

你可能收到两种响应,分别对应可恢复池和不可恢复池。

可恢复池

下面这个池可以很容易恢复:

这个被销毁的池 db2 看起来像一个正常、健康的池。state 行显示它是 DESTROYED ,但是输出信息显示了池中单个VDEV中的两个提供者,并且它们都在线。

要导入已销毁的池,可以在以上命令后面加上池名称:

另一些池就没那么简单了。

不可恢复池

另一些销毁的池看起来像这样:

此例中 state 行显示为 UNAVAIL ,这意味着无法导入它。config 中显示其中一个提供者也是UNAVAIL。ZFS找不到此池具有ZFS特定GUID的存储提供者。也许是你将该磁盘用在其他地方了,也许是把它从机箱拔出了。

这个例子中最令人烦恼的是什么?这个池是一个双盘镜像池。你有一份关于幸存提供者的数据的完美副本。但是池不完整,所以,无法导入它。根据 action 行的提示,需要找到那个丢失的盘,然后再试。

另一个常见的情况是,池中不再有SLOG设备。SLOG合适的硬件通常会直接在另一个池中重新使用或丢弃。使用 zpool -m 命令可以告诉zpool忽略丢失的SLOG并无论如何都导入池。

恢复时重命名池

有时,当你在恢复销毁的池时,你像重命名它。也许它与当前活动的池同名,也许这个池的功能已经更改。要在导入池时重命名它,请在池的旧名称后面添加新名称:

在其他方面,这与重命名导入的池相同。

克隆机器

ZFS对于虚拟机来说时一个非常明智的选择。虽然失去对磁盘的直接访问意味着ZFS无法处理错误检测,但快照和复制等功能使ZFS变得有价值。

大多数虚拟化系统都提供了克隆服务,有时伪装成你可以部署的标准模板。但是,如果将包含ZFS池的磁盘映像复制到新的虚拟机,则你的虚拟机是原始虚拟机的副本。这意味着一些应该是全局唯一的项目不再是。不在独立的虚拟机上。但是,如果要在虚拟机之间移动磁盘映像,则需要更改每个VM池的GUID。

guid 属性包含池的GUID。这里我们得到池 db2 的GUID:

要生成新的池GUID,请使用 zpool reguid 。将池名称作为参数:

现在,您可以将磁盘映像从一个虚拟机附加到另一个虚拟机器,而不会使ZFS变得非常适合。

如果由于某种原因 zdb(8) 找不到您的池,为池提供一个新的GUID也会有所帮助。

大小写不敏感文件系统

有些客户端(特别是OS X)希望文件系统不区分大小写。可以更改数据集的 casesensitivity 属性。此属性的默认值是 sensitive (敏感),这是类Unix风格的区分大小写属性。将其改为 mixed (混合),ZFS可以支持区分大小写和不区分大小写的请求。若改为 insensitive ,ZFS将完全不区分大小写。

只能在创建数据集时设定该属性

要修改已有数据集的 casesensitivity 属性,就只能创建正确设置的属性创建新数据集,然后复制文件。

ZFS 深潜 : zdb(8)

为了更好地了解ZFS内部发生的事情,能够窥探幕后是有帮助的。检查ZFS的内部状态可以帮助你了解系统为什么以这种方式运行或表现。即使您对香肠的制作方式不太感兴趣,本节也可能包含一些有趣的内容。

许多人阅读了《The Design and Implementation of the FreeBSD Operating System, 2nd Edition》(FreeBSD操作系统的设计与实现,第二版)(Addison-Wesley Professional,2014)的ZFS章节,以了解ZFS的内部数据结构。虽然 D&I 书提供了大量信息,但研究系统上实际文件的数据结构的能力可以帮助一切变得有意义。

FreeBSD中提供的ZFS工具套件包括ZFS调试器 zdb(8)zdb(8) 手册页明确指出:

输出基于磁盘上的内容和 zdb(8) 对任何给定时刻内存中内容的模拟。解释在很大程度上取决于操作员。如果你真的深入研究了 zdb(8) ,你可能需要一份方便的 D&I 副本作为参考。

zdb(8) 命令有大量标志。所有这些都可以多次指定,每次都会增加信息的详细程度。

块统计

zdb(8) 实用程序可以检查块在池中的分配方式。我们将从虚拟机中检查一个非常小的池开始,然后检查越来越大的池。使用 –b 标志和池名称获取该池的块统计信息。

分析块统计信息需要大量内存,因为 zdb(8) 在计算各种统计信息时必须跟踪每个块。一个非常大的池可能需要比主机更多的内存,最终内核的内存不足杀手会出于自卫而终止 zdb 。在生产系统上运行此程序时要小心,因为 zdb(8) 可能会使系统停止运行。

让我们从虚拟机中的一个小型单磁盘池开始。

统计数据的第一部分涵盖了块指针,即包含元数据和数据块索引详细信息的块。

以下是一个来自只有一个磁盘VDEV的活动服务器的稍大的池:

这个池包含大量不可压缩的数据。bp physical 条目显示,压缩系数为1.09。不过,再往下看分配的空间(bp allocated),实际上得到了0.98的压缩率。压缩几乎弥补了元数据造成的空间损失。

下面是一个来自四盘RAID-Z1的例子:

使用四盘RAID-Z1,你会损失25%的物理空间来实现奇偶检验。它有大约15GB的数据,但压缩会将其压缩到10GB。不过看一下分配空间,压缩和RAID-Z元数据实际是相互抵消了。

下面是另一个四盘RAID-Z1,但数据更多:

它存储了大约845GB的数据,但由于是RAID-Z1,所以它实际分配了超过1TB的空间。

(对于我实际的NAS系统,有一个三盘RAID-Z1池。8T的数据,实际分配(bp allocated)了11T的空间,压缩比只有0.69。)

详细块统计

如果这些数字并没有让你的大脑爬出耳道,然后摔死,那就加上第二个 -b来获得详细的块统计数据。

你会得到一个统计表,包含以下列:

最后一行给出了总数。这个池有大约83K个指针块,代表2.01GB的数据。此数据使用856MB的逻辑空间,但由于ZFS添加了填充和元数据,则需要1.08GB。

下面看看Type列出现的这些类型:

ZFS 配置

使用 zdb -C 命令可以查看系统的ZFS配置。如果添加池的名称,zdb将从池中提取信息。如果跳过池, zdb(8) 将显示 zpool.cache 中的所有内容。

这给出了 media 池的基本细节。其中一些信息相对明显,例如池的GUID和使用此池的主机的 hostid 。此池已提交15612202个事务组。vdev_children 字段显示此池中有多少VDEV。

池VDEV信息以树状显示,列出每个VDEV,然后列出该VDEV中的磁盘。这是 media 池中的VDEV:

你还记得在创建VDEV时指定了一个 ashift 吗?在这里你可以找到那是什么。您还将看到VDEV的GUID和VDEV类型,以及其他ZFS内部的详细信息。

VDEV中的每个磁盘都有一个条目。

这告诉您关于ZFS GUID、FreeBSD设备节点和一些内幕。

在最底部,您将看到此池上的只读功能列表:

如果使用第二个 -C 和池名,zdb 将检索磁盘上的数据和缓存的数据,以便进行比较。这些应该有所不同吗?不是真的。

数据集信息

详细检查数据集还可以提供大量信息,并有助于可视化文件系统的内部。

数据集基本信息

使用 zdb -d 加上数据集名称,可以查看指定数据集的内部结构信息:

这并没有提供太多信息。cr_txg 字段显示了创建此数据集的事务组,此池上15612202的编号4820082。它保存了3.82GB数据,其中有259339个对象。对象包括目录和文件,还包括元数据、ACL和ZFS可以保存的所有其他类型的数据。

数据集细节

你想了解更多细节吗?你明白了。再加一个 -d ,然后戴上你的帽子。您可能希望在 script(1) 或其他终端录制程序下运行此程序。

这种情况会持续多久?嗯…很久。

这个数据集中有一大堆文件、目录和相关内容。

系统管理员可能会对其中一些列感兴趣,第一列是编号、dblk 列是用于此对象的记录大小、lsize 是对象的逻辑大小,这是我们大多数人在说“文件大小”时想到的。

第一行提供了基本信息。不过,在那之后,我们会看到数据集中每个对象的详细信息。虽然前几个对象总是ZFS元数据,但后面的对象大多是文件和目录。您将看到分配给对象数据结构的值。

检查特定对象

这个数据集对象列表可能很有趣,但每个对象是什么?通过在数据集名称后指定对象编号来检查对象。在这里,我们将zdb一直调高,并在 media/svn/base 数据集上研究对象1294328。

我们从数据集的细节开始,比如创建txg和对象数量,就像我们在不太密集的 zdb 查询中看到的那样。我们还获得了更多的细节,比如当前的校验和以及其他只有在学习 D&I 时才有意义的东西。

然后我们得到一些关于文件本身的细节。

此文件的大小为4.50 KB,但数据大小(dsize)为8 KB,因为磁盘上的大小是整个扇区。

然后我们将进入文件的核心。

您将看到一堆传统的Unix信息:权限、文件名和路径、时间、父对象等。如果你用 ls 查看这个文件,它看起来是4139字节,但不包括任何支持它的ZFS元数据。

现在让我们考虑一个更大的lz4压缩文件。

这从基本的池信息开始,然后深入到文件本身。虽然此文件占用244 KB的磁盘空间,但其实际大小为896 KB。压缩减少了所需的磁盘空间量。

不过,在Unix信息之后,我们会得到一个间接块列表。

当文件大于recordsize(dblk)时,ZFS将其存储为多个单独的块。第一列是文件中的偏移量,以十六进制表示。十六进制中的数字20000是128KB。所讨论的文件有一个L1间接块,然后是七个实际保存数据的128KB L0块,最后一个实际上稍小。

检查特定文件

也许你想看看一个特定的文件——比如,看看它是用什么块大小写的。你不能轻易地从 zdb –d 中提取它,但你可以通过使用 –i 标志从 ls(1) 中获取序列号或索引节点号。在这里,出于某种难以形容的原因,我们对MySQL数据文件感兴趣。

这是 zroot/var/mysql 数据集上的文件132。

当使用 inode 编号工作时,用 zdb -v 查看此文件:

使用四个或更多-v选项显示传统的Unix信息和间接块。如果你有 ls(1) ,你可能已经有了传统的Unix信息。

Metaslabs 和自由空间直方图

histograms——直方图

每个顶级虚拟设备都被分解为 metaslabs 。ZFS在 metaslab-by-metaslab 基础上填充空间。

当ZFS分配空间时,它会寻找足够大的磁盘块来容纳新事务。当你的池满了,写入数据时唯一的选择就是把它分成剩下的小块空间。则就会造成ZFS性能随着池的填满而降低——可用空间变得碎片化。使用以下命令查看metaslab直方图,可以查看metalab有多满:

每个metalab定义都以一个基本描述开始。这里我们看到VDEV 0,它有130个metalabs。然后我们继续对编号0的metalab进行处理。它就在池的开头,偏移量为0。该池有806个分配(allocations)或分段(segments),其中只有3%是可用的。

然后,就系统内存而言,我们得到了这个metalab中块分配的直方图:

第一列中的数字是块大小,以2的幂表示为千字节。213=8192,所以13是8KB。此metaslab有281个8KB的分配。

第14行是16KB。此metaslab有197个16KB的分配。

第15行是32KB,有189个分配,以此类推,一直到一个232(或4GB)的分配。

在内存中的metaslab直方图之后,我们可以看到磁盘上metaslab的版本是什么样子的。在繁忙的磁盘上,ZFS始终在分配和取消分配块。

向下滚动。不,更进一步。最终,您将看到一个看起来大不相同的后期metalab。在这种情况下是metaslab 43。

这个metalab有17893个分配,但46%是空闲的。块大小分布也大不相同。

我们最常见的分配是17(128 KB)和20(1 MB)。

不过,这个磁盘暗示了为什么这个metalab只有大约一半的空间。

注意碎片级别——11。在磁盘上,metaslab 43是碎片化的。这意味着许多自由空间块相对较小。对于旋转磁盘,连续存储文件块可以提高性能。如果ZFS需要编写一个大块,它可能会继续到稍后的metalab。

进一步深入你的metaslabs内部。

磁盘末尾附近的metalab由连续的128 GB块组成。如果这个池以前几乎满了,编号较高的元实验室可能会包含一些数据,而在这个池上,它们是不受影响的。

ZFS通常将VDEV划分为200个大小相等的metalab。您可以使用 vfs.zfs.vdev.metalabs_per_vdev sysctl调整此数字,但必须在创建vdev之前设置sysctl。之所以选择数字200,是因为它似乎运行得很好,但metalab分配还有很大的实验空间。

当您通过用更大的磁盘替换VDEV来扩展VDEV时,ZFS会创建新的metalab来支持增加的空间。通过这种方式,你可以得到一个拥有200多个metalab的池。

ZFS一次只能在内存中保留这么多metalab。将VDEV扩展到其原始大小的许多倍可能会对性能产生负面影响,因为ZFS会在磁盘之间来回移动metalab。

Uberblock

如果不查看池的uberblock,这将是什么样的ZFS调试部分?

你能用这个做什么?不多。但在这一点上,ZFS几乎赤裸裸地躺在你面前。

你还能学到什么取决于你。

后记

Allan已于2016年前往AsiaBSDC,留下我独自撰写后记。

许多工具改变了我们进行系统管理的方式。ZFS实际上是独一无二的,因为这种变化是为了更好。一旦你使用了ZFS一段时间,其他文件系统似乎都很古怪。二十年来,我管理了UFS和EXT的各种迭代,但在使用ZFS仅几个月后,无法进行 ufs sendufs clone 会立即让我大发雷霆。幸运的是,UFS确实有快照,所以在太多人永久致残之前,我能够恢复镇静。

更重要的是,这是关于FreeBSD存储的四本书中的最后一本。我想指出的是,我终于写了一部四部曲(tetralogy)。嗯,大部分是四部曲。是的,如果没有Allan的帮助,这些东西就不会出现在这里——Allan知道的比我多,他给了两本ZFS书一个我无法独自拥有的深度。他有我没有的硬件,比如多路径SAS,这意味着他可以在我根本写不出来的时候写这些部分。他知道该找谁来访问我们都没有的硬件,比如NVMe。是啊,好吧,如果没有Allan,我的四部曲就会变成三部曲,而ZFS的单行本也不会那么好——但这不是我的观点。我的观点是什么?哦,看那边!一只吃人的鸭嘴兽!快跑!