换到博客园排版有问题,原版在这里:http://xubenbenhit.github.io/LogStructureFileSystem.html
Log-structured File Systems
2014-12-26 #system
先来扯淡,这篇博客讲的是LFS,之前写的硬盘与磁盘冗余阵列其实是第一篇,而这个应该算是第三篇,这中间差了一篇介绍文件系统的博客,原本打算今天回去之后一起写了,但是考虑到自己回家之后的效率不敢保证。故而先在实验室写完这个文章,而文件系统的资料全部在家。
1. 什么是LFS?
首先回顾一下我们是怎么来写Log文件的,不就是把最新的状态一行一行的写在日志文件的最末尾么(这个联系是我自己YY的)。那么Log-structured File System也是一样,就是更新数据的时候直接将数据放在末尾。如下图:
一开始,我们写入磁盘数据块Foo,然后旁边的A是它的inode:
然后,我们再写入块Bar,旁边的A‘是inode:
最后,我们更新数据块Foo,获得Foo‘,这时候我们将其写回磁盘,就变成这样子:
2. LFS是怎么来的?
LFS是在上个世纪九十年代前后搞出来的,当时的主要考虑有这个几点(道听途说):
?
- 内存越来越大,进而可以不断扩充Cache的容量,也就会意味着数据读取操作可以越来越不需要访问磁盘了,进而使得磁盘写操作越来也成为性能瓶颈;
- 序列磁盘数据读取速度跟随机读取磁盘数据速度的差距越来越大,这种情况下如果可以将随机磁盘操作转换为序列磁盘操作,则收益可观;
- 处理小文件的时候,之前的文件系统效率不高,而事实上小文件才是常见的;此外对于小文件的写操作,磁盘本身也不擅长,因为RAID4/RAID5处理小文件的时候性能也是不高的;
?
因为有了这些,所以就有了LFS。LFS首先将磁盘数据的更新缓存在Cache中,当缓存到一定规模(称之为一个segment)的时候再一次性写入空闲磁盘,由此获得高性能。 当然了,说起来很简单,做起来就不是喽,这里面主要有两个方面的问题:其一是整个操作过程中的文件定位;其二是磁盘空间管理。
这里我觉得就不按照论文的思路来写了,因为没劲,不如就按照自己的理解一步步来做推演吧,比较论文作者也是自己一步步想出来的。
3. 开始推演
LFS是先在内存在存储文件更新,这个存储单元称之为一个segment,等到segment存满了的时候再一次性写入空闲磁盘。于是乎就需要首先保证整个操作的正确和高效。
- 首先,根据FFS(Fast FIle System)的设计经验,需要用一个inode来存储这个segment中每个文件的元信息,也就是文件的一些属性,诸如大小、文件块(文件可能需要多个磁盘block来存储)的物理地址、权限等等。当然了,inode也是在segment中的,为了方便考虑就存储在对应文件的后面,见第一部分的图。注意一点,这个inode一般很小,只有128Byte左右。
- 按照FFS的设计,将inode存储在一块连续的磁盘空间(物理地址是可以事先计算的),但是一次性将segment写入磁盘这个操作本身就会使得文件的inode“散布在”整个磁盘空间,而且按照LFS的设计,文件是不可能被原地修改的,这也就意味着最新版的文件的inode的地址始终在变化!!故而按照“惯例”,设计一层间接连接(indirection),称为imap,用来存储各个inode的物理地址。
- 既然有了imap,那么问题来了,imap存储在哪儿?第一种选择是将其存储在磁盘的某一固定位置,但是我们注意到每次写segment的时候都会对imap进行更新,而每次更新imap都需要将磁头转到固定位置,有点影响效率啊!不如将imap放在inode后面吧。
这里多说一句,imap本身可能也占用多个磁盘block,故而只需要将更新的那一块imap放在inode后面就可以了。这样又有个问题,imap地址不确定,怎么寻址呢?所以还是需要一个地址确定的区域,里面存储imap的地址就可以了,这个区域称之为CheckRegion,这里面存储了imap各个block的物理地址。就可以通过CheckRegion->imap->inode->file的方式定位到文件了。 - 但是,文件夹怎么处理?
按照FFS的设计,所谓的文件夹无非就是一些文件名和文件inode的映射,在FFS中文件夹被当做普通的文件数据来存储,那么LFS也这么干。于是我们就可以看到 /dir/foo 是这样存储的:
具体而言是这样,首先定位到dir文件夹的inode所在的imap,然后根据查找到dir文件夹的inode物理地址,然后找到dir文件夹的物理地址,进而获取foo文件的inode,然后在CR->imap->inode->foo。注意到这样设计还有一个意外的好处,就是说文件夹下的文件更新操作是不需要更新文件夹本身的,这也就避免了“递归更新”文件夹的文件夹的文件夹。。。好的,至此,文件定位以及没有问题了。但是每次这样将segment写入空闲磁盘,一直这样下去,哪有那么多磁盘呢?这就涉及到磁盘空间回收(segment cleaning)的问题了。
一般来说,磁盘空间回收大致需要这三步:首先将一些segment读入内存;然后判断segment里面的block数据是否是“存活的”,也就是说文件是否处于最新版本;最后将所有的“存活”文件集中写进segment中,而将剩下的空间回收。 - 如何判断block是否为最新版本?
为了实现这个功能,那么就需要知道segment中block的原信息了,比如它所归属的文件inode以及在文件中的位置。这些信息需要存储在segment中的一个称为segment summary block的区域中。那么我们就可以查找这个block数据的当前物理地址,比对一下就知道是否是最新版了。当然了,这样一来,最好将imap整个加载到内存中方便查询。
当然了,可以改进这个设定,为每个文件设置版本号,每次文件更新版本号递增,将这个版本号存储在segment summary block以及imap中,这样只需要比对一下文件版本号就可以判断整个文件是否是最新版本了。 - 关于segment clean,什么时候执行呢?执行clean达到什么目标就停止呢?怎么选择segment执行clean?以及是否需要重组live file以便获得更好的locality? 第一个第二个实验表明不太重要,实际操作而言,设置两个阈值,点那个空闲segment数目低于某一阈值后执行clean,当回收使得空闲segment数目高于另一阈值后停止clean。
第三个问题很有讲究,事实上,论文中采用的算法是计算每个segment的clean性价比(cost-benefit),这个计算公式具体参考论文吧。为此在CheckRegine中保存一个segment usuage的表单。
第四个问题,也是很有技巧性的地方,论文中的做法是按照存活文件最近一次更改时间的顺序依次写入segment中。至此,描述的差不多了,下面看看两个细节。
- Check Point Check Region中包含的数据imap地址以及segment usage等等都是在变化的,所以需要每过一段时间更新一下。正常情况下先将Cache中的segment写入磁盘,然后更新Check Region,因为考虑到更新CR过程中也可能出现异常,所以用两个timestamp块包裹住真实的CR数据,只有当前后两个timestamp一致的时候才认为这个CR是正常的。为了避免在更新CR过程中出现故障导致CR不可用,LFS使用两个CR,交替使用,这样即使故障了,也可以用另一个CR来恢复。
- Crash Recovery 遇到特殊故障,LFS怎么恢复呢? 首先,寻找到最新的可用CR,这里的“可用”表示该CR的前后timestamp是一致的。然后使用论文中提及的Roll-Followed算法来恢复。这里面有一个“奇怪”的地方,就是说imap的最新版block不都已经在segment里面了么,那么直接用这个更新就可以了嘛!!其实不是的,因为考虑到磁盘写操作的时候imap本身可能没有写正确。。。。
至此,应该是差不多了,详细的东系统的介绍直接看下面的资料吧。
4. 参考文献
?
- http://pages.cs.wisc.edu/~remzi/OSTEP/file-lfs.pdf
- http://www.eecs.berkeley.edu/Pubs/TechRpts/1992/CSD-92-696.pdf
- http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.54.502
- http://blog.notdot.net/2009/12/Damn-Cool-Algorithms-Log-structured-storage
?