简单明了的ZFS及其内原理

本文最后更新于:2023年3月18日 晚上

本文主要介绍 ZFS 及其实现原理,个人主观性过强,如有错误及缺漏欢迎指出。

概念

我们定义三层对“磁盘”的抽象。

  1. 磁盘读写与分配层:管理 I/O 和块的分配。
  2. 数据块/数据集抽象层:对磁盘块/扇区进行抽象。ZFS 在这一层。
  3. 文件系统层:构成目录结构。

所以,ZFS 是对于磁盘的二次抽象,充其量只算一个“磁盘管理系统 Plus”。

基本上,ZFS 使用 Merkle 树结构对磁盘块进行校验,通过将磁盘块结构可持久化来保存历史版本,并给出了对数据损坏的处理方法。

抽象的艺术

计算机科学基本定理:任何程序问题都可以通过增加一层抽象层来解决。

基本原理

ZFS 用来管理的机制,无非只有几点:

  • 与底层储存块池的交互
  • 树形结构的哈希/Checksums(Merkle Tree)
  • 写时复制(Copy-on-Write)

底层交互

ZFS 通过与储存池分配器交互来获得数据块。与一般的文件系统不同,ZFS 并不关心获取的数据块在哪里、有多大,他们可以处于不同的驱动器上,是不同大小,甚至可以是一块 128MB 在固态硬盘上,另一块 1GB 放到机械硬盘上!

反过来,这也某种意义上方便了组织数据集(Data Set)的方法。

树上的校验和

ZFS 通过“间接块”(Indirect Blocks)和“直接块”(Direct Blocks)来管理数据。直接块比较直接,只储存数据和数据的校验和;而间接块就比较间接(?),只储存指向的块的 Offset (也就是在磁盘上的块号之类,用来确定位置的) 和 所有指向的块的 Checksum

这样有什么好处呢?我们先看看 ZFS 是怎么组织数据集的:

ZFS 的数据块组织结构。很容易看出来是一棵树,对吧?

利用这种树一样的方式(当然,他可以是多叉树,例如 B 树 或者 2-3 树 等),整个数据集以一棵树来呈现,间接块中储存直接块的哈希/Checksum,父亲块储存子块的哈希的哈希值。不难看出,这是一棵 “leafy”(也就是,数据全部) 的树。这种树就叫做默克尔树(MerkleTree),因为其原理所以又称哈希树。

这棵树的根节点有一个非常炫酷的名字:超级块(SuperBlock or UberBlock)!这是因为他有着整个数据集的校验和。每次我们检查一个块,就可以先算出这个块的校验和,然后与父亲块的校验和进行比较,一直到超级块,就可以确定这个块的正确性了。

嘿嘿,你也不想超级块坏掉的吧,同学~
理所当然,超级块这种很重要的东西一般都是有备份的,因为我们默认超级块的正确性。

不过,带好墨镜,因为下面才是真正的魔法。

Copy On Write

顾名思义,写时复制是在写入数据的时候进行复制。

话说某一天,我在开开心心 Build GCC 的时候,突然有个人过来机惨我,抓住我的电脑电源键,左右摇摆(?),把我电脑关机了。开机之后发现,关机时正好卡在 sync 写入数据的时候,导致文件系统损坏,我应该怎么办才能防止下一次攻击呢?

有的人说,啊啊啊,这玩意不是备份一下就好了吗?很明显,备份对我不起作用。而且,难道我每写几个块就要全部备份一次吗?那样的话时间是两倍,效率却是二分之一。

那到底怎么解决呢?

由于有根树的奇妙性质,我们发现,当我更改一个直接块的时候,只会影响这个块到根的路径上所有的块。既然这样,我们只把路径上的块复制下来不久好了吗?就像这样:

只要我们把所有路径上的块复制一遍然后更改,其他没变的块再保留不就好了吗?

整个流程如下:

  1. 找到要改的块。
  2. 从下到上复制并更改,不变的部分直接继承。
  3. 更新超级块指针,使他指向原有的块。

我们发现这个过程并不会产生任何修改:我们只是在写入时进行复制,就做到了“保留原来的数据”的功能。并且如果要回溯的话很方便:只需要更改一下超级块指针,使其指向想要的时间点的超级块就好了。

这个数据集不一定要按照文件系统目录结构来。但是修改文件的时候确实也需要把文件所在的块和他所属的目录的块进行修改。

同时,为了性能考虑,ZFS 会将磁盘事务捆绑发送给底层的磁盘管理层。

数据安全与恢复

ZFS 特有的结构使得他可以很轻松的进行数据的维护,当然也可以轻松处理数据的恢复与备份。

场景 1:只有几块

想象一下你组了一个 RAID1[1](一个主磁盘,一个镜像磁盘。当然我是没钱组喽),但是突然某一天其中一块盘翘掉了几个分区。但是还好,之前的 CS 学习使你知道,ZFS 可以修复这个小错误。

还记得我们保存的校验和吗?我们可以通过这个来验证哪些块出了问题。一旦知道哪些块除了问题,我们就可以从另一块盘上复制过来,修复这个块。

你问如果两个块都坏了咋办?
非洲人,没救了。
同时出现两个坏块,且坏在同一个位置的概率微乎其微。
如果你想更实用,也可以多加一块,这样会更安全。
(除非你喜欢摔磁盘,或者喜欢带着 NAS 玩高危运动,那么这种储存方式已经不适合你了,我觉得打绳结比较适合你。)

场景 2:整个磁盘

同一个问题,但是你的整个磁盘罢工了。

你也可以试着使用镜像进行恢复,但是一次性复制所有数据实在是太费事了,有没有什么快一点的解决方案呢?

很简单:每次用到一个块,就从镜像里把他读出来,然后扔进新磁盘里,等到系统空闲的时候再全部复制剩下的。

总结

ZFS 的未来前景十分广阔。理论上他的空间可以无限扩展,并且支持校验、修复、热插拔等高级特性,是高性能、高安全性数据存储的理想文件系统——也许这就是“Z”的意义吧。


简单明了的ZFS及其内原理
https://blog.decalvin.tk/2022/10/31/zfs-and-how-it-works/
作者
Franz DeCalvin
发布于
2022年10月31日
更新于
2023年3月18日
许可协议