Total Pageviews

Friday, 20 October 2017

文件系统的稳定性探讨

一,引入:为什么说保证无损坏或数据丢失的写文件非常难?
一.1)举个栗子,假定有一个包含有字符串foo的文件txt,现在要将其更新为bar,那么可以采用pwrite函数:
1
pwrite([file], "bar", 3, 0)  // write 3 bytes at offset 0
在一切顺利的情况下,文件txt的内容将变为bar,但是如果在write的过程中发生了崩溃,那么文件txt的内容可能是boo或bao等异常数据。
当然,这只是个例子,在实际中,磁盘数据的写入是按块来的,但原理一致。
如何改进?朴素的想法是做原子写入,即要么全写,要么全不写,也就是使得文件txt的内容要么是foo,要么是bar,而不要是任何异常数据。如何实现这种想法?采用日志方式:
1
2
3
4
creat(/dir/log);
write(/dir/log, "0,3,foo", 7);
pwrite(/dir/orig, "bar", 3, 0);
unlink(/dir/log);
即:
a.备份旧文件到日志文件
b.修改文件
c.删除日志文件
d.发生奔溃时,用日志文件进行恢复
通过上面方式,一旦在数据写入过程中发生奔溃,那么可以通过日志文件进行恢复。ext3/ext4等日志文件系统采用的原理与此类似。
上面存在一个值得注意的问题,那就是步骤不能打乱,否则日志功能将失去意义,比如:
1
2
3
4
creat(/dir/log);
pwrite(/dir/orig, "bar", 3, 0);         -----reordered
write(/dir/log, "0,3,foo", 7);          -----reordered
unlink(/dir/log);
一.2)关于ext3/ext4等日志文件系统的基本认知
我们要认清,文件系统里至少有两种数据,一种是filedata,也就是文件的实际内容,比如上一节里文件txt的内容foo或bar。
还有一种为metadata,也就是文件的元数据,比如文件的文件名,文件大小,文件类型,创建时间,User ID,Group ID,文件权限,占用的磁盘块号等等。
而在日志文件系统里,就有了第三种数据journal data日志数据,用于记录对文件系统所进行的添加、修改、删除等操作。当然,在具体的实现时,从稳定性和性能两个方面的综合考虑,日志可能只记录部分操作,比如仅记录对metadata的修改。
很明显,根据日志数据的目的,日志数据只需记录最近一段时间的操作即可,因此其所在的磁盘空间一般并不会无限增大,每隔一段时间就可以删除之前的日志重新开始,不过这可能依赖于具体的实现。
正是因为有了filedata和metadata,所以看似对文件系统一个非常简单的操作都不是一步完成的,例如删除一个文件就涉及到三步:
1
2
3
1) Removing its directory entry.
2) Releasing the inode to the pool of free inodes.
3) Returning all used disk blocks to the pool of free disk blocks.
在其中任何一步中断都会导致文件系统出现不一致。
因此ext3的做法是在进行上面第1)步之前就先将需要进行的操作作为journal data日志数据而记录下来,记录ok后再添加一条commit record日志数据,只有完成了这两个步骤,系统才开始执行真正的文件操作。即具体的操作流程是这样:
1
2
3
1,The log blocks are written to the journal.
2,The commit record is written.
3,Metadata/Filedata writes begin at some later point. ---这里就是进行实际的文件操作
因此,即便是第3步中的实际文件操作被中断(比如断电关键),那么后续(比如重启后fsck检测恢复)仍然可以利用日志记录将操作继续完成。当然,也有可能前面的两步日志操作都没记录完就中断了,那么此时实际文件操作还完全没有开始,因此文件系统仍然是完整的。
二,预防:怎样尽量保证ext3/ext4文件系统的稳定性?
任何事情总是受到多种不同因素的影响,不同的侧重会有不同结果。对于文件系统而言,有的场景追求稳定,有的场景追求性能,而操作系统给的默认配置是两者之间的平衡。如果要偏重于稳定,那么不妨修改一下默认配置。
可以适应大多数场景的稳定选项组合为(注:对于SSD磁盘,下列选项可能会导致其寿命缩短):
1
auto,exec,relatime,noatime,sync,nodiratime,dirsync,barrier=1,commit=1,data=ordered,data_err=abort,errors=remount-ro,
这一系列的选项可以大大提高文件系统稳定性,下面对几个关键选项进行逐一介绍。
二.1)选项data,用于指定日志记录的内容和方式,可选的参数有三个:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
data=journal        All data are committed into the journal prior to being
            written into the main file system.  Enabling
            this mode will disable delayed allocation and
            O_DIRECT support.
        对文件数据filedata和元数据metadata的操作都被日志记录,性能最慢,但最高稳定性。
             
data=ordered    (*) All data are forced directly out to the main file
            system prior to its metadata being committed to the
            journal.
        仅记录元数据metadata的操作日志,但会确保在修改元数据metadata之前将文件数据filedata更新到磁盘。该值为默认配置。
 
data=writeback      Data ordering is not preserved, data may be written
            into the main file system after its metadata has been
            committed to the journal.
        仅记录元数据metadata的操作日志,但不会保证元数据metadata和文件数据filedata的更新顺序。具有最佳性能。
上面的英文来之参考1,而中文来之参考2,两者有一定误差,暂时以中文为准。
稳定性最好的日志模式为journal,但其性能也最差。另外,我们还可以使用命令chattr单独对某一个文件设置journal模式:
1
chattr +j testfile
通过查看chattr的man手册,+j的作用如下:
1
2
3
4
5
A  file  with  the  ‘j’ attribute has all of its data written to the ext3 journal before being
written to the  file  itself,  if  the  filesystem  is  mounted  with  the  "data=ordered"  or
"data=writeback"  options.   When the filesystem is mounted with the "data=journal" option all
file data is already journalled and this attribute has no effect.  Only  the  superuser  or  a
process possessing the CAP_SYS_RESOURCE capability can set or clear this attribute.
采用-R选项进行递归设置
1
-R     Recursively change attributes of directories and their contents.
牛刀小试:
1
2
3
4
5
6
[root@localhost ~]# touch testfile
[root@localhost ~]# lsattr testfile
-------------e- testfile
[root@localhost ~]# chattr +j testfile
[root@localhost ~]# lsattr testfile
---------j---e- testfile
设置data=journal有一个值得注意的影响,其会导致延迟分配和O_DIRECT支持失效,即在open(2)时带O_DIRECT的话,open(2)可能会返回EINVAL,或者在实际读写时候并非direct I/O,而fall back到了buffer I/O去了,这依赖于具体的内核版本实现,需要查看对应的内核代码。
二.2)选项errors,用于指定当出现错误时如何进行处理,可选的参数有三个:
1)continue: 继续正常运行
2)remount-ro: 重新以只读方式挂载,系统可以继续运行(会受到只读方式挂载的影响,比如某些需要进行写操作的应用功能可能不正常),在一定程度上缓解文件系统被继续损害
3)panic: 系统立即当机,阻止损害加剧
不论哪种方式,只要文件系统有问题发生,重启Linux系统后都自动执行fsck进行完整性检查。
有三种方式改变这个选项:
1)在/etc/fstab里指定错误处理方式:
1
/dev/sda7  /home     ext3   errors=remount-ro  0    1
2)挂载文件系统时通过选项-o errors=error-behavior指定:
1
mount -t ext3 -o errors=remount-ro /dev/sdb5 /mnt
3)如果不想在每次挂载或fstab里指定,那么可以使用tune2fs命令修改文件系统的super block来指定缺省方案。既然是缺省方案,就意味着可以用前面两种方案覆盖这个缺省值。
1
tune2fs -e remount-ro /dev/sda8
将errors指定为panic可以有效阻止文件系统损害加剧,系统重启后执行fsck能够较大概率修复文件系统,保证文件系统稳定性。
但值得注意的是,如果系统panic后没有类似硬狗这样的机制把系统自动重置,那么可能会导致业务长时间完全中断,所以一般可以退而其次采用remount-ro配置。
二.3)选项data_err,这是在2.6.28里给ext3新增的选项,用于指定在data=ordered模式下,当在文件数据(filedata,相对于metadata而言)缓存中发生错误时,是否终止日志。可选的参数有两个:
1)ignore: 为默认值,仅用printk打印一条内核日志
2)abort: 终止日志
如果在读写文件数据块发生IO错误时不终止日志,那么会导致文件数据损坏扩散。因为大部分应用程序都是基于缓存的写数据,在没有调用fsync()进行同步的情况下,这些应用程序没法及时的意识到IO错误,导致错误蔓延。
注:根据patch中的注释来看,该选项仅支持ordered mode。
二.4)barrier选项,取值1或0,具体作用接着看。
在前面,我们介绍到ext3里在进行文件操作时,具体的流程是这样:
1
2
3
1,The log blocks are written to the journal.
2,The commit record is written.
3,Metadata/Filedata writes begin at some later point. ---这里就是进行实际的文件操作
这里会有一个潜在问题,因为磁盘按块读写并且有缓存,那就有可能会出现第2步实际先于第1步完成,也就是commit record实际先写到磁盘,然后才是操作日志实际写到磁盘。这里就有个空隙,如果在commit record实际写到磁盘后,系统掉电了,就会出现操作日志还没有实际写到磁盘的情况。问题就来了,fsck将没法找到对应的操作日志进行修复。
因此,barrier选项就是做这个作用,它在上面步骤中再插入两步:
1
2
3
4
5
1,The log blocks are written to the journal.
2,A barrier operation is performed.   ---确保日志数据被实际写到磁盘
3,The commit record is written.
4,Another barrier is executed.      ---确保commit record被实际写到磁盘
5,Metadata/Filedata writes begin at some later point. ---这里就是进行实际的文件操作
这样就最大可能的确保文件系统一致性。可以通过cat /proc/mounts查看,该值默认貌似为1。
注:
1,在LVM机制上,barrier无效,待详细确定。
2,ext4文件系统对日志有进行checksum,所以上面第2步的barrier不是必须。
二.5)commit选项,指定数据的同步时间。
1
2
3
4
5
6
7
8
9
10
11
commit=nrsec    (*) Ext4 can be told to sync all its data and metadata
            every 'nrsec' seconds. The default value is 5 seconds.
            This means that if you lose your power, you will lose
            as much as the latest 5 seconds of work (your
            filesystem will not be damaged though, thanks to the
            journaling).  This default value (or any low value)
            will hurt performance, but it's good for data-safety.
            Setting it to 0 will have the same effect as leaving
            it at the default (5 seconds).
            Setting it to very large values will improve
            performance.
默认值为5秒,修改为1有助于提高数据安全,但很明显会增加I/O次数,理论上会对性能产生一定影响。
二.6)其他选项看man mount手册。
1
2
3
4
5
6
7
8
9
10
11
12
relatime
    Update inode access times relative to modify or change time. Access time is only updated if the previous access time was earlier than the current modify or change time. (Similar to noatime, but doesn't break mutt or other applications that need to know if a file has been read since the last time it was modified.)
     
noatime
    Do not update inode access times on this filesystem (e.g, for faster access on the news spool to speed up news servers).   
sync
    All I/O to the filesystem should be done synchronously. In case of media with limited number of write cycles (e.g. some flash drives) "sync" may cause life-cycle shortening.
     
nodiratime
    Do not update directory inode access times on this filesystem.
dirsync
    All directory updates within the filesystem should be done synchronously. This affects the following system calls: creat, link, unlink, symlink, mkdir, rmdir, mknod and rename.
三,试验和源码分析
给虚拟机新加一块磁盘:
1
fdisk -l /dev/sdb
分区:
1
fdisk /dev/sdb
在fdisk里输入n,p,回车,回车,w,完成分区创建。
格式化:
1
mkfs -t ext3 /dev/sdb1
挂载/卸载:
1
2
3
4
mkdir sdb1
mount /dev/sdb1 sdb1
mount -t ext3 -o auto,exec,relatime,noatime,sync,nodiratime,dirsync,barrier=1,commit=1,data=ordered,data_err=abort,errors=remount-ro, /dev/sdb1 sdb1
umount sdb1
改变参数:
1
tune2fs -e remount-ro /dev/sda1
查看信息:
1
2
3
dumpe2fs /dev/sdb1
tune2fs -l /dev/sdb1
cat /proc/mounts
通过strace跟踪到系统调用:
1
mount("/dev/sdb1", "sdb1", "ext3", MS_MGC_VAL, "data=journal") = 0
man手册是这样的:
1
2
3
4
5
6
SYNOPSIS
       #include <sys/mount.h>
 
       int mount(const char *source, const char *target,
                 const char *filesystemtype, unsigned long mountflags,
                 const void *data);
相关内核代码实现在linux-2.6.30.8/fs/ext3/super.c里,mount选项解析函数为parse_options(…)。
四,补救:在出现文件系统问题后,怎样补救?
补救有两方面的含义:
1,确保尽量不损失数据。
2,确保业务继续运行。
如果第1点可以做到,那么业务自然可以继续运行。如果1点做不到,那么就可以尝试采用替换默认数据(比如恢复默认配置,重置为初始数据库等)的方式继续运行业务,这种情况会导致用户数据丢失,而且简单的数据重置在某些场景可能并不合适,但也是一种退而求次的方法。
文件系统出现问题有两种情况:
1,掉电导致文件系统损坏,软件问题。
2,磁盘出现坏道,硬件问题。
软件问题采用fsck等方式修复。
重点是硬件问题,如果坏得比较彻底,那自然是没救了,至少非专业公司不可救。如果只是出现坏块,通常的做法是在底层屏蔽掉这些坏块,让磁盘能够继续努力工作个一年半载。
四.1)badblocks,linux类操作系统中用于扫描检查硬盘和外部设备损坏扇区的命令工具。损坏的扇区或者损坏的区块是指硬盘中因为永久损坏或者是操作系统不能读取的空间。
Badblocks命令可以探测硬盘中所有损坏的扇区或者区块并将结果保存在一个文本文档中,这样,我们就可以使用e2fsck命令来配置操作系统不在这些损坏的扇区中存储数据。
使用示例:
1
badblocks -v /dev/sdb > /tmp/bad-blocks.txt
利用e2fsck/fsck强制操作系统不使用这些损坏区域(需要保证设备没有被挂载):
1
2
e2fsck -l /tmp/bad-blocks.txt  /dev/sdb
fsck.ext3 -l /tmp/hda-badblock-list.final /dev/hda1
直接在格式化做坏道检测:
1
2
3
e2fsck -c /dev/sdb1   --只读测试
e2fsck -cc /dev/sdb1   --无损读写测试,也就是先把数据读出来,再按一定的模式做测试,最后把最先读出来的数据放回去,因此最终效果就是无损害。
mkfs.ext3 -c /dev/hda1
重点:
1,e2fsck/fsck加上-c或-cc参数后,会调用badblocks命令进行坏块检测。
2,badblocks的-b参数的默认值为1024,而实际情况可能是4096,可以通过多种方式查看:
1
2
3
4
5
[root@localhost ~]# blockdev --getbsz /dev/sdb1
4096
[root@localhost ~]# dumpe2fs /dev/sdb1 | grep "Block size"
dumpe2fs 1.41.12 (17-May-2010)
Block size:               4096
正确指定这个值非常重要,这样的输出结果才能给e2fsck/fsck使用。要么就直接使用e2fsck/fsck加上-c或-cc参数。
3,对于已经mount的分区,badblocks可以执行,而e2fsck/fsck执行不了,因此一般情况下,会要在分区卸载后执行相关操作。
4,执行检测的时间可能比较长,请注意是否能够忍受。
四.2)dd_rescue,与dd类似,但是如果dd读出失败会终止,而dd_rescue则将尝试读出并继续执行。因此,dd_rescue主要用于从损坏分区恢复数据。
语法:
1
dd_rescue [options] infile outfile
示例:
1
dd_rescue /dev/sda1 /dev/sda2/backup.img
对于坏道,可以报如下错误,忽略即可:
1
2
dd_rescue: (warning): output file is not seekable!
dd_rescue: (warning): Illegal seek
四.3)smartctl对于Linux物理服务器十分有用,在这些服务器上,可以对智能磁盘进行错误检查,并将与硬件RAID相关的磁盘信息摘录下来。
快速自检:
1
smartctl -t short /dev/sda
查看自检的进度和结果:
1
smartctl -l selftest /dev/sda
查看设备的自检评估结果:
1
smartctl -H /dev/sda
四.4) MHDD是俄罗斯人开发的一个DOS下的免费专业硬盘检测和坏道维修软件,它能检测IDE、SATA和SCSI等硬盘,近几年MHDD几乎成为了专业硬盘检测软件的标准;它对硬盘的操作完全符合ATA/ATAPI规范,可以进行硬盘的检测、S.M.A.R.T操作、坏道检测、解密、清除数据、坏道维修、改变容量等操作。MHDD工作在纯dos环境,内置了大部分的南桥芯片驱动和adaptec SCSI卡驱动,可以在BIOS中将硬盘设为NONE,依靠它自身的驱动对硬盘进行检测,这个功能对检测中病毒(如逻辑锁)的硬盘非常有用,同时它还提供了对PC3000 ISA的支持。
MHDD的makebad命令可以模拟磁盘坏道来做测试,怎么模拟没具体看,因为我需要能用在Linux环境下的工具。
找到一个MHDD的linux版本whdd,试了一下(下载whdd源码后执行build_static.sh进行编译),貌似功能不够。
五,延伸思考
可以做LVM的快照snapshots吗?
LVM可以做底层问题屏蔽吗?

No comments:

Post a Comment