第四章:ZFS数据集

传统文件系统在创建分区的时候会限制分区的大小,随着数据量增加,可能会没有足够的空间来容纳所有数据。

ZFS通过池化可用空间来解决这个问题,每个数据集都可以访问池中所有可用空间。

也可以使用配额(quota)限制数据集的大小,或通过保留(reservation)来保证数据集最小空间量。

传统文件系统的核心问题归结为缺乏灵活性。

数据集

数据集是一个命名的数据块。这些数据可能是:

类似于传统的文件系统; 一个原始的块设备; 其他数据的副本; 任何可以塞进磁盘的东西。

数据集具有层次关系。单个存储池是每个顶级数据集的父级。

每个数据集都可以有子数据集。

数据集继承了其父级的许多特性。

zfs命令有一系列子命令。

数据集类型

ZFS目前包括物种类型的数据集:

为什么需要数据集?

每个ZFS数据集都有一系列控制器操作的属性,允许管理员控制数据集的执行方式以及保护数据的谨慎程度。可以精确地调整每个数据集。

系统管理员可以将单个数据集地控制权委托给另一个用户,允许该用户在没有root权限的情况下对其进行管理。

将数据分成逻辑组可以更容易地使用这些ZFS功能来支持组织。

数据集的属性可以继承到子数据集。

数据集之间移动文件实际是复制然后删除,比传统文件系统中移动文件要消耗更多时间。

查看数据集

使用zfs list命令查看所有数据集:

如果指定数据集,zfs list命令将列出指定数据集的信息:

-t选项则显示特定的数据集类型(filesystem/volume/snapshot),比如:

创建、移动、销毁数据集

使用zfs create命令创建数据集。

创建文件系统

文件系统是最常见的数据集类型。

以上命令在池mypool中创建一个新的数据集lamb。如果池有默认的挂载点,那么新数据集将默认挂载。

括号中的装载设置通常是从父数据集继承的ZFS属性。

要创建子文件系统,需要提供父文件系统的完整路径:

创建卷

在zfs create命令中使用-V选项和卷大小创建卷。同样需要提供卷数据集的完整路径:

zvols自动保留的空间量等于卷的大小加上ZFS元数据,这个4GB的zvol使用了4.13GB的空间。

作为块设备,zvol没有挂载点,只是在/dev/zvol下获得了一个设备节点,因此可以像访问其他任何块设备一样访问它。

可以在这个设备点上用newfs命令、向其复制磁盘映像,并像使用其他块设备一样使用它。

数据集改名

使用zfs rename命令可以修改数据集的名称:

如果数据集正在使用中,可以使用-f参数可强制执行这个命令。

移动数据集

利用 zfs rename命令可以将数据集从一个ZFS树中移动到另一个ZFS树,是数据集成为其新父级的子集。这可能会导致数据集的许多基于继承关系的属性发生变化,而数据集上专门设置的属性不会发生变化。

注意,如果挂载点是继承的,这可能会改变数据集的挂载点。如果使用-u参数,则可以不立即更改挂载点。但重启系统会使新挂载点生效。

可以重命名快照,但不能将快照移出其父数据集。

销毁数据集

使用-r选项可以递归销毁数据集的所有子项(数据集、快照等)。

使用-R选项会销毁任何克隆的数据集。

使用-v和-n选项可以查看销毁数据集时会发生什么。

-v显示关于被销毁内容的详细信息。

-n则执行一次干运行。(不实际运行,仅显示运行后的效果)

ZFS属性

ZFS属性用于控制数据集的工作方式。有些属性是在创建数据集时才能设置的,有些是在数据集运行时可以调整的。

ZFS还提供了许多只读属性,比如数据集消耗的空间量、压缩或去重率、数据集的创建时间等等。

ZFS的属性有:

 

查看属性

zfs get命令可以查看数据集属性:

SOURCE有四种情况:

default表示使用了默认属性;

local表示有人设置了此数据集自己的属性;

temporary表示当前数据集挂载时使用了临时属性,卸载后将恢复为正常值;

inherited表示从其父数据集继承的属性。

有些属性没有来源,因为来源要么无关紧要,要么本质上显而易见。

记录数据集创建日期和时间的creation属性就没有来源,因为该值来自当时的系统时钟。

使用all选项可以获取所有属性,而如果同时未指定数据集,则显示所有数据集的所有属性。

要想获取某几个属性,可以按以下方式操作:

也可以使用zfs list命令的-o选项来查看属性,当想从多个数据集中查看多个属性时,这最合适。

也可以指定数据集名称,查看该数据集的某些属性。例如:

更改属性

使用zfs set命令修改属性:

大多数情况下,变更属性仅影响修改属性后写入的数据,而不会影响数据集中已有的数据。

要想让所有数据都套用新的属性,最好的方法是创建新的数据集,设置好数据集属性后,用zfs send明了复制数据,然后销毁原始数据集。

只读属性

某些属性为只读,用于提供有关数据集的基本信息,无法更改。

文件系统属性

管理传统文件系统性能和行为的一个关键工具是挂载选项。可以以只读方式挂载传统文件系统,也可以使用noexec标志禁用运行其中的程序。

ZFS使用属性来实现相同的效果。

atime

ZFS的atime属性控制数据集是否跟踪访问时间。

默认为on,每次访问文件时都会更新文件的atime元数据。

使用atime意味着每次读取磁盘时都要写入磁盘。

关闭此属性可以避免在读取文件时写入磁盘,并可以显著提高性能。但可能会影响依赖文件读取时间的程序,比如邮件程序等。

启用atime会增加快照大小。

exec

exec属性决定是否有人可以在此文件系统上运行二进制文件和命令。

默认为on,允许执行。

将exec属性设置为off可以禁止用户从此数据集运行程序。

但是exec属性并不禁止运行解释性脚本。如果用户可以运行/bin/sh,那他也可以运行/bin/sh /home/mydir/script.sh。因为只是从脚本中获取指令。

readonly

如果不希望向此数据集写入任何内容,可以将readonly属性设置为on。

默认值为off。

setuid

setuid程序又风险,默认情况下,zroot/tmp、zroot/usr/ports、zroot/usr/src、zroot/var/audit、zroot/var/crash、zroot/var/log、zroot/var/tmp这些数据集的setuid是禁用的(off)。

用户定义属性

自定义属性有利于开发全新的自动化程序。且子数据集可以自动继承这些自定义的属性。

为了避免冲突,应创建一个命名规则。大多数人在自定义属性前面加上组织标识符和冒号。

例如,FreeBSD特定的属性具有"org.FreeBSD:propertyname"的格式,形如org.FreeBSD:swap。

较真实的实例,zfstools工具使用特定的属性来实现快照:

父子关系

数据集继承其父数据集的属性。在数据集上设置属性时,该属性将应用于其下所有子数据集。

为方便起见,可以通过-r选项在数据集及其所有子数据集上运行zfs命令。例如:

通过SOURCE值可知,第一个数据集mypool/lamb从父池继承了compression属性。

第二个数据集mypool/lamb/baby则是设置了自己的compression属性。

以下操作恢复数据集的属性继承:

当更改了父级属性时,新属性会自动向下传播到子级:

继承和重命名

若移动或重命名数据集而使其具有新的父级时,父级的属性会自动向下传播到子级。

local来源的属性保持不变,inherited属性则会切换到新父级的属性。

接上一个例子,mypool/lamb/baby的compression属性值为gzip-9,将其重命名到mypool/second下面之后,其compression属性值自动变更为lz4,因为它的compression属性是继承自父数据集的。

这种情况下,baby数据集上数据有点混乱。在启用压缩之前写入的数据是未压缩的,当数据集使用gzip-9压缩时写入的数据用gzip-9进行压缩。现在写入的任何数据都将使用lz4进行压缩。虽然ZFS会自动解决这些问题,但确实会让强迫症头疼。

移除属性

使用以下命令移除自定义属性:

使用以下命令可以将数据集及其所有子数据集的属性重置为默认值,或完全删除自定义属性:

挂载ZFS文件系统

对于传统的文件系统,可以在/etc/fstab中列出每个分区、其类型以及应挂载的位置。甚至列出如软盘、光盘等。

由于ZFS允许创建大量文件系统,在/etc/fstab中列出每个文件系统变得不现实。

每个ZFS文件系统都有一个mountpoint属性来定义其挂载点。

默认的mountpoint来自池的mountpoint。如果池没有设置挂载点,就必须为数据集分配一个挂载点。

除了具有根文件系统的池之外的任何池,通常有一个以池名称命名的挂载点。例如,创建了名为db的池,则它的挂载点为/db,在它下面创建的数据集将继承这个挂载点。

当更改文件系统的mountpoint属性时,该文件系统和继承装载点的任何子文件系统将被卸载。如果新值是legacy,则保持卸载状态。否则,如果属性以前是legacy或none,或者如果它们是在属性更改之前转载的,则它们会自动装载到新位置。

另外,任何共享的文件系统将停止共享,并在新位置共享。

与普通文件系统一样,ZFS文件系统不一定被挂载。canmount属性控制文件系统的挂载行为。如果canmount设置为yes,则运行zfs mount -a命令会挂载文件系统。就像mount -a命令一样。

在/etc/rc.conf中设置了zfs_enable="YES",则FreeBSD会在启动时运行zfs mount -a以挂载所有canmount属性为yes的ZFS文件系统。

如果canmount设置为noauto,数据集只能被明确地挂载和卸载。 在数据集创建或导入时不会被自动挂载。 即不会由zfs mount -a命令挂载,也不会由zfs unmount -a卸载。 zroot/ROOT/default的canmount属性为noauto。

当canmount设置为off时,事情会变得有趣。 可以创建两个具有相同挂载点地不可挂载数据集。一个数据集可以仅仅为了未来数据集地父数据集而存在,但实际上并不能存储文件。 zroot/usr、zroot/var的canmount属性为off。

子数据集不继承canmount属性。

更改canmount属性不会自动卸载或装载文件系统。如果在已挂载地文件系统上禁用挂载,则需要手动卸载文件系统或重启系统。

没有挂载点的数据集

ZFS数据集是分层的。可能需要创建一个永远不会只包含任何文件的数据集,这样它就可以成为许多其他数据集的公共父级。

我们在/usr下有各种各样的数据集,但没有挂载/usr数据集。

使用以下命令检查canmount和mountpoint:

由于zroot/usr的canmount属性为off,表示此数据集从未被挂载。所有写入/usr目录的文件(例如/usr/bin中的命令和/usr/local中的软件包)会进入root文件系统。低级别的挂载点(例如/usr/src)有自己的数据集和挂载点。

该数据集仅作为子数据集的父数据集而存在。/var分区也是类似情况。

多数据集挂载在同一挂载点

将canmount设置为off允许数据集仅用作继承属性的机制。

将canmount设置为off的一个原因是有两个具有相同挂载点的数据集,这两个数据集的子数据集就会出现在同一个目录中,但可能具有不同的继承特征。

FreeBSD安装程序没有为默认池zroot设置mountpoint。创建新数据集时需要指定挂载点。

如果你不想为在池下创建的每个数据集分配一个挂载点,你可以将/的挂载点分配给zroot池,并将canmount设置为关闭。这样,当你创建一个新数据集时,它有一个要继承的挂载点。这是一个使用具有相同挂载点的多个数据集的非常简单的例子。

想象一下,你想要一个包含两组子目录的/opt目录。其中一些目录包含程序,安装后不应写入;其他目录包含数据。你必须锁定在文件系统级别运行程序的能力。

现在给这两个数据集赋予/opt的挂载点,并设置不能挂载:

安装软件到数据集,然后设置其为只读:

不能从db/data数据集运行软件,所以关闭exec和setuid。但可以写数据:

现在创建一些子数据集,db/programs数据集的子级继承该数据集的属性,而db/data数据集的子集则继承另一组属性:

我们现在在/opt中挂载了四个数据集,其中两个用于二进制文件,两个用于数据。

目前不知道如何使用此功能。

没有挂载点的池

池通常挂载在以池命名的目录中,但以下操作可以产生一个例外:

这个池将不再被挂载。除非指定挂载点,否则池上的任何数据集都不会挂载。

如有必要,将创建目录并挂载文件系统。

手动挂载和卸载文件系统

使用zfs mount挂载文件系统。这常用于canmount设置为noauto的文件系统。

使用zfs unmount目录卸载文件系统即其所有子系统:

如果要在其他位置临时挂载数据集,可以使用-o选项指定新的挂载点:

只能挂载定义了挂载点的数据集。当数据集没有挂载点时定义临时挂载点将会导致错误。

ZFS 和 /etc/fstab

可以使用/etc/fstab管理某些或全部ZFS文件系统。将数据集的mountpoint属性设置为legacy(这将卸载文件系统):

现在可以使用mount命令挂载这个数据集:

也可以将ZFS数据集加入到/etc/fstab中。使用完整的数据集名称作为设备点。设置类型为zfs。可以使用标准的文件系统选择noatime、noexec、readonly、ro、nosuid。还可以明确地给出atime、exec、rw和suid地默认行为。

以下示例将scratch/junk挂载到/tmp:

但强烈建议使用ZFS属性来管理挂载

调整 ZFS 卷

zvols非常简单——这里有一块空间作为块设备,使用它。

可以调整卷使用空间地方式以及它提供的设备节点类型。

空间预留

zvol的volsize属性指定卷的逻辑大小。默认情况下,创建卷会为数据集保留与卷大小相等的空间量。

更加volsize会更改预留。volsize只能设置为volblocksize属性的倍数,不能为零。

如果没有预留,卷可能会耗尽空间,导致未定义的行为或数据损坏,具体取决于卷的使用方式。当体积大小在使用过程中发生变化时,也会出现影响,特别时在缩小尺寸时。调整卷大小可能会混淆使用块设备的应用程序。

zvols还支持稀疏卷(sparse volumes),也称精简资源调配(thin provisioning)。

稀疏卷是指保留量小于卷大小的卷。从本质上讲,使用稀疏卷可以分配比数据集可用的空间更多的空间。

通过稀疏资源调配,可以在5TB的数据集上创建10个1TB的稀疏卷。只要卷未被大量使用。

不建议使用稀疏卷。即使卷本身看起来只是部分满,写入稀疏卷也可能会失败,并出现“空间不足”错误。

通过在zfs create -V命令中指定-s选项,在创建是指定稀疏卷。预定中不会反映卷的变化。

Zvol 模式

FreeBSD通常将zvols作为geom提供者暴露给操作系统,赋予它们最大的灵活性。可以使用volmode属性更改此设置。

将卷的volmode设置为dev只会讲卷作为/dev中的字符设备公开。此类卷只能作为原始磁盘设备文件访问。它们不能被分区或挂载,也不能参与RAID或其他GEOM功能。它们更快,在某些情况下,如果不信任使用卷的设备,dev模式可能更安全。

将volmode设置为none意味着卷不会暴露在ZFS之外。但这些卷可以快照、克隆和复制。这些卷可能适用于备份的目的。

将volmode设置为default意味着卷暴露有sysctl vfs.zfs.vol.mode控制。可以在系统范围内设置默认的zvol模式。值1表示默认值为geom,2表示dev,3表示none。

虽然可以更改活动卷的属性,但它没有任何效果。此属性仅在卷创建和池导入期间处理。

可以通过适用zfs rename命令来重建zvol设备。

数据集完整性

ZFS的大多数保护都在VDEV层工作。毕竟,这就是块和磁盘变坏的地方。然而,一些硬件限制了池冗余。很少有笔记本电脑有足够的硬盘来使用镜像,更不用说RAID-Z了。但是,您可以在数据集层做一些事情,通过使用校验和、元数据冗余和副本来提供一些冗余。大多数用户永远用不到前两个,拥有冗余虚拟设备的用户可能希望不去碰这三个。

校验和

ZFS计算并存储它写入的每个块的校验和。这确保了当一个块被读回时,ZFS可以验证它与写入时相同,并且没有以某种方式被静默损坏。checksum属性控制数据集使用哪种校验和算法。有效设置包括on、fletcher2、fletcher4、sha256、off和noparity。

默认值为on,使用OpenZFS开发人员选择的算法,2015年,该算法是fletcher4。 标准算法fletcher4足够好用,且速度很快。如果想永远使用fletcher4,可以将此属性值设置为fletcher4。但建议保持默认的on,并在适当的时候让ZFS升级池的校验和算法。 ZFS的旧版本使用fletcher2。 sha256算法比fletcher4慢,但不太可能导致冲突(collision),多数情况下冲突是无害的。在执行去重时,经常建议使用sha256算法。

off会禁用对用户数据的完整性检查。

noparity不仅禁用完整性,还禁用维护用户数据的奇偶检验。此设置由驻留在RAID-Z池上的转储设备在内部使用,不应该被任何其他数据集使用。

拷贝

ZFS存储两到三个重要元数据的副本,并可以对重要用户数据进行相同的处理。

copies属性告诉ZFS要保留多少个用户数据副本。ZFS会尝试将这些副本保存到不同的磁盘上。如果没有多余的磁盘,则保存在同一磁盘上尽量远的物理位置,以防止硬件故障。copies属性默认值为1,最大值为3。

如果一个池是由两个硬盘组成的镜像,copies设置为3,则数据会有6个副本。其中一个应该能在极端情况下幸存下来。

增加(increasing)或减少(decreasing)副本只会影响设置更改后写入的数据。将copies从1改为2,不会立即创建所有数据的重复副本。

以下示例创建10MB的随机数据:

现在每个块都存储两次。如果其中一个副本损坏,ZFS仍能读取文件。它知道哪些块已经损坏,因为它的校验和不匹配。但是看看池上的空间使用情况(REFER值):

只使用了我们写入的10MB。由于在更改copies属性之前编写了此文件,因此没有对其进行额外的复制。

然而,当copies设置为2时,如果写入另一个文件或覆盖原始文件,我们将看到不同的磁盘使用情况。

现在看看磁盘用量:

总空间使用量为30MB,10MB是第一个10MB文件的随机数据,20MB是第二个10MB文件的2个副本。

当我们用ls查看文件时,它们只显示实际大小:

如果真的想破坏数据集的弹性,可以看看元数据冗余。

元数据冗余

每个数据集都存储了其内部元数据的额外副本,因此,如果单个块损坏,丢失的用户数据量是有限的。此额外副本是VDEV级别提供的任何冗余(例如通过镜像或RAID-Z)。

它也是copies属性指定的任何额外副本的补充,最多三份。

redundant_metadata属性运行决定数据集的冗余程度。大多数用户永远不应该更改此属性。

redundant_metadata属性的默认值为all,ZFS存储所有元数据的额外副本。如果磁盘上的单个块损坏,最坏的情况是,单个用户数据块可能会丢失。

如果将redundant_metadata属性设置为most,ZFS仅存储大多数类型元数据的额外副本。这可以提高随机写入的性能,因为必须写入的元数据更少。当只有大多数元数据是冗余的时,如果磁盘上的单个块损坏,最多可能会丢失大约100个用户数据块。冗余存储元数据块的确切行为可能会在未来的版本中发生变化。

如果将redundant_metadata属性设置为most,且copies属性为3,而数据集位于一个镜像池内。则ZFS存储大多数元数据的六个副本、数据和一些元数据的四个副本。

此属性是为经常更新元数据的特定用例(如数据库)而设计的。如果数据已经受到足够强的容错保护,减少每次数据库更改时必须写入的元数据副本数量可以提高性能。只有当你知道自己在做什么时,才能更改此值。