A ZFS fan
2014年(17)
分类: 服务器与存储
2014-04-18 23:26:04
对于很多的应用环境下,文件系统中数据完整性(Data Integrity)的重要性甚至高于文件系统的读写性能。因此,目前的文件系统或是存储系统都在数据完整性方面下了一番功夫。使用Checksum就是提高数据完整性的一种方式。
对于一般的文件系统来说,一个数据块的Checksum值是和对应的数据存放在一起的(如下图所示),在读取数据的同时就包含了对应数据的Checksum值,以此来检查数据是否有错误。
一般文件系统Checksum方式
这样的checksum方式可以检测到数据块中某个位发生错误。但是这样的校验方式存在很多问题,比如无法检测到静默数据错误(Silent Data Corruption)。这种错误的发生一般是来自硬件本省,比如磁盘损坏,硬件驱动错误,RAID卡损坏等等。举一个形象点得例子,假如我们在网上买了件瓷器,卖家将完整的瓷器包装好,交给快递公司,快递公司之后将瓷器原封不动地交给我们手中时,这里面的瓷器一定是完好无损的?显然是不一定的,说不定运输过程中给颠碎了。同样磁盘也有这样的问题,这些问题虽然难以被检测到,但是这些问题确实经常发生,严重影响了数据的完整性。在ZFS文件系统之前,还没有文件系统能够处理好这些问题。而ZFS在设计时就将这一切考虑到了,我们来看一下ZFS是ZFS通过Checksum来实现完整性校验的。
在ZFS文件系统中,数据的Checksum值并不是与数据存储在一起的,而是将数据的Checksum值存储在指向它的块指针中(如下图所示)。这样数据块的损坏并不会影响到Checksum,这样ZFS就维护了这样一棵树,每个节点都包含了它的子节点的Checksum值。(这里留下一个问题,这棵树的根是没有父节点的,它怎么校验?)。通过这棵树,ZFS可以完成自检工作,在自上而下的自检过程中,由于上面的Checksum值是经过验证的的,所以如果下面的数据发生错误,将很容易被检测出来。
ZFS Checksum存储方式
下面,我们将通过一个实例来看看,如果我们偷偷修改了磁盘上的数据,ZFS和ext4文件系统将会有怎样的反应。
实验OS:Ubuntu 12.04(启动磁盘文件系统为ZFS)。
实验的主要流程:
1. 通过分别在ext4文件系统和ZFS文件系统中分别放置一个相同的文件allocator.txt。
2. 通过文件的在磁盘中的信息,找到第一个块位置(N),以这个blcok位界限,把整个磁盘分成三部分,N之前的部分,N,N之后的部分
3. 通过dd命令将三个部分分别独处放到三个不同的文件中
4. 修改分开后的N的文件,也就是文件的开头所在的block
5. 将三部分重新整合,重新挂载文件系统,看文件是否已经被修改过,文件系统能否发现文件已经被修改了。
首先,我们创建两个文件作为磁盘,分别在两个磁盘上创建ZFS文件系统和ext4文件系统。
# dd if=/dev/zero of=forzfsdiskfile bs=1024k count=100
# dd if=/dev/zero of=forextdiskfile bs=1024k count=100
# ls -lh
total 173M
-rw-r--r-- 1 root root 100M Apr 16 21:43 forextdiskfile
-rw-r--r-- 1 root root 100M Apr 16 21:42 forzfsdiskfile
# mkfs.ext4
mkfs.ext4 mkfs.ext4dev
# mkfs.ext4 -b 4096 forextdiskfile
(省略创建过程中的输出)
# mount -oloop forextdiskfile ext4mountpoint/
# zpool create poolfortest /root/forzfsdiskfile
# zfs create poolfortest/testpool
其次,我们分别放两个同样的文件到这两个新建的文件系统中。我们放入的文件是ZFS的首席设计师Jeff Bonwick之前提出的一篇大作:An Object-Caching Kernel Memory Allocator
# debugfs forextdiskfile
debugfs 1.42 (29-Nov-2011)
debugfs: stats
...
Directory Hash Seed: ea28afd5-8b52-4023-8957-f29217db095f
Journal backup: inode blocks
Directories: 2
Group 0: block bitmap at 8, inode bitmap at 24, inode table at 40
23760 free blocks, 25589 free inodes, 2 used directories, 25589 unused inodes
!!!放置文件之前,空闲的block数目位23760个 !!!
[Checksum 0x8aed]
# du -sh ext4mountpoint/allocator.txt
52Kext4mountpoint/allocator.txt
使用debugfs命令查看一下ext4文件系统中allocator.txt文件的相关信息
# debugfs forextdiskfile
debugfs 1.42 (29-Nov-2011)
debugfs: stats
...
Journal backup: inode blocks
Directories: 2
Group 0: block bitmap at 8, inode bitmap at 24, inode table at 40
23747 free blocks, 25588 free inodes, 2 used directories, 25587 unused inodes
!!!放置文件之后,空闲的block数目为23747个 !!!
[Checksum 0x3bdb]
debugfs: ls
2 (12) . 2 (12) .. 11 (20) lost+found 13 (4052) allocator.txt
debugfs: ncheck 13
InodePathname
13//allocator.txt
。。。
EXTENTS:
(0-12):24592-24604 <— 注意这个值,它表明了这个文件所处的数据块。
下面,将把forextdisk分成三个部分,修改文件,然后重组,确认:
# dd if=forextdiskfile of=/tmp/forextdisk_part1 count=24592 bs=4096
# dd if=forextdiskfile of=/tmp/extfirstblock bs=4096 skip=24592 count=1
# dd if=forextdiskfile of=/tmp/forextdisk_part2 skip=24593 bs=4096
# vi /tmp/extfirstblock <— 这里修改了allocator.txt文件的一些信息,把“An Object-Caching” 改成 “An Object-Cachedd”,这里需要注意的是,修改这样的文件需要保持大小(字符多少)不变。
# dd if=/tmp/extfirstblock of=/tmp/extfirstblock2 ibs=4096 obs=4095 count=1
# 以下开始合并磁盘文件
# cat /tmp/extfirstblock2 >> /tmp/forextdisk_part1
# cat /tmp/forextdisk_part2 >> /tmp/forextdisk_part1
# cp -pRf /tmp/forextdisk_part1 forextdiskfile、
# 一下开始重新挂载文件系统
# fsck.ext4
fsck.ext4 fsck.ext4dev
# fsck.ext4
fsck.ext4 fsck.ext4dev
# fsck.ext4 -v forextdiskfile
e2fsck 1.42 (29-Nov-2011)
forextdiskfile: clean, 12/25600 files, 1853/25600 blocks
# mount -o loop forextdiskfile ext4mountpoint/
# 这里你可以通过debugfs命令查看forextdiskfile磁盘文件中allocator.txt文件的信息,其结果与之前看到的相同。这是我们再来看allocator.txt的前几行
# head ext4mountpoint/allocator.txt
The Slab Allocator:
An Object-Cachedd Kernel Memory Allocator
这里已经不是“Caching“了,但是文件系统什么也没发现。
Jeff Bonwick
Sun Microsystems
Abstract
This paper presents a comprehensive design overview of the SunOS 5.4 kernel
ext4对于我们偷偷修改数据的行为一点也不知情,接下来,我们看看ZFS有什么表现:
# ls -i /poolfortest/testpool/
7 allocator.txt
# zdb -dddddd poolfortest/testpool 7
Dataset poolfortest/testpool [ZPL], ID 40, cr_txg 697, 81.5K, 7 objects, rootbp DVA[0]=<0:48400:200> DVA[1]=<0:122e800:200> 。。。
Indirect blocks:
0 L0 0:32e00:ce00 ce00L/ce00P F=1 B=702/702
segment [0000000000000000, 000000000000ce00) size 51.5K
# 从这里我们可以看出,整个文件放在一个连续的空间内,位于pool中的0号磁盘,0x32e00偏移处,大小为0xce00;其次,从DVA中的<0:48400:200>可以判断Block大小为0x200,即512字节。
# 那么我们可以计算allocator文件的第一个Block的位置。(0x400000 + 0x32e00)/ 512 = 8599
# dd if=forzfsdiskfile of=/tmp/zfsdiskfile_part1 bs=512 count=8599
# dd if=forzfsdiskfile of=/tmp/zfsheadfile bs=512 skip=8599 count=10
# dd if=forzfsdiskfile of=/tmp/zfsdiskfile_part2 bs=512 skip=8609
# vi /tmp/zfsheadfile <— 这里修改文件的内容,跟ext4实验时一样,将”Caching”改成“Cachedd”。
# 以下开始,我们将修改过的三部分文件重新整合到一起
# cp -pRf /tmp/zfsdiskfile_part1 /root/forzfsdiskfile
# dd if=/tmp/zfsheadfile of=/tmp/zfsheadfile_changed bs=5120 count=1
# cat /tmp/zfsheadfile_changed >> /root/forzfsdiskfile
# cat /tmp/zfsdiskfile_part2 >> /root/forzfsdiskfile
# zpool import -d /root/ poolfortest
# zpool status poolfortest
pool: poolfortest
state: ONLINE
scan: none requested
config:
NAME STATE READ WRITE CKSUM
poolfortest ONLINE 0 0 0 <— 这里貌似一切OK
/root/forzfsdiskfile ONLINE 0 0 0
errors: No known data errors
# 我们来看看文件
# ls /poolfortest/testpool
allocator.txt <— 文件也好好地存在着;然后我们访问一下看看
# head /poolfortest/testpool/allocator.txt
head: error reading `/poolfortest/testpool/allocator.txt': Input/output error <— 终于出错了,文件访问出错,这时再来看一下Pool的状态
# zpool status -v
pool: poolfortest
state: ONLINE
status: One or more devices has experienced an error resulting in data
corruption. Applications may be affected.
action: Restore the file in question if possible. Otherwise restore the
entire pool from backup.
see:
scan: none requested
config:
NAME STATE READ WRITE CKSUM
poolfortest ONLINE 0 0 2 <— Checksum显示有问题了。
/root/forzfsdiskfile ONLINE 0 0 4
errors: Permanent errors have been detected in the following files: <— 同时,也给出了发生问题的文件
/poolfortest/testpool/allocator.txt
操作过程中,我们有一个计算Block的公式:(0x400000 + 0x32e00)/ 512 = 8599,这里的0x400000,表示了,磁盘的最前面4M空间(保留空间+Vdev Label),如果有疑问的话,可以看一下我前面两篇博客: ZFS - Ondiskformat 第一章 虚拟设备(vdevs),Vdev Label以及Boot BlockZFS的校验算法,这里面说明了前面4M的详细分配情况, ZFS - Ondiskformat 第二章 块指针、间接块,这里面有说明如何计算偏移地址(2.1节)。
OK,到这里,我们通过ext4与ZFS两种文件系统在应对静默数据损坏时的效果,可以看出ZFS的完美地发现了我们修改数据的事情。需要说明的是,我们实验的时候使用的是条带化的磁盘,如果我们创建pool的时候使用mirror的话,ZFS将会自动从有正确数据的磁盘上帮我们修复错误数据,这个过程无需我们手动干预。
其实ZFS在保证数据完整性方面不仅仅体现在这一个方面。下面我们在给出一些介绍:
元数据在文件系统中是十分重要的,一个元数据块的损坏可能导致多块数据无法访问。因此,ZFS采用多份元数据的方式来保护它们。在 ZFS - Ondiskformat 第二章 块指针、间接块这篇博客中我们可以知道,ZFS的块指针中有三个DVA(数据虚拟地址),对于普通的数据块,ZFS只使用一个DVA,也就是说数据只存储一份,对于普通的元数据,ZFS使用两个DVA,这样的元数据将会在磁盘中有相同的两个备份,对于更重要的元数据,比如文件系统的根,则使用三个DVA,这样的数据将被存储三份。
ZFS是一个整合了文件系统与卷管理系统的新型文件系统,所以ZFS也提供了Mirror和RAID功能。这里需要的是,如果同时使用了硬件的RAID,则可能会与ZFS的RAID相冲突,这将导致ZFS将只会检测数据的完整性,而并不会自动修复错误的数据。
如果我们只有一块磁盘,而且我们创建pool时也没有做RAID或是mirror,我们也可以让ZFS为我们的每个数据存储多份来保护我们的数据。这个只要设置文件系统的copies属性就好了,但是如果设置了copies属性为2,那么磁盘空间也会相应地减半(因为我们每个数据都存储了两次),依次类推。
点击(此处)折叠或打开
通过这两个变量的定义,在ZIO流水线中,我们使用一个整数来代表Checksum算法,通过这个整数在上面的zio_checksum_table中查找,直接就可以调用该checksum的计算算法,对数据计算对应的checksum值。
对于ZFS的Checksum设计,源码中还给出了以下的几个原因:
1) 不同类型的数据需要不同强度的Checksum值,比如SPA的元数据就需要高强度的Checksum算法,而对于用户数据,则可以由用户来在速度和强度之间进行权衡选择。
2) 加密用的算法是在不断的发展的,说不定未来的某一天会出现一种更强但是计算速度同样更快的算法,通过这种方式就很容易将该算法加入到代码中。
3) 如果有人设计了速度更快的硬件,我们也可以迅速利用那样的硬件。
ZFS是一种新型的文件系统,它在保证数据完整性方面做足了功夫以保护用户的数据。很多的设计都具有颠覆性的意义,比如将RAID,Mirror整合到文件系统中等等。这一切让我们更加放心地使用ZFS。