ZFS存储池配置终极指南

文章目录

概述

在使用 ZFS 时,最常提出的问题往往是"我的 zpool 应该如何配置"。有些人希望在已有硬盘的基础上获得最佳配置方案,而另一些人则想知道应该购买多少、以及什么类型的硬盘。无论属于哪种情况,要回答这个问题都必须先理解 ZFS 存储池的基本构成。一个 pool 并不是直接由磁盘组成,而是由多个 vdev 组成,每个 vdev 再由若干磁盘构成。因此,在规划 ZFS 存储架构时,更合理的思考方式是从上往下:先理解 pool 的结构,再到 vdev 的冗余方式,最后才是具体使用多少块磁盘。只有掌握这种层级关系,才能做出合理的存储设计。

zpool结构

一、从从结构视角看 ZPool

zpool 是 vdev 的集合,大致可以分为:

  1. Support vdevs,包含:
  • Cache(L2ARC,不是main ARC)
  • Log
  • Special
  1. Storage vdevs

    一个 zpool 由 四个 RAIDz2 storage vdev 组成,并且包含每一种受支持的 support vdev 类型各一个------LOG、CACHE 和 SPECIAL。
    data 会在这些 vdev 之间进行分布,而 容错性是在 每个 vdev 内部提供的,而不是在 vdev 之间提供的。
    上图展示了一个 pool,它使用了 四个 RAIDz2 vdev,并且包含当前所有受支持的 support vdev 类型各一个。
    从最顶层开始,zpool 是 ZFS 的顶层结构。
    从结构上来说,zpool 是由 一个或多个 storage vdev 以及 零个或多个 support vdev 组成的集合。
    需要注意的是,zpool 并不会直接包含实际的 disks(或其他 block/character devices,例如 sparse files)。这正是下一层对象 vdev 的职责。vdev 是 virtual device 的缩写,每个 vdev------无论是 support vdev 还是 storage vdev------都是由多个 block 或 character devices(在大多数情况下是 disks 或 SSDs)按照特定 topology 组织而成的集合。

1. Storage vdevs

storage vdev 可以使用这几种 topology。 :

  • single
  • mirror
  • RAIDz1/2/3
  • 或 DRAID
    其中前三种 topology 相对比较简单,而 DRAID 则是一个比较特殊的机制,我们会在后面的独立章节中单独介绍。
Single

最简单的一种 vdev 类型,只包含 一个 disk。这里几乎没有 容错性(fault tolerance)(虽然 metadata blocks 会以副本形式存储,但这并不能在 drive 完全故障时保护数据)。在相同 disk 数量 的情况下,这是速度最快的 vdev 类型,但前提是你必须把 backups 做好。

Mirror

Mirror:这种简单的 vdev 类型是速度最快的容错类型(fault-tolerant type)。Mirror 最常见的是 双盘(two-wide) 配置,不过也支持由更多磁盘组成的配置。

在 mirror vdev 中,所有成员设备(member devices)都保存该 vdev 上写入数据的完整副本(full copies)。

读取(reads)的速度最高可以达到单个磁盘的 n 倍,其中 n 是 vdev 中的磁盘数量;但 writes 的性能会受到限制,无论 vdev 中有多少磁盘,其速度都只会略低于单个磁盘,主要原因如下:

  1. Mirror write 需要等待所有磁盘完成
    在 OpenZFS 的 mirror vdev 中,一次写入会同时发送到所有 mirror 成员盘:
bash 复制代码
write
 ├─ disk1
 └─ disk2

IO 只有在 所有磁盘都完成 时才算完成,因此:

bash 复制代码
write latency = max(latency of each disk)

这意味着:

  • 不会比最快盘快
  • 取决于最慢盘
  1. ZFS 软件层的额外开销
    相比单盘直写,ZFS 需要做额外工作:
  • mirror IO split
  • vdev IO queue 调度
  • checksum 计算
  • completion 聚合
    流程类似:
bash 复制代码
ZFS
  ↓
mirror vdev
  ↓
split IO → disk1
         → disk2
  ↓
wait all completion

这些软件层操作带来少量延迟。

  1. IO completion 需要同步聚合
bash 复制代码
write → complete

mirror IO:

bash 复制代码
write
  ↓
disk1 complete
disk2 complete
  ↓
wait both
  ↓
return
RAIDz1

这种条带化的校验虚拟设备(striped parity vdev) 类似于经典的 RAID5。数据会在 vdev 中的所有磁盘之间进行条带化(striped),并且每一行中会保留 一个磁盘用于 parity。

在经典 RAID5 中,parity 会进行 "staggered(交错)"分布,因此不会总是落在同一块磁盘上,这样在 阵列降级(degraded ,比如一个硬盘故障)状态 或 rebuild 过程中可以提高性能。

举一个简单的例子,假设有 4 块磁盘:D0、D1、D2、D3。RAID5 会把数据按 stripe 写入,并让 parity 在不同磁盘之间轮流移动:

bash 复制代码
Stripe 0:  D0: Data0   D1: Data1   D2: Data2   D3: Parity
Stripe 1:  D0: Data3   D1: Data4   D2: Parity  D3: Data5
Stripe 2:  D0: Data6   D1: Parity  D2: Data7   D3: Data8
Stripe 3:  D0: Parity  D1: Data9   D2: Data10  D3: Data11

可以看到:

  • 每一行必须写完整条带(stripe)
  • stripe 宽度固定
  • 每一行(stripe)都会有一个 parity 块,但它的位置在磁盘之间不断轮换。
    这种"交错"布局意味着:
  • 所有磁盘都既存储数据又存储 parity
  • 没有单独的 parity 磁盘成为瓶颈
    如果你只写 4KB 数据,而 stripe 需要 12KB data(因为它的每个条带是固定宽度),RAID5 就必须:
bash 复制代码
read old data
read old parity
recalculate parity
write data
write parity

这就是著名的 RAID5写惩罚(write penalty)

而 RAIDz1 则依赖其 dynamic stripe width(动态条带宽度) 来实现类似效果:与经典 RAID5 不同,RAIDz1 可以用较窄的 条带(stripe) 写入少量数据。

因此,一个 metadata block(一个metadata block 的大小可能小到只占用一个 sector)会被存储为:

  • 一个 data block
  • 一个 parity block
    即使是在一个更大磁盘宽度的 RAIDz1 vdev 上也是如此。
    在 RAIDz1 中,条带(stripe) 不是固定宽度
    为了更进一步澄清动态条带宽度的概念,这里同样假设你有 4 块盘 RAIDz1:
    最大 stripe 可能是:
bash 复制代码
3 data + 1 parity

但如果写入数据很小,只是写入一个metadata block:

RAIDz1 可以写成:

bash 复制代码
1 data + 1 parity

只用 两块盘。

bash 复制代码
disk1 data
disk2 parity
disk3 -
disk4 -

下一次写入可能变成:

bash 复制代码
disk3 data
disk4 parity

所以 parity 自然会分布到所有盘。

这种方式不仅 节省空间并提升性能,还可以将 parity 有效地分布到 vdev 中的所有磁盘上,而不是像某些 stripe 写入那样,总是把 parity 写到同一块磁盘上。

RAIDz2

这是 ZFS 三种 striped parity vdev 拓扑中的第二种(也是最常用的一种)。它的工作方式与 RAIDz1 基本相同,但使用的是 dual parity(双重 parity),而不是 single parity(单重 parity)。

在这种情况下,一个 metadata block 会被写入 三块磁盘,而不是两块:

  • 一块磁盘存储原始 data
  • 另外两块磁盘存储 parity blocks
    对于 RAIDz1 vdev 来说,如果丢失 一块磁盘,系统仍然可以正常工作而不会发生灾难性故障;而 RAIDz2 vdev 则可以在丢失 两块磁盘 的情况下仍然保持正常运行。
RAIDz3

这是最后一种 条带校验拓扑(striped parity topology),它使用 三重校验(triple parity)。这意味着在 三块磁盘故障的情况下,系统仍然不会发生 灾难性故障

同时,这也意味着 元数据(metadata) 和其他 小块(small blocks) 在写入时会被写入 四块磁盘(four drives):

一块磁盘存储 数据(data)

三块磁盘存储 校验块(parity blocks)。

Support vdevs

前面我们已经了解了 存储 vdev 拓扑(storage vdev topologies),接下来再来讨论 vdev 类别(vdev classes)。

需要说明的是,"vdev 类别(vdev class)" 和 "支持 vdev(support vdev)" 并不是 OpenZFS 的官方术语,但它们提供了一种有用的方式,可以帮助我们对 ZFS 如何管理存储进行分类和理解。

目前已经实现的 支持 vdev 类型(support vdev types) 包括:

  • 日志(LOG)
  • 缓存(CACHE)
  • 特殊设备(SPECIAL)
  • 备用设备(SPARE)
Log

我们先从日志设备(LOG vdev)说起,并先澄清一个常见的误解:LOG vdev 并不是写缓存(write cache)或缓冲区(buffer)。

在正常运行情况下,OpenZFS 会将 同步写入(sync writes) 保存到磁盘 两次:

  • 第一次是 立即(immediately) 写入 ZFS 意图日志(ZFS Intent Log,ZIL)
  • 第二次是在稍后的某个时间点写入主存储(main storage)
    如果没有 日志设备(LOG vdev),那么这两次写入都会发生在 主存储 vdev(main storage vdevs) 上。
    当为存储池配置了 独立日志设备(separate log device,SLOG)时,ZIL 就会被写入这个 LOG vdev。换句话说,SLOG 实际上就是用于存放 ZIL 的专用 LOG vdev,其作用只是为 ZIL 提供一个独立且更快的存储位置。

与直觉相反,这种双重写入(double writes) 实际上可以提高同步写入吞吐量(sync write throughput)。其主要原因是通过使用预分配的 ZIL 块(pre-allocated ZIL blocks) 来降低延迟(latency):这样可以避免在写入时花时间去寻找存放数据的位置,同时还能减少 碎片,从而降低寻道延迟(seek latency)。

日志设备(LOG vdev) 只是一个用于存放 ZFS 意图日志(ZFS Intent Log,ZIL) 的地方,而且这个地方 不是主存储(main storage)------仅此而已,没有其他额外功能。因此,通常所说的 SLOG,本质上只是一个 专门用于存放 ZIL 的独立 LOG vdev。

通常情况下,LOG vdev(SLOG) 会使用 低延迟设备,例如 SSD 或 NVMe SSD,因此将数据 同步到它上的速度会明显快于写入 主存储(main storage)。这正是 LOG vdev 类别(LOG vdev class) 存在的原因。

写入 日志设备(LOG vdev) 的数据在正常情况下 不会再次被读取,除非发生 系统崩溃或 内核崩溃。在系统恢复时,ZFS 会读取 ZIL 来重新执行尚未完成的操作。因此,LOG 或 SLOG 都不应该被理解为"写缓冲区(write buffer)"。

虽然一个 高速日志设备(fast LOG vdev / SLOG) 可以极大地加速 同步写入密集型工作负载,但对于 几乎没有同步写入(sync writes)的工作负载来说,它 完全不会产生任何影响。事实上,大多数工作负载------即使是 服务器工作负载------通常也 很少包含同步写入。

同步写入密集型工作负载的典型场景包括:

  • NFS 导出(NFS exports)
  • 数据库(databases)
  • 虚拟机镜像(VM images)

在 早期版本的 ZFS中,如果 日志设备(LOG vdev)丢失,就意味着 整个存储池也会丢失。

而在现代 ZFS 中,LOG vdev的丢失不会危及 存储池或其中的数据。

如果 LOG 发生故障,唯一可能丢失的只是其中的 脏数据(dirty data)------尚未提交到磁盘的数据。

日志设备(LOG vdev可)以使用 单盘拓扑 或 镜像拓扑;但不能使用 RAIDz 拓扑或分布式 RAID 拓扑(DRAID拓扑)。

如果同步写入(sync writes) 数量非常多,希望将负载分散到多个 日志设备(LOG) 上,那么你可以配置 任意数量的 LOG vdev。

CACHE

需要澄清的是,这里的缓存设备(CACHE vdev)并不是指main ARC,而是 二级自适应替换缓存(L2ARC,Level 2 Adaptive Replacement Cache)。

从实现上看,它只是 ARC 的扩展读缓存层,用于在内存 ARC 不足时缓存部分热点数据块。L2ARC 通常部署在 SSD 或 NVMe SSD 等高速设备上。
与 ARC(内存缓存)不同,L2ARC 只是 二级缓存层,其数据始终是主存储中数据块的副本。

实际上它并不是一个真正的 自适应替换缓存(ARC,Adaptive Replacement Cache)。L2ARC 本质上是一个简单的后进后出环形缓冲区(LILO ring buffer,Last-In-Last-Out)。它并不是由那些已经从 ARC 中淘汰的数据块来填充,而是由那些可能很快就会从 ARC 中被淘汰 的数据块提前写入。

CACHE vdev 上对写入有一个相当严格的限速(write throttle),目的是防止它迅速把 SSD 的写入寿命耗尽。这也意味着,你之前读取过的某个数据块仍然存在于 CACHE vdev 中、并再次命中的概率,比想象的要低。再加上主 ARC 本身的命中率已经非常高,因此在大多数工作负载下,CACHE vdev 实际上很少被命中。CACHE vdev 本身也不可能拥有很高的命中率,因为一旦发生命中,该数据块就会被重新移动回主 ARC 中。

CACHE vdev 里的数据一定来自主存储(main pool)

它不是唯一副本的原因是:

  • CACHE 只缓存已经安全写入主存储的 clean block。L2ARC / CACHE 只是把 已经存在于主存储的数据块复制一份作为缓存。
  • 只有 clean block(即未受污染的干净的数据块) 才会进入 CACHE
  • 还没写入主存储的 dirty block 不会被放进 L2ARC。

因此 CACHE 永远不会保存唯一副本,如果 CACHE 丢失:

bash 复制代码
缓存没了
↓
重新从主存储读取
SPECIAL

SPECIAL vdev 是最新引入的一种支持类设备。 它的出现主要是为了弥补 DRAID vdev 的一些劣势。当你在一个存储池中添加 SPECIAL vdev 后,该池之后产生的所有元数据写入 都会落在 SPECIAL 上,而不是写入主存储设备。

值得提到的是,RAIDZ 也有这个问题,但 dRAID 的随机 I/O 更严重,因此 SPECIAL vdev 对 dRAID 的提升更明显,主要理由如下:

  1. DRAID 的数据分布更分散
    在 dRAID 中,数据和校验块会被均匀分布到整个磁盘集合中,并且还预留了 distributed spare。
    结果是:
  • 一个小的元数据读取
  • 可能需要访问很多不同的磁盘位置
  1. 元数据访问非常频繁
    例如目录遍历、打开文件等操作都会大量读取 metadata,如果这些都在 HDD 上,会产生很多随机读。
    例如:
bash 复制代码
ls -l
遍历目录
打开文件
snapshot
dataset 操作

这些都会读取medata:

bash 复制代码
dnode
indirect block
block pointer
object set metadata

如果这些 metadata 在 HDD dRAID vdev 上,就会产生大量随机 I/O。

  1. 另一个原因与dRAID 的典型使用场景有关,即DRAID通常用于非常大的 HDD 阵列,几十到几百块盘

在这种规模下:

  • metadata 随机 I/O 成本更高
  • SSD metadata tier 的收益更大

此外,SPECIAL vdev 还可以选择用于存储小数据块。

例如,当设置 special_small_blocks=4K 时,大小为 4 KiB 或更小的文件将会完全存储在 SPECIAL 上;当设置 special_small_blocks=64K 时,大小为 64 KiB 或更小的文件也会完全存储在 SPECIAL 上。

special_small_blocks 是一个可调参数,可以在每个 dataset 级别单独设置。

但对于 元数据(metadata块,则无法选择不存储到 SPECIAL 上:只要存储池中存在一个或多个 SPECIAL vdev,所有元数据写入都会被写入到这些 SPECIAL 设备上,没有例外

丢失任何一个 SPECIAL vdev,就像丢失任何一个storage vdev 一样,会导致整个存储池失效。 因此,SPECIAL 必须采用具备容错能力的拓扑结构,而且其容错能力需要与存储 vdev 保持一致。

例如,如果一个存储池使用 RAIDz3 作为存储 vdev,那么 SPECIAL vdev 就需要使用四盘镜像(quadruple mirror)------因为 RAIDz3 的存储 vdev 可以承受三块磁盘故障,所以 SPECIAL 也必须能够承受三块磁盘故障。

与 LOG vdev 类似,如果需要将负载分散到更多磁盘上,你可以添加任意数量的 SPECIAL vdev。不过,与 LOG 不同的是,一旦丢失任何一个 SPECIAL vdev,整个存储池也会随之不可恢复地丢失。

SPARE

SPARE vdev 在正常运行时不会执行任何操作,但会处于待命状态,一旦某个 vdev 中的磁盘发生故障,它就会被自动加入并进行数据重建。显然,要想自动替换故障磁盘,SPARE 的容量至少必须与故障磁盘一样大。

如果你的存储池只有 一个存储 vdev,那么其实不需要 SPARE。SPARE 的作用在于:当池中的某个 vdev 进入 降级(degraded)状态 时,它可以被用来替换其中发生故障的磁盘。

而在只有单个 vdev 的存储池中,与其配置 SPARE,不如直接提高冗余级别更合理。比如,在一个 12 盘位的系统中,使用 11 盘宽的 RAIDz3 vdev,其性能会明显优于 10 盘宽的 RAIDz2 vdev 再加一个 SPARE。

是否需要 SPARE vdev 的主要指标是 存储 vdev 的数量,而不是 存储磁盘的数量。如果存储池中有 六个或更多 vdev,几乎可以肯定应该配置一个或多个SPARE。

而且这六个 vdev 是 两个磁盘镜像(总共十二块盘),还是 十个磁盘宽度的 RAIDz2(总共六十块盘),其实差别不大;无论是哪种情况,当某个 vdev 进入 降级(degraded)状态 时,你都会非常希望通过 SPARE 来缩短系统处于脆弱状态的时间窗口。

vdev 类型 vdev 数 每个 vdev 盘数 总盘数
two-disk mirror 6 2 12
10-wide RAIDZ2 6 10(8data+2parity) 60
DRAID

前面已经多次提到过 DRAID,如果不了解它之前的设计(比如传统 vdev、SPARE 等)就很难理解 DRAID------因此,在我们已经介绍了标准的 vdev 拓扑结构以及支持类 vdev 类型(尤其是 SPARE)之后,现在来讨论 DRAID。

这种拓扑是为拥有 60 个或更多硬盘的系统设计的,它用一个单一的、极其巨大的 vdev,取代了传统的由多个Sotrage vdev 和 SPARE vdev 组成的架构。

这个庞大的 vdev 同时集成了 RAIDz 和 SPARE 的功能。

假设你正好有 60 块磁盘,并且想用它们来构建一个 DRAID。第一步是决定你的条带于宽度(stripe width) 和 校验冗余级别(parity level),与 RAIDz 不同,这两者并不严格受制可用驱动器数量的限制。

在 DRAID 出现之前,你可能会决定把这 60 块磁盘划分为:9 个由 6 盘组成的 RAIDz2 vdev,再加上 6 个 SPARE vdev。一旦确定了每个 vdev 的宽度(vdev width)和校验级别(parity level),vdev 的数量也就随之固定:本质上,在总共 60 块磁盘上,你不可能创建超过 10 个6盘宽"的 vdev。

DRAID 提供了更高的灵活性:你可以选择 DRAID2:4:4(每个 stripe 包含 2 个校验块和 4 个数据块,而整个 DRAID 在所有磁盘上分布式地预留了相当于 4 块磁盘容量的备用空间),但你同样也可以选择 DRAID3:4:4。显然,每个 stripe 中 3 个校验块加 4 个数据块(共 7 个 block)并不能整除你总共的 60 块磁盘......但不用担心,因为 DRAID 会在需要时对 stripe 进行"环绕(wrap)"分布,使其跨越磁盘矩阵进行放置,从而适配你现有的磁盘布局。

  1. 场景:DRAID2:4:4(60 块盘)
项目 数值 说明
数据块(data) 4 用户数据
校验块(parity) 2 可容忍最多 3 盘故障
每条 stripe 总块数 6 = 4 + 2
分布方式 跨 6 块盘 每个 block 落在不同磁盘
spare 相当于4块硬盘的容量 从60块硬盘中预留相当于4块硬盘的容量作为spare空间(而不是像RAIDZ中固定硬盘作为spare设备),60 块盘 × 每盘预留一小部分≈ 总共等效 4 块盘容量

DRAID2:4:4 条带范例:

bash 复制代码
Stripe #1:
D1  D2  D3  D4  P1  P2

条带分布示列:

Disk 内容
Disk8 D1
Disk15 D2
Disk22 D3
Disk30 D4
Disk41 P1
Disk6- P2

Stripe #2:

会换另外 6 块盘(轮转分布),不是固定哪 6 块盘,而是在 60 块盘里不断"游走"

因为DRAID2:4:4的条带长度是6,能被60整除,所以不存在wrap(回绕)的情形

我们再来看场景2

2.场景:DRAID3:4:4(60 块盘)

项目 数值 说明
数据块(data) 4 用户数据
校验块(parity) 3 可容忍最多 3 盘故障
每条 stripe 总块数 7 = 4 + 3
分布方式 跨7块盘 每个 block 落在不同磁盘
spare 相当于4块硬盘的容量 从60块硬盘中预留相当于4块硬盘的容量作为spare空间(而不是像RAIDZ中固定硬盘作为spare设备),60 块盘 × 每盘预留一小部分≈ 总共等效 4 块盘容量

DRAID3:4:4 条带范例:

bash 复制代码
Stripe #1:
D1  D2  D3  D4  P1  P2 P3

条带分布示例:

Disk 内容
Disk8 D1
Disk15 D2
Disk22 D3
Disk30 D4
Disk41 P1
Disk52 P2
Disk60 P3

3.wrap(环绕)

顺序 Disk 内容
1 Disk58 D1
2 Disk59 D2
3 Disk60 D3
4 Disk1 D4
5 Disk2 P1
6 Disk3 P2
7 Disk4 P3

stripe 从 60 走到头,然后从 1 继续,如果理解成环形将会更直观:

bash 复制代码
(环形)

        [1] [2] [3]
     [60]       [4]
     [59]       [5]
     [58]       [6]
        [57] [56] ...

Stripe 跨过去:
57 → 58 → 59 → 60 → 1 → 2 → 3

到这里,我们应该理解了"环绕",就是:stripe 写到磁盘末尾时,不停下来,而是从开头继续写

不过,这里还是有个小代价:如果 dRAID 的条带宽度不能被硬盘总数整除(例如 7 除不尽 60),系统就需要对条带进行"环绕(wrap)"处理,导致数据在硬盘上的分布无法做到最优对齐。这可能会带来一定的性能损失,尤其是在处理小文件或随机 I/O 时,读写效率会稍微降低一些。

但在拥有 60 块甚至更多硬盘的超大规模系统中,这个问题通常可以忽略不计。因为此时整个存储系统的瓶颈,已经不再是单个硬盘的性能,而是硬盘控制器(HBA 卡、PCIe 通道等)的整体吞吐能力。单个硬盘或条带排列带来的那点轻微损失,在海量并行 I/O 面前就显得微不足道了。

在 DRAID 配置中,并不是像传统 RAIDZ 那样,把某一块磁盘固定对应到某一个特定条带(stripe)。相反,dRAID vdev 的每一个成员数据都会分布到 vdev 中的全部 60 块磁盘上。

因此,当某一块磁盘发生故障时,并不是丢失某一个完整的条带,而是等价于从这 60 个成员中的每一个都损失了 1/60 的数据。

下图描述了RAIDz 与 DRAID 的重建(Resilvering)行为

上图结构说明:

  1. 基本配置:
    两边用的是完全相同的11块盘(编号0~10),只是组织方式不同。
  2. 左边:传统 RAIDz
区域 说明
RAIDz1-0 盘 0~4 独立的一组,5块盘
RAIDz1-1 盘 5~9 独立的一组,5块盘
Hot Spare 盘 10 热备盘,平时完全闲置
  • 红色的 0 代表 parity 块,集中固定在盘0这一列
  • RAIDz1-1 实际上也有自己的 parity,图中未标红,属于示意图简化
  • 两个 RAIDz 组互相独立,parity 不跨组共享
  • 绿色的盘10是热备盘,平时完全空闲浪费
  1. 右边:DRAID(Declustering 后)
区域 说明
Group 0 打散重组后的第一组
Group 1 打散重组后的第二组
Logical Spare 逻辑备用,分散在所有盘上
  • 红色的 0 依然代表 parity 块 ,但不再固定在某一列,而是打散分布到所有盘的各个位置
  • 绿色的备用容量也不再是单独一列,而是每块盘都贡献一部分(Logical Spare)
  • 这正是 Declustering(去集中化) 的核心------parity 和备用容量均匀散布在所有盘上

这张图展示的是:同样11块盘,左边 RAIDz 的 parity 块集中固定在某一列,热备盘整块闲置;右边 DRAID 通过 Declustering 把 parity 块和备用容量全部打散到所有盘上,既消灭了热备盘的浪费,又让 resilver 可以所有盘并行加速。

如果我们的 DRAID vdev 中有一块硬盘发生故障,原本属于该故障硬盘的数据块会通过校验信息重建,并重新写入到 DRAID 内部"分布式备用(SPARE)空间"中。这种重建(resilver)过程比传统将数据重建到一个独立 SPARE vdev 上要快得多。

举个对比的例子:假设之前有一个由 6 组、每组 9 盘宽的 RAIDZ2(并配有 6 块备用盘)。当一块硬盘故障时,系统会从剩余的 8 块盘读取数据,并写入到 1 块备用盘中。

为何需要读取剩余的8块盘?

因为这里一个条带跨9盘分布

在坏的是数据块的情况下,需要读取剩余的8块盘,用"剩下的所有数据块 + 校验块"来反推

如果坏的是校验块,因为校验块是由所有数据计算出来的,因此仍然要读取所有其他块(7 数据 + 1 校验)

而在 DRAID 中,由于传统重建的主要瓶颈------"写入速度"------现在被分散到其余的 59 块硬盘上,而不是集中在一块盘上,因此性能大大提升。也就是说,DRAID 会同时从 59 块盘读取,并向 59 块盘写入,这显然比传统 RAIDZ 的"8 读 + 1 写"要快得多。

但是,为了回收那些已被占用的分布式备用容量,DRAID vdev 的运维人员仍然必须对一块新磁盘执行传统的 resilver(重建),而且这次 resilver 的速度会和任何传统 resilver 一样慢。

而且,第一次 resilver 存在一个脆弱窗口期(以上图只配备一个parity为例,此时 DRAID 已经少了一个奇偶校验级别,这时如果再有磁盘故障,数据就会丢失),而第二次则不存在脆弱窗口期(因为缺失的奇偶校验级别已经通过分布式备用容量的利用而得到了恢复)。

不过,DRAID vdev 也有其缺点------最大的缺点是它失去了传统 RAIDz vdev 的动态条带宽度存储能力。如果你配置的是 DRAID2:4:4,每个条带必须固定是6块盘宽(4个数据块 + 2个奇偶校验块),即使该条带只存储一个 4KiB 的元数据块也是如此。

这也正是 SPECIAL vdev 发挥作用的地方------虽然 SPECIAL 对传统存储池来说只是锦上添花,但对于拥有宽条带 DRAID vdev 的存储池来说,它几乎是必不可少的。将元数据(以及可选的其他非常小的数据块)存放在 SPECIAL vdev 上,意味着不必再把它们浪费性地存储在全宽条带中,从而避免了大量磁盘空间和性能的浪费!

二、从功能视角看ZPool

  • 从功能上来说,zpool 是一个 storage object,可以进一步划分为 :
  • datasets(看起来像文件夹)
  • zvols(看起来像简单的 block/character devices,例如 raw disks)。
    在 OpenZFS 中,dataset 是一个非常核心但也容易让初学者混淆的概念。
    很多存储系统会区分:
  • 文件系统(filesystem)
  • 逻辑卷(logical volume)
  • 快照(snapshot)
    但在 ZFS 的设计里,这些对象都统一抽象为:dataset
    Dataset 的层次结构

也就是说:

dataset 并不只是"数据集",

而是 ZFS 中"可被管理的数据对象"的统称。

Dataset 的层次结构

从逻辑结构来看:

bash 复制代码
Physical Disks
      ↓
    vdev
      ↓
    zpool
      ↓
   datasets
      ↓
filesystem / zvol / snapshot / bookmark

其中dataset是位于这个逻辑结构的最底层,有分为4个类型:

bash 复制代码
dataset
 ├── filesystem
 ├── volume(zvol)
 ├── snapshot
 └── bookmark
1. Filesystem Dataset

这是最常见的 dataset 类型。

例如:

bash 复制代码
zfs create /ZFS100G/data

创建完成后,它会像一般 Linux 文件系统一样,可直接进行文件读写操作:

bash 复制代码
root@idpcipve01:~# ls -alh /ZFS100G/ | grep -v '\.$'
total 5.0K
drwxr-xr-x  2 root root    2 May 10 10:11 data

同时,也可以通过 zfs 命令查看该 dataset 的相关信息:

bash 复制代码
zfs list -t filesystem
NAME           USED  AVAIL  REFER  MOUNTPOINT
ZFS100G        672K  9.20G    96K  /ZFS100G
ZFS100G/data    96K  9.20G    96K  /ZFS100G/data
2. Volume Dataset(zvol)

volume dataset 通常简称:zvol

它不是文件系统,而是:

它并不是传统意义上的文件系统,而是由 ZFS 提供的虚拟块设备(block device)。可供 iSCSI、数据库或虚拟机磁盘等场景使用。

例如:

bash 复制代码
zfs create -V 1G ZFS100G/vm01

创建完成后,系统会在 /dev/zvol/ 下生成对应的设备文件:

bash 复制代码
 ls -alh /dev/zvol/ZFS100G/vm01
lrwxrwxrwx 1 root root 9 May 10 10:56 /dev/zvol/ZFS100G/vm01 -> ../../zd0

其中:

  • /dev/zvol/ZFS100G/vm01 为 ZFS 提供的逻辑块设备
  • zd0 为 Linux 内核识别到的实际 block device
    系统会将其视为一块虚拟磁盘,因此可以进一步:
  • 格式化文件系统(ext4、xfs 等)
  • 挂载使用
  • 提供给虚拟机作为磁盘
  • 用于 iSCSI、数据库等需要 block storage 的场景

也可以通过 zfs 命令查看该 volume:

bash 复制代码
 zfs list -t volume
NAME           USED  AVAIL  REFER  MOUNTPOINT
ZFS100G/vm01  1.02G  9.20G    56K 

filesystem 与 zvol 的区别

两者最本质的差异是:

类型 面向对象
filesystem dataset 文件
zvol(volume dataset) block device
3. Snapshot Dataset

snapshot某个 dataset 在特定时间点的只读状态。

它的概念与 VMware 虚拟机 snapshot 类似,可用于:

  • 数据回滚
  • 误删恢复
  • 备份
  • 快速复制
  • 历史版本保留
    不过 ZFS snapshot 是基于 Copy-on-Write(COW)机制实现,因此建立速度非常快,且初始几乎不占额外空间。
    需要注意的是:

当原始数据发生修改时,ZFS 会将新数据写入新的 block,而旧 block 会因 snapshot 仍在引用而被保留。

因此,snapshot 本身初始占用空间通常很小,但随着数据持续变更以及 snapshot 数量增加,被保留的历史数据块也会逐渐占用更多存储空间。

创建snapshot:

bash 复制代码
zfs snapshot  ZFS100G/data@snap01

如下命令可查看已经创建的snapshot:

bash 复制代码
zfs list -t snapshot
NAME                  USED  AVAIL  REFER  MOUNTPOINT
ZFS100G/data@snap01     0B      -    96K  -
ZFS100G/vm01@snap01     0B      -    56K  -
4. Bookmark Dataset

Bookmark 是 ZFS 中一种轻量级的快照参考点,可以理解为「快照的书签」。

为了说明bookmark的作用,我们先跟随下面的步骤

  • 建立 Bookmark
    首先查看现有的 snapshot:
bash 复制代码
zfs list -t snapshot
NAME                  USED  AVAIL  REFER  MOUNTPOINT
ZFS100G/data@snap01    56K      -    96K  -
ZFS100G/vm01@snap01     0B      -    56K  -
  • 建立 bookmark(
    從 snapshot 建立 bookmark(注意:snapshot 用 @,bookmark 用 #):
bash 复制代码
zfs bookmark ZFS100G/data@snap01 ZFS100G/data#bm01
  • 查看建立的 bookmark:
bash 复制代码
zfs list -t bookmark
NAME                USED  AVAIL  REFER  MOUNTPOINT
ZFS100G/data#bm01      -      -    96K  

可以看到 USED 字段為 -,代表 bookmark 几乎不占用用任何磁盘空间。

Bookmark 的本质:只保存 createtxg

  • 查看 snapshot 的 createtxg:
bash 复制代码
 zfs get createtxg  ZFS100G/data@snap01
NAME                 PROPERTY   VALUE      SOURCE
ZFS100G/data@snap01  createtxg  1401685 
  • 查看 bookmark 的 createtxg:
bash 复制代码
 zfs get createtxg  ZFS100G/data#bm01
NAME               PROPERTY   VALUE      SOURCE
ZFS100G/data#bm01  createtxg  1401685    -

两者的 createtxg 完全相同。这说明 bookmark 的核心就是保存了这个 TXG ID(Transaction Group ID)。

TXG 是 ZFS 的写入提交序号,每个资料区块都带有「由哪个 TXG 写入」的标记。有了这个序号,ZFS 在做增量传输(zfs send)时就能精确找出「哪些数据区块是在此之后发生变更的」。

核心使用场景:节省来源端空间的增量备份

Bookmark 最典型的用途是在备份完成后,让来源端可以删除 snapshot 释放空间,同时仍保留继续做增量备份的能力:

不同 snapshot 钉住不同版本的 block,就是为了保留每个时间点的完整状态,让你能还原到任意一个过去的时间点。代价就是这些旧版本 block 无法释放,snapshot 保留越多,占用空间越大。

我们来看看大致的过程:

bash 复制代码
# 1. 建立 snapshot
zfs snapshot ZFS100G/data@snap01

# 2. 全量传输到备份端
zfs send ZFS100G/data@snap01 | zfs receive backup/data

# 3. 建立 bookmark,记录传输基准点
zfs bookmark ZFS100G/data@snap01 ZFS100G/data#bm01

# 4. 删除来源端 snapshot,释放空间
zfs destroy ZFS100G/data@snap01

# 5. 下次备份时,以 bookmark 为起点做增量传输
zfs snapshot ZFS100G/data@snap02
zfs send -i ZFS100G/data#bm01 ZFS100G/data@snap02 | zfs receive backup/data

整个流程中,来源端通过 bookmark 记录「上次传输到哪个 TXG」,下次只需传输该 TXG 之后发生变更的数据块,无需保留占用空间的旧 snapshot。

需要注意的是:

bookmark 除了记录 TXG 外,会保留 snapshot lineage metadata等其他信息,用于确认 source 与 target 曾经共享同一个 snapshot ancestor。

否则:

增量 replication 的 block delta 将无法正确套用到目标端。

总结

本文完整拆解了 ZFS 存储池(zpool)的核心架构与各类 vdev 的设计逻辑、适用场景与选型原则。zpool 本质是storage vdev 与 support vdev 的集合,数据分布与容错能力均以 vdev 为基本单元,合理规划 vdev 拓扑直接决定存储系统的冗余能力、性能表现与空间利用率。

在存储 vdev 方面,Mirror 提供最佳读写性能与简单可靠的冗余,适合高性能、低延迟场景;RAIDz1/RAIDz2/RAIDz3 以校验方式实现不同等级容错,空间利用率更高,是大容量存储的主流选择,其中 RAIDz2 兼顾安全性与成本,通用性最强;DRAID 面向超大规模磁盘阵列,通过分布式重构与分布式热备大幅缩短重建窗口,提升集群整体可靠性,但需配合 SPECIAL vdev 弥补随机 I/O 短板。

Support vdev 作为性能增强组件,LOG(SLOG) 加速同步写入,适配数据库、NFS、虚拟化等关键业务;CACHE(L2ARC) 扩展内存读缓存,提升热点数据访问效率;SPECIAL vdev 专门承载元数据与小数据块,显著优化大规模阵列(尤其 DRAID)的随机性能;SPARE 则缩短降级运行时间,提升多 vdev 环境的可靠性。

从功能管理视角,ZFS 以 dataset 统一抽象文件系统、卷、快照与书签,实现灵活的资源分配、高效快照与轻量增量备份,简化运维并降低数据保护成本。

综上,ZFS 存储池配置无绝对最优方案,需围绕数据重要性、性能需求、磁盘规模与运维成本综合选型:追求性能优先选 Mirror;容量与平衡优先选 RAIDz2;大规模集群优先用 DRAID 并搭配 SPECIAL、LOG 等辅助 vdev;同时善用快照与书签建立可靠备份体系,最终构建出高性能、高可靠、易维护的企业级存储架构。

相关推荐
CableTech_SQH1 小时前
江苏理工学院武进绿建区协同创新园智能化建设 F5G 全光方案百盛分析报告
大数据·网络·数据库·5g·信息与通信
剑神一笑1 小时前
Linux top 命令深度解析:进程监控的性能优化实战
linux·运维·正则表达式
上海云盾-小余1 小时前
企业云服务器安全底线:漏洞自查、攻击溯源与应急防护手册
运维·服务器·安全
楼田莉子1 小时前
Linux网络:网络多路IO模型详解
linux·网络·数据库
wen_zhufeng1 小时前
python-dotenv 使用文档
数据库·python·oracle
phltxy1 小时前
Redis Java 集成到 Spring Boot
数据库·redis·git
HMS工业网络1 小时前
技术干货:EtherCAT设备ESI(XML)文件中的CompleteAccess关键字有什么作用
xml·运维·服务器
dadaobusi2 小时前
PCIe的ATS和PRS
java·网络·数据库
汽车仪器仪表相关领域2 小时前
HORIBA MEXA-584L 全功能汽车排放废气分析仪:便携精准排放检测 + 多参数同步测量 + 国六 / 欧 7 合规适配,汽车检测与调校的黄金标准
服务器·数据库·人工智能·功能测试·汽车·压力测试·可用性测试