对于很多人来说,文件系统没什么好关心的。Windows 上只能用 NTFS
1,而在 macOS 上,要么 HFS+
要么 APFS
。Linux 上选择多一些,但是一般情况下无脑选 XFS
或者 ext4
即可。
但是文件系统其实是可以很强大的!一些现代文件系统可以实现很多惊人的功能。这里着重介绍一个我很喜欢的文件系统:ZFS。
文件系统入门
早期
日志
为了应对这个问题,人们给文件系统加入了日志功能(journaling)4。支持日志的文件系统会在任何写入操作之前创建一条记录。这样的话,即使发生了意外断电,文件系统的 "状态" 也会被保留下来。在系统重新启动后文件系统即可使用这些记录来修复文件系统。正在写入的数据仍会丢失,但是至少已有的数据不会有问题。
大多数现代操作系统都默认使用日志文件系统,比如 Windows 上的 NTFS
,macOS 曾经的默认文件系统 HFS+
和 Linux 上面的 ext3
, ext4
和 xfs
。
Copy-on-Write(写入时复制)
断电问题的另一种解决方案是 CoW。上面提到,日志文件系统在进行写入操作的时候仍是在原处进行修改,只是在写入前后会记录日志。在 CoW 文件系统当中,当进行写入操作的时候,实际上写入的位置是由文件系统分配的一块全新的区块。在写入完成后,CoW文件系统会将目录中的指针指到新位置上并将旧块标注为空闲(可被 GC 回收),以便在未来使用。
在这种模型下,即使在写入过程中发生了断电,原数据块和目录表仍然完好。而在新的位置上,既然目录没有更改,在目录中新位置仍然是未使用状态,因此万事大吉,文件系统状态完好。
除了避免意外断电带来的文件损失,这种模型还有许多意外的好处。最简单的例子即是 快照 。在日志文件系统上面,创建和维护快照是一个费事费力的行为。而在 CoW 文件系统上则非常简单:在写入完成后,不将原始区块标记为空闲,而是标记为被某快照使用。在访问快照时,只需找出该文件对应的,标记为快照的区块即可。
然而,由于在机械硬盘的特性,这种策略会产生比较严重的磁盘碎片。不过既然现在 HDD 主要作为大容量慢速存储(而且可以使用内存和 SSD 作为存储池的读写缓存),在现在这个时间点上这个问题已不明显。
ZFS 的优势
相对于传统的文件系统,CoW 文件系统天生有利于实现许多高级功能。最明显的高级功能即是上面提到过的快照。这里,我们会介绍 ZFS 相对于其他 CoW 文件系统的几个独特功能。
自愈
听上去不是一个用来描述文件系统的词…
ZFS 没有 fsck
(或者 CHKDSK
,如果你用的是 Windows 的话)。 所以说再也不会在开机界面卡半天了
与之相对的,ZFS 的文件系统检查发生在每一次读取过程中。每当一个存储块被读取时,ZFS 会计算这个块的校验和,并与文件系统中记录的这个块应该有的校验和进行比对。如果校验和匹配,则一切正常;如果不匹配的话,ZFS 就会自动从一个备份源中获取这个块的数据(例如 内存中的缓存,RAIDZ(稍后会提)中的冗余等),将这个正确的数据块提交给程序,并将损坏的块标注为不可靠以免未来再用。
但是,这套机制只对热数据有用。对于冷数据,ZFS 通过一个叫做 scrub
的操作检查数据。这个过程简单来说就是强制读取一遍数据池中所有的数据并检验校验和。合格的 ZFS 运维通常会通过计划任务的方式定期(一般为一星期至一个月一次)执行 scrub
指令。
RAIDZ
RAID 应该是一个很常见的概念了。ZFS 有一套软件 RAID 功能,名叫 RAIDZ
。
与传统的硬件 RAID 卡相比,RAIDZ 是基于数据块的(而不是基于整块磁盘),因此更加灵活和高效。在硬盘失效发生时,传统 RAID 5 阵列必须将硬盘阵列下线以进行耗时的重构,而 ZFS 的自愈功能会自动从 RAIDZ 中的冗余盘中计算出数据并提供给应用程序(当然,有性能降级)。
而且,当 RAID 5 阵列重构失败时,你只能将剩下的数据拷出来,然后重新构建阵列(而重建过程中的高负载有可能会让其他盘也出现故障,导致整个存储池崩溃)。而在 RAIDZ 中,ZFS 可以自动重建所有正常的块,并通知管理员哪些块无法重建;且因为无需像传统 RAID 5 阵列一样重新读取整块硬盘(我们知道哪些地方是有数据的,只要读这一部分就可以),重建导致其他盘出故障的概率也低一些。
透明压缩
ZFS 支持透明压缩。也就是说,(如果启用的话)ZFS 会自动压缩所有你放进去的文件。这不仅仅可以节省空间,甚至在慢速的硬盘上甚至可以提高性能(因为默认的高速压缩算法的压缩速度比写入速度快得多,且压缩后需要写入的数据量降低)。
数据集(dataset)
ZFS 的 数据集 有点类似于 LVM 的逻辑卷,但是它内部仍然是 ZFS 的数据结构。
数据集的主要目的在于方便细化管理。例如,在一个存放图片的数据集上就没有必要打开压缩了(因为 JPG 等早就被高度压缩了)。在存储冷数据的数据集上,则可以打开速度慢但是压缩比高的压缩算法以节省空间。
ZFS 的问题
没有事物是完美的。
许可证不兼容(Linux)
讨论这个问题之前,首先我们得了解一下 ZFS 的历史。Sun Microsystems(在大陆通常被译为 太阳计算机系统)开发了 ZFS 以替换 Solaris 上面老旧的 UFS 文件系统。在 2005 年,ZFS的源代码以 OpenSolaris 一部分的身份被开源。问题是,当时发布时,Sun 选用了 CDDL
作为开源许可协议,而这个协议与 Linux 届常用的 GPL
并不兼容。
这种不兼容直接导致了 ZFS 的代码并不能合并入 Linux 主线代码库中,而且分发者(各种发行版,例如 ubuntu,Arch Linux 等)不能直接分发编译好的内核模块。这就意味着如果用户想要使用 ZFS,他们必须在设备上自行编译 ZFS。这意味着相对于主线支持的文件系统,ZFS 安装起来相对麻烦。以及,在维护无法启动的系统,常用的安装/维护媒介很少支持 ZFS。
无法并入主线内核的另一个后果是,目前为止 OpenZFS for Linux 是在内核源码树之外单独开发的。因此,有时 OpenZFS 很难跟上 Linux 主线代码的发展。
也是因为这个原因,Linux 的开发者们另起炉灶,开发了 BtrFS
。但目前为止,ZFS 的稳定性和功能集仍然领先。
高 RAM 占用
ZFS 使用了自己的 ARC 缓存模型。这带来了更高的缓存命中率,但是这也导致了这部分缓存不在内核的 cached
内存内。因此,当内存不够时,这部分缓存无法被及时的释放。因此,推荐使用 ZFS 的系统准备充足的内存。
无法从阵列中移除硬盘
目前为止,ZFS 无法从一个 RAIDZ 阵列中移除部分硬盘。同样,你也不能缩小一块硬盘上的 ZFS 分区大小。如果是在 NAS 上使用 ZFS 的话,这不算什么。但是如果是在一台个人计算机上使用而且你还经常折腾系统的话,这个缺点非常恼人。
好消息是,ZFSonLinux 从 v0.8.0 开始支持了从 镜像阵列 和 简单阵列(类似 RAID 0)中移除单块硬盘了。
尝试!
如果这都没有吓跑你的话,好极了!
目前 FreeBSD 与 Linux 的 ZFS 开发已被整合到了一个项目中(即 OpenZFS),因此在功能上两者应该没有区别(版本不同带来的区别另当别论)。但由于 FreeBSD 使用相对宽松的 BSD协议,ZFS 可以被合法地整合入 FreeBSD 主线代码中。因此,FreeBSD 开箱支持 ZFS 且可以毫无问题地使用 ZFS 作为根文件系统。然而,由于 FreeBSD 的桌面支持欠佳,它更适合被安装在一台纯粹的服务器上(例如 NAS)。
由于上文提到过的版权原因,Linux 上的 OpenZFS 只能作为树外模块手动安装,但是只要不是刚刚发布的内核版本一般都没什么大问题。而且感谢 DKMS
,其实安装起来也没那么麻烦。
macOS 也有自己的 ZFS 移植版本。它叫 OpenZFS on OS X。从名字即可知这玩意有点年头了,也是一个很稳定的移植,但功能和 OpenZFS 比可能会滞后。
这两年 ZFS 甚至被移植到了 Windows 上面,叫 OpenZFS on Windows。这个移植版本相对较新,而且由于 Windows 闭源且架构和 UNIX 不同,开发比较艰难。目前为止,这个版本还不是很稳定(经常造成蓝屏……),但是开发很活跃,并且正在变得越来越稳定。
更多阅读
文件系统是一个很有趣也很高深的话题,况且 ZFS 可能是目前最为复杂的文件系统。如果你对这一块感兴趣的话,可以读一读/看一看以下内容。
- OpenZFS on YouTube,包含了许多 ZFS 峰会演讲的录像。
- FreeBSD handbook 中的 ZFS 章节
- Will ZFS and non-ECC RAM kill your data? 关于有争议的的 "ZFS 是否必须配合 ECC 内存使用" 问题。
- farseerfc 的 ZFS 分层架构设计,关于 ZFS 的内部架构。
致敬!
感谢所有 创造了 ZFS 的人们!