ZFS 文件系统
请以(OpenZFS)官方文档为准:OpenZFS 文档
oracle 也有个文档是 Solaris 的 zfs:OracleSolarisZFS 文档
本文使用的是 Ubuntu 22.04 系统。使用的是 OpenZFS。
本文仅个人学习使用 zfs 笔记。
概念
许多常见的文件系统都被设计用在一个单独的分区或者逻辑卷上(例如 ext3/4 及 NTFS)。ZFS 则整合了逻辑卷管理功能,因此一个存储池可以被部署在许多块存储盘之上。存储池在 ZFS 中被称为 zpool
。在一个 zpool 里面可以存在多个 VDEV (Virtual DEVices, 虚拟设备)
。写入 zpool 的数据将被分散到各个 VDEV 上。
虽然 zpool 和 RAID 看上去的确很相似,ZFS 并不会简单地将要写的数据平均写到各个盘。实际上,ZFS 有一套很复杂的分配及调度机制来决定何时写数据,以及数据将写到何处。
zfs 支持以下几种磁盘阵列组合方式:
- single(default):不指定时默认为此种方式组合。简单的单盘 VDEV,类似 raid0
- mirror:镜像 VDEV,允许多块存储盘,只要一块可用就可以保证数据安全,性能最高。类似于 RAID 1
- raidz:最少需要 3 块磁盘,允许 1 块损坏。类似 raid5
- raidz2:最少需要 4 块磁盘,允许 2 块损坏。类似 raid6
- raidz3:最少需要 5 块磁盘,允许 3 块损坏。
raidz 阵列需要至少 2 + p 块存储盘(p 代表 raidz 冗余等级,raidz 中 p 为 1,raidz2 中为 2,raidz3 中为 3)。举个例子,假设所有盘都是相同容量:
- 2 存储盘 + 1 冗余盘 (raidz)
- 2 / 2+1 ≈ 66.7% 空间效率,允许 3 块盘中损失 1 块
- 4 存储盘 + 1 冗余盘 (raidz)
- 4 / 4+1 = 80% 空间效率,允许 5 块盘中损失 1 块
- 4 存储盘 + 2 冗余盘 (raidz2)
- 4 / 4+2 ≈ 66.7% 空间效率,允许 6 块盘中损失 2 块
注意: 的是如果混用不同容量的盘,则 ZFS 将把所有盘视作最小盘的大小。
设备选择
创建存储池时,有多种方式让我们选择设备。推荐使用 /dev/disk/by-id/
方式创建你的阵列。以下简单列举几种方式:
- /dev/sdX、/dev/hdX:适合开发/测试
- 在所有的 Linux 发行版都是可用的,但是名字并不是固定的,并且会根据磁盘的检测顺序而改变。当发生变化后需要删除
/etc/zfs/zpool.cache
文件并使用新名称重新导入池。
- 在所有的 Linux 发行版都是可用的,但是名字并不是固定的,并且会根据磁盘的检测顺序而改变。当发生变化后需要删除
- **/dev/disk/by-id/**:适合小型池(少于 10 个磁盘)
- 此目录包含具有更易读名称的磁盘标识符。磁盘标识符通常由
接口类型
、供应商名称
、型号
、设备序列号
和分区号
组成。标识符名称是永久的,并且不受磁盘插入物理位置,识别顺序的先后影响。 - 注意此方式不适合虚拟机环境下。因为虚拟机并不会生成正确的唯一标识符。
- 此目录包含具有更易读名称的磁盘标识符。磁盘标识符通常由
- **/dev/disk/by-path/**:适用于大型池(大于 10 个磁盘)
- 此目录包含系统中物理电缆布局的设备名称,这意味着特定磁盘绑定到特定位置。该名称描述了 PCI 总线号,以及机箱名称和端口号。这允许在配置大型池时进行最大程度的控制。
- **/dev/disk/by-uuid/**:不是一个很好的选择
- /dev/disk/by-partuuid/ 、 by-partlabel:仅适用于现有分区
- 分区UUID是在创建时生成的,所以使用受限。而且如果没有提前记录相关信息,定位故障盘会变的很困难。
存储池
zfs 中,使用 zpool 命令创建与维护。
创建存储池
指令格式如下:
1 | zpool create <options> <pool_name> [raidz|raidz2|raidz3|mirror] <volumes/devices> |
1 | # 示例 |
使用上面的命令 zpool 会创建存储池后,自动挂载存储到根目录
下和存储池同名的文件夹下。使用df -hT
即可查看到。
并且默认情况下如果不特别指定存储池类型,创建的是single(raid0)
类型阵列。
为了使用方便,通常我们会增加几个常用参数,如下:
1 | zpool create -f -o ashift=12 -m <mountpoint> <pool_name> [raidz|raidz2|raidz3|mirror] <volumes> |
-f
用于避免 Does not contain an EFI label 错误。这个可能是 Linux 特有的错误,具体查看手册。-o ashift=12
指定扇区大小。ashift 值范围从 9 到 16,默认值 0 意味着 zfs 应该自动检测扇区大小。该值实际上是一个位移值,因此 512 字节的 ashift 值为 9 (2^9 = 512),而 4,096 字节的 ashift 值为 12 (2^12 = 4,096),8,192字节的 ashift 值为 13 (2^13 = 8,192)。具体查看官方手册-高级格式化磁盘-m <mountpoint>
指定默认挂载点。
创建 raidz(raid5)
类型的存储池,并且指定格式化扇区单元为 4k
并指定挂载点位置。
1 | # 示例 |
创建raid(10)
类型的存储池,创建需要最少 4 块硬盘,在同一个存储池中创建两个镜像。
1 | sudo zpool create -f -o ashift=12 -m /mmt/zfs-raid10-pool raid10-pool mirror /dev/sda /dev/sdb mirror /dev/sdc /dev/sdd |
删除存储池
删除存储池之前必须停止所有的活动,命令行不能在挂载点的目录内,关闭所有打开的相关文件。如果无法达成上述操作,那么只能使用 强制删除
了
1 | sudo zpool destroy [-f] <pool name> |
查看存储池状态
可以执行显示某个存储池,也可以不指定显示所有存储池。
1 | sudo zpool status [options] [pool name] |
部分参数列举:
-x
:仅显示出现错误或不可用的存储池状态。-v
:显示详细的错误信息。-t
:显示 VDEV 的 TRIM 状态-s
:显示 VDEV 的慢 I/O 操作数量,这是未完成的 I/O 操作数 zio_slow_io_ms毫秒 (30000 默认情况下)。这并不一定意味着 I/O 操作无法完成,只是花费了不合理的长时间。这可能表明底层存储存在问题。-P
:显示 VDEV 的完整路径-p
:以可解析(精确)值显示数字。-g
:显示 VDEV 的 GUID ,可用于代替 zpool detach/offline/remove/replace 命令的设备名称。-i
:显示 VDEV 初始化状态。-c
:显示额外的指定磁盘信息。具体参考下面列举:
1 | Available 'zpool status -c' commands: |
查询存储池列表
1 | sudo zpool list |
重新设置挂载点
mountpoint 属性更改时,文件系统将自动从旧挂载点取消挂载,并重新挂载到新挂载点。挂载点目录根据需要进行创建。如果 ZFS 由于文件系统正处于活动状态而无法将其取消挂载,则会报告错误,并需要强制进行手动取消挂载。
1 | sudo zfs set mountpoint=<mount path(abs)> <pool name> |
当 mountpoint 设置为 legacy
或者none
时,则 zfs 不会在自动挂载该存储池。
手动挂载参考:oracle-管理 zfs 挂载点
取消挂载存储池
1 | sudo zfs unmount [-f] <pool name> |
使用-f
参数强制取消挂载
挂载所有存储池
1 | sudo zfs mount -a |
查看所有存储池挂载状态
1 | sudo zfs mount |
查看所有已挂载存储池
1 | sudo zfs get mounted |
查看所有存储池挂载点信息
1 | sudo zfs get mountpoint |
查看存储池修改历史
1 | sudo zpool history <pool name> |
添加新的 VDEV
添加新的 VDEV 来扩展存储池的空间。
1 | sudo zpool add <pool name> [raidz|raidz2|raidz3|mirror] <volumes/devices> |
zpool.add.8 有该指令的详细描述
替换 VDEV
当设备发生损坏/故障时,可以使用 replace
命令替换故障的设备。
1 | sudo zpool replace <pool name> <old volume/device> <new volume/device> |
zpool.replace.8 有该指令的详细描述
转换简单盘为 mirror
1 | sudo zpool attach <pool name> <exisitng_volume/VDEV_name> <new_volumes> |
zpool.attach.8 有该指令的详细描述
删除存储池中的设备
目前,OpenZFS 只支持从不包含 RAIDz 的存储池中移除 single 和 mirror 类型的 VDEV。这个操作会把数据迁移至剩下的存储盘上,并相应地降低存储池的大小。
1 | sudo zpool remove <pool name> <volume/device> |
zpool.remove.8 有该指令的详细描述
导入导出存储池(迁移)
如果要在别的设备/操作系统上使用存储池,首先需要将存储池导出。
1 | sudo zpool export <pool name> |
导入存储池时需注意,默认会使用非持久命名导入数据盘。为了避免磁盘排布变化造成无法开机时载入存储池,导入时应注明从哪个位置搜索磁盘:
1 | zpool import -d /dev/disk/by-id <pool name> |
快照
得益于cow
机制,zfs 的快照非常快速,基本不占用额外的空间,但是因为快照会让 zfs 保留旧的数据,时间长了之后快照就会占用很多空间。
建议经常清理快照。
创建快照
1 | sudo zfs snapshot <pool name>@<snapshot name> |
查看快照
1 | sudo zfs list -t snapshot |
删除快照
1 | sudo zfs destroy <pool name>@<snapshot name> |
回滚快照
1 | sudo zfs rollback -r <pool name>@<snapshot name> |
-r
:销毁比指定快照更新的快照,并回滚。(销毁指定快照之后创建的快照,并回滚)
zfs.rollback.8 有该指令的详细描述
数据清理&重建
本文未实践过的操作,此处记录备份,日后有机会更新完善此处。
zfs 默认不进行任何清理检查工作。
只有在读取到错误的数据时,zfs 才会知道数据已经损坏。
定期执行 scrub
指令可以,尽早检测出错误。
1 | sudo zpool scrub <pool name> |
建议设置上面的指令为计划任务,每周 或者 每月一次。
当 zfs 对您的存储池进行数据清理(scrub)时,它将根据其已知的校验值和逐一检查存储池中的每个块。默认情况下,从上到下的每个块都使用适当的算法进行校验。当前是使用的“fletcher4” 算法,它是 256 位的算法,速度很快。当然你也可以更改为使用 SHA-256 算法,但不推荐这样做,因为计算 SHA-256 校验和的开销比 fletcher4 更大。
如果阵列磁盘发生了损坏。我们需要手动 replace
阵列中的磁盘。然后等待 zfs 的自愈
机制,自动修复该数据。