OpenZFS 系统管理
版本 2023 年 9 月 11 日 17:16
本页面旨在记录对系统管理员有用的信息。
概述
ZFS 是对传统存储栈的重新设计。ZFS 中的基本存储单元是存储池(pool),从池中可以获得数据集,这些数据集可以是挂载点(可挂载的文件系统)或块设备。ZFS 池是一个完整的存储栈,能够替代 RAID、分区、卷管理、fstab/exports 文件以及仅跨单个磁盘的传统文件系统(如 UFS、XFS)。这使得可以通过更少的代码、更高的可靠性以及简化的管理来完成相同的任务。
可以通过一条命令实现从一组磁盘创建具有冗余的可用文件系统,并且在重启后依然保持。这是因为 ZFS 池总是包含可挂载的文件系统,称为根数据集,该文件系统在池创建时被挂载。在创建时,会将存储池导入到系统,从而在 zpool.cache 文件中创建一个条目。在导入或创建时,池会存储系统的唯一 hostid,并且为了支持多路径,除非强制,否则导入到其他系统会失败。
ZFS 本身由三层主要结构组成。最底层是存储池分配器(Storage Pool Allocator,SPA),负责将物理磁盘组织成存储空间。中间层是数据管理单元(Data Management Unit,DMU),它使用 SPA 提供的存储,以事务性的、原子方式进行读写。最顶层是数据集层,它将池提供的文件系统和块设备(zvol)上的操作转换为 DMU 中的操作。
底层存储
SPA 在池中对磁盘的组织形式是一个由 vdev(虚拟设备)构成的树。树的顶层是根 vdev。它的直接子节点可以是除自身之外的任意 vdev 类型。主要的 vdev 类型有:
mirror(支持 n 路镜像)
raidz
raidz1(1 磁盘校验,类似 RAID 5)
raidz2(2 磁盘校验,类似 RAID 6)
raidz3(3 磁盘校验,无 RAID 类比)
disk
file(不建议在生产环境中使用,因为其他的文件系统会增加不必要的层)
任意数量的这些 vdev 都可以作为根 vdev 的子节点,被称为顶级 vdev。此外,其中一些也可能有子节点,例如 mirror vdev 和 raidz vdev。命令行工具不支持将 raidz 作为 mirror 的子节点或 mirror 作为 raidz 的子节点,尽管这种配置在开发者测试中有使用。
使用多个顶级 vdev 会以加法方式影响 IOPS,总 IOPS 为各顶级 vdev 的 IOPS 之和。因此,任何一个主要顶级 vdev 的丢失都会导致整个池的丢失,因此必须在所有顶级 vdev 上使用适当的冗余。
支持的最小 file vdev 或 disk vdev 大小为 64MB,(2^26 字节),最大值取决于平台,但所有平台应至少支持 16EB(2^64 字节)的 vdev。
还有三种特殊类型:
spare
cache
log
spare 设备在磁盘故障时用于替换,前提是池的 autoreplace 属性已启用且你的平台支持该功能。它不会替换 cache 设备或 log 设备。
cache 设备用于扩展 ZFS 的内存数据缓存,它替代了页缓存(page cache),除了 mmap(),在大多数平台上 mmap() 仍使用页缓存。ZFS 使用的算法是自适应替换缓存(Adaptive Replacement Cache, ARC)算法,其命中率高于页缓存使用的最近最少使用(LRU)算法。cache 设备通常与闪存设备一起使用。存储在其中的数据是非持久的,因此可以利用廉价的设备。
log 设备能让 ZFS Intent Log(ZIL,ZFS 意图日志)记录写入到不同设备(例如闪存设备),可提高同步写操作的性能,然后再写入主存储。除非设置了数据集属性 sync=disabled,否则这些记录会被使用。在任何情况下,同步操作的更改会保存在内存中,直到下一个事务组提交时写入。只要下一个事务组提交成功,ZFS 可以在 log 设备丢失的情况下继续运行。如果系统在 log 设备丢失时崩溃,池将被标记为故障。虽然可以恢复,但当前事务组中所做的所有同步更改将丢失,不会保存在存储在该池的数据集上。
数据完整性
ZFS 通过多种机制尝试提供数据完整性:
已提交的数据存储在一个 Merkle 树中,该树在每次事务组提交时以原子方式更新。
Merkle 树使用存储在块指针中的 256 位校验和来防止错误写入,包括那些对较弱校验和可能发生碰撞的写入。支持将 sha256 用于加密强保证,尽管默认是 fletcher4。
每个 disk/file vdev 包含四个磁盘标签(每端两个),以确保即使一端由于磁头掉落丢失数据,也不会擦除标签。
事务组提交使用两个阶段来确保在事务组被视为已提交之前,所有数据都已写入存储。这就是为什么每个磁盘两端都有两个标签。在机械存储上执行事务组提交需要完整的磁头扫描,并使用刷新(flush)操作以确保后半部分不会在其他操作之前执行。
用于同步 IO 的 ZIL 记录存储待更改的数据,这些记录是自检块,仅在系统在上一个事务组提交之前进行更改时,在池导入时读取。
默认情况下,所有元数据存储两份,其中包含池在特定事务组状态的对象(标签指向的对象)会写入三次。ZFS 尽量将元数据存储在相距至少 1/8 磁盘的位置,以防磁头掉落导致不可恢复的损坏。
标签包含一个 uberblock 历史,这能在最坏情况下将整个池回滚到近期的某一点。使用此恢复机制需要特殊命令,因为通常不需要。
uberblocks 包含所有 vdev GUID 的总和。只有当总和匹配时,uberblocks 才被视为有效。这可以防止已损坏的旧池的 uberblocks 被误认为是有效的。
支持 N 路镜像和 raidz 的最多 3 级校验,这样在使用适当冗余时,越来越常见的 2 磁盘故障[1] 不会在恢复过程中破坏 RAID 5 或双镜像,从而避免 ZFS 池被损坏。
在 FreeNAS 论坛上曾流传一种错误信息,称在未使用 ECC 内存时,ZFS 的数据完整性功能不如其他文件系统[2]。这一说法已被彻底驳斥[3]。为了可靠运行,所有软件都需要 ECC 内存[4],在这方面 ZFS 与其他文件系统并无不同。
启动过程
在传统的 POSIX 系统中,启动过程如下:
CPU 开始执行 BIOS。
BIOS 进行基本硬件初始化并加载 bootloader。
bootloader 加载内核并传递包含 rootfs 的驱动信息。
内核进行额外初始化,挂载 rootfs 并启动
/sbin/init。/sbin/init运行启动其他所有服务的脚本,包括挂载 fstab 中的文件系统以及通过 exports 导出 NFS 共享。
此过程存在一些差异。例如,在 illumos(通过 boot_archive)和 FreeBSD(单独)上,bootloader 还会加载内核模块。某些 Linux 系统会加载 initramfs,这是一个临时 rootfs,包含需要加载的模块,并将部分启动逻辑移动到用户空间,通过 pivot_root 挂载真实 rootfs 并切换到它。此外,EFI 系统能够直接加载操作系统内核,从而无需 bootloader 阶段。
ZFS 对启动过程的改动如下:
当 rootfs 位于 ZFS 上时,必须在内核挂载之前导入池。illumos 和 FreeBSD 的 bootloader 会将池信息传递给内核,以便导入根池并挂载 rootfs。在 Linux 上,必须使用 initramfs,直到支持动态创建 initramfs 的 bootloader 功能实现为止。
无论是否存在根池,导入的池必须显示。通过读取
zpool.cache文件中的导入池列表实现,该文件在大多数平台上位于/etc/zfs/zpool.cache,在 FreeBSD 上位于/boot/zfs/zpool.cache。该文件以 XDR 编码的 nvlist 存储,可通过不带参数执行zdb命令读取。池导入后,必须挂载文件系统,并创建任何文件系统导出或 iSCSI LUN。如果数据集的 mountpoint 属性设置为 legacy,可以使用 fstab。否则,启动脚本将在池导入后运行
zfs mount -a挂载所有数据集。类似地,通过 NFS 或 SMB 共享文件系统,以及通过 iSCSI 共享 zvol 的数据集,将在挂载完成后通过zfs share -a导出或共享。并非所有平台都支持在所有共享类型上使用zfs share -a。在不支持自动化的系统上,必须使用传统方法。
下表显示了 zfs share -a 在各平台上支持的协议:
平台
iSCSI
NFS
SMB
FreeBSD
不支持
支持
不支持
illumos
支持
支持
支持
Linux
不支持
支持
支持
Mac OS X
不支持
支持
不支持
OSv
不支持?
不支持?
不支持?
池创建
通过 zpool 和 zfs 命令执行对 ZFS 的管理。要创建池,可以使用 zpool create poolname ...。poolname 后面的部分是 vdev 树的规格。根数据集默认会挂载在 /poolname,除非使用 zpool create -m none。其他挂载点位置可以通过将其写在 none 位置来指定。
在指定顶级 vdev 关键字之前的任何文件或磁盘 vdev 都将成为顶级 vdev。顶级 vdev 关键字之后的任何 vdev 都将成为该 vdev 的子节点。顶级 vdev 内的不等大小磁盘或文件会将其存储限制为较小的大小。对于生产环境的池,重要的是不要创建非 raidz、mirror、log、cache 或 spare 的顶级 vdev。
更多信息可以在你的平台上的 zpool 手册页中找到。在 illumos 上为第 1m 节,在 Linux 上为第 8 节。
重要信息
扇区对齐
ZFS 会尝试通过从磁盘中提取物理扇区大小来确保正确对齐。每个顶级 vdev 会使用最大扇区大小,以避免部分扇区修改带来的额外开销。当磁盘错误报告其物理扇区大小时,这种方法将不正确。性能调优 页面提供了更多说明。
整盘调整
在提供整盘时,ZFS 在各个平台上也会尝试进行一些微调。在 illumos 上,ZFS 会启用磁盘缓存以提升性能。当提供的是分区时则不会启用,以保护可能不耐受磁盘缓存的其他文件系统共享该磁盘,例如 UFS。在 Linux 上,IO elevator 会被设置为 noop,以减少 CPU 开销。ZFS 有自己的内部 IO elevator,使得 Linux 的 elevator 成为冗余。性能调优 页面对这种行为有更详细的说明。
磁盘错误恢复控制
ZFS 目前不会自动调整磁盘的 错误恢复控制。当磁盘发生读取错误时,它会重复尝试读取,希望从略微不同的角度读取成功,从而能够正确重写扇区。它会一直尝试,直到达到超时,这在许多磁盘上通常为 7 秒。在此期间,磁盘可能无法处理其他 IO 请求,这会严重影响 IOPS,因为事务组提交必须等待所有 IO 完成才能开始下一步。在 ZFS 修改以控制其管理的磁盘之前,系统管理员应使用 smartctl 等工具或系统本地文件在每次启动时进行设置。
磁盘或磁盘控制器挂起
类似地,如果磁盘或磁盘控制器挂起,会导致整个池挂起,并触发死锁定时器(deadman timer)。在 illumos 上,这会触发系统重启。在 Linux 上,会尝试记录失败,但不采取其他操作。deadman timer 的默认超时为 1000 秒。
硬件
ZFS 设计用于使用商用硬件可靠存储数据,但某些硬件配置本身不可靠,以至于任何文件系统都无法补偿。有关硬件配置的建议,请参见 Hardware 页面。
硬件 RAID
不应在 ZFS 上使用硬件 RAID。原因可见 Hardware 页面。
性能
ZFS 具有自我调优能力,但有一些参数可优化特定应用。这些内容在 性能调优 页面中有说明。
冗余
双盘故障发生的概率足够高[5],因此 raidz1 vdev 不应在生产环境中用于数据存储。双盘镜像也存在风险,但不如 raidz1 vdev 高,除非仅使用两块磁盘。这是因为在双盘镜像中,所有磁盘都失败的概率统计上低于在奇偶校验配置(如 raidz1)中仅两块磁盘失败的概率,除非仅使用两块磁盘,此时风险相同。同样的风险也适用于 RAID 1 和 RAID 5。
单盘 vdev(不包括 log 和 cache 设备)在生产环境中不应使用,因为它们没有冗余。然而,可以通过使用 zpool attach 将单盘池的磁盘转换为镜像,从而实现冗余。
一般管理
在池级通过 zpool 命令进行一般管理,在数据集级通过 zfs 命令进行。文档可在各自平台的 zpool 和 zfs 手册页中找到。在 Linux 上为第 8 节,在 illumos 上为第 1m 节。
有关部分手册页链接,请参见 平台/发行版文档。请注意,一个平台的手册页在其他平台上可能也适用。
其他资源
第三方工具
bdrewery/zfstools 提供快照管理工具,使用 Ruby 编写。
fearedbliss/bliss-zfs-scripts 提供“ZFS 快照与备份管理”工具,遵循 MPL 2.0,针对 Gentoo Linux 设计。
jimsalterjrs/sanoid 基于策略的快照管理和复制工具,遵循 GPL,在 Linux 和 FreeBSD 上测试过。
Rudd-O/zfs-tools 提供快照管理和复制工具,使用 Python 编写。
ZFS Watcher 是一个池监控与通知守护进程。
一般文档
手册页:zdb | zfs | zpool | zpool-features | zstreamdump – 来自 illumos;更好的渲染页面会更好
Oracle Solaris ZFS 管理指南 – 大部分内容适用
ZFS – 文件系统的最后方案 – 来自 SNIA 2008 的概览
RAID-Z 计算工具 – 包含 RAID-Z1/Z2/Z3 计算
平台/发行版文档
FreeBSD
手册页:zdb | zfs | zpool | zpool-features | zstreamdump
FreeBSD 手册 的 ZFS 章节
Gentoo
illumos
手册页:zdb | zfs | zpool | zpool-features | zstreamdump
OpenIndiana ZFS 管理指南
最后更新于