delphij's Chaos

选择chaos这个词是因为~~实在很难找到一个更合适的词来形容这儿了……

08 Apr 2008

ZFS时代FreeBSD系统的数据冗余策略

2008硬盘磨损年!

我相信很多人都遇到过硬盘卡壳、掉链子的情况。当然,这篇文档的主旨不是告诉你怎么样可以绕过那些老爷子写的课本上说的金科玉律──重要的数据都应该有备份──如果你的数据最终丢失了,那么我的问题是:你的备份呢?

但是,即使你有经常备份的习惯,有些数据还是会难免出现一些没有及时备份而导致丢失的情况。我的观点是,没有备份计划的数据都不是重要数据,不要等到数据丢失了再去后悔,但是我们显然应该采取各种各样的手段来阻止没有及时备份的那一小部分数据的丢失。

硬盘

大家一起默念:它很便宜!它会坏掉!

是的,实战经验会告诉你,它很便宜!它也会坏掉!不管这个硬盘是来自什么厂商,也不管它是SATA、SCSI、SAS或者是传统的ATA接口,它出现故障只是时间早晚的问题。

为了解决这个问题,人们提出了廉价磁盘冗余阵列(RAID)的概念。例如,使用两块相同容量的磁盘组成 RAID-1 (MIRROR) 阵列,可以在其中任意一块出现问题时,从另一块中取出数据。而如果有至少3块硬盘,便可以组成 RAID-5 (注:还有其他RAID级别可以用3块硬盘组成冗余结构),只损失 1/n 的容量(n为硬盘数量)来得到带冗余的存储,使得存储可靠性得以提高。

除了改善可靠性之外,RAID还可以用来改善读写性能。例如用多块硬盘组成 RAID-0 阵列,可以将读写性能提高 n 倍,等等。我们并不讨论这些RAID级别。

不幸会发生

和很多人已经想到的一样──不要高兴的太早……

带数据冗余的 RAID 的一个基本假设是,磁盘是不骗人的,它有两种状态:好、坏,并且,主机(或RAID控制器)能够可靠地识别这种状态。

很不幸,这句话只对了一半。一块磁盘要么是好的、要么是坏的(这里,“坏的"的定义是读写时会发生任何错误),但是主机未必能够识别这种状态。

更为严重的是,有些时候主机甚至连读出来的数据是否是正确的这件事都不知道!当你发现自己的程序在其它机器上都很正常,但是在某台机器上总是神秘的崩溃的时候,你就要看看是不是那台机器的内存或者其他存储器出现问题了。

经历了大量痛苦的数据恢复工作之后,终于有人想出了一个主意:把数据的校验和放到别的地方,例如,引用这块数据的地方,并且将元数据保存多份。解决问题的方法有很多,一种是花更多的钱去买更好的硬件,而另一种,则是采用一些合理的技术手段来降低硬件发生问题时的影响──你不是不可靠吗?那么我检查你,确认你的数据是对的我才接受;你不是容易丢失数据吗?那我就多存几份,多到将真的丢失数据的概率降到可以接受的水平为止。

这种技巧是广大劳动人民在长期的科研实践中总结出来的,并且在历史上有着相当广泛的应用。例如,传统 BSD 系统中的伯克利快速文件系统(FFS,在现代 FreeBSD 版本中称为 UFS)会保存大量的关键元数据──超级块和柱面组映射表的副本,而这些副本在布局的时候,被保证不会放在磁盘的相同盘面、柱面或扇区上,从而保证了磁盘在失去一个盘面、被划伤一个柱面,或者被子弹击穿时,仍然会有可用的超级块副本存在,从而能够恢复部分数据。

不过,简单地把所有数据保存多份有时是不需要的。首先,它会影响写入性能,并损害写操作的原子性。其次,存储设备尽管越来越便宜,但它总归还是要钱的,你不会希望把钱均匀的用在不同的数据上面──举个例子,你花12个小时写了一些代码,这些代码丢了你会很心疼;但是你的程序有bug,运行5分钟之后吐了一个2GB的core文件,这个文件丢了,你可能会觉得无所谓,至少没有丢掉代码那样心疼。那么,假如有3份存储资源,我想多数人不会给两种文件各分配1.5份,因为很明显,给代码分配2份,而给core文件分配1份,使得代码数据能够有更高的冗余度,是比较合算的做法。

对于RAID来说,一个比较常见的做法便是将一部分划成RAID-0卷,另一部分划成RAID-5卷。不过,这个做法并不完美,因为你并不能动态调整这两个卷的尺寸。如果之前规划的卷尺寸不合适,那只有停机、导出数据、重划RAID,导入数据,并祈祷下次发现不合适的时间越晚越好。

当然,这是一个极端的例子。对于个人用户来说,他们往往并不会在意 RAID-0 所带来的那一点点性能改善,而数据的可靠性才是他们更关注的事情。

个人系统中的数据冗余

虽然 RAID 是廉价磁盘冗余阵列,但是事实上它并不是那么的廉价。更重要的是,许多便携式系统,如笔记本计算机,并不具备安装多块硬盘的条件(无论是体积还是重量)。这类用户可能只有一块硬盘,但是仍然希望尽量提高他们系统中数据的可靠性。这个时候,就需要做一些存储规划了。

以我个人的笔记本计算机为例,它安装的是双系统,Windows和FreeBSD。平时工作的时候主要使用的是 FreeBSD,工作内容包括代码编写和调试、远程登录和撰写文档等等。

我们可以简单地把这个系统中的数据分成下面几类:

o 操作系统。
o 第三方软件(如 X11 视窗系统、Eclipse、OpenOffice办公系统等)
o 下载的数据(例如 ports、其 distfiles等等),其特点是来自互联网,只要带宽够,既不需要备份,也不担心丢失。
o 用户数据 (完全不希望丢失的数据)

我使用 ZFS 作为 / 分区(注意:这种做法有好处也有缺点,我们将在最后讨论),由于只有一块硬盘,因此除了用于引导的 UFS 之外,其余部分全部划给同一个 zpool。

针对不同类型数据的备份和冗余需要,在这个 zpool 上面建立了 4 个 ZFS:

/ - 即 zpool 本身的 ZFS,用于存放操作系统
/.portspace - 保存第三方软件(为什么分开放将在后面解释)
/.download - 保存下载的数据,例如 CVS 代码库、ports本身及其distfiles等
/.data - 保存用户数据,包括 /home,/usr/src,自己工作用的代码库等等。这部分数据采取的保存策略是压缩,同时设置 copies = 2。

之后,做下列调整:

使用符号链接将 /usr/local 和 /var/db/pkg 放到 /.portspace 上。这样做的好处是,在进行 port 升级的时候(例如恐怖的OpenOffice、xorg之类的升级),你可以做下面的操作:

zfs snapshot tank/.portspace@preclean
rm -fr /usr/local/* /var/db/pkg/*

或者,如果出现问题,可以用zfs的rollback功能回到快照版本,更变态的用法甚至可以跑branch出来。

关于快照

ZFS可以在文件系统中建立大量的快照。通过脚本可以将快照操作自动化。sysutils/freebsd-snapshot提供了一组易于配置的snapshot工具,我的配置中,对 / 和 /.portspace 每天中午的时候做一次snapshot,保留3份;对 /.download 每逢6的整数倍点做一次snapshot,保留2份;对/.data每小时做一次snapshot,保留25份。

成果

在本次硬盘坏损中,使用recoverdisk恢复了3天,最后有大约3.4MB(IBM记法;最初的结果是有大约2GB数据存在问题,占磁盘总容量的5%)数据未能恢复。

使用zpool scrub共发现650处坏损数据,共导致出现了132个无法恢复的数据块,这些数据块分属于20个文件,/.data 中完全没有丢失数据。

其他一些问题:

A。是否应使用 ZFS 作为 /?
答:取决于具体情况。ZFS作为/对于安装、故障恢复都会带来一些困难,并且其基于快照的revert能力也不能直接使用,只能通过复制或rsync的方法来恢复快照中的内容。

B。磁盘出现故障,通过recoverdisk恢复之后,ZFS说卷degraded,如何恢复?
答:首先zfs export,然后再重新zfs import(可能需要-f参数)。注意此时/boot/zfs必须可写。

C。ZFS的快照占多少空间?
答:取决于修改的文件数量和数据块数量。通常快照占用的空间可忽略不计(120K左右),除非此后又翻回头来狂砍/狂改文件。