首页 > 代码库 > LFS(the Log-structured File System)系统详解

LFS(the Log-structured File System)系统详解


There was a large and growing gap between random I/O performance and sequential I/O performance:
Existing file systems perform poorly on many common workloads:

LFS文件系统的设计木主要是为了解决上面这以前文件系统所存在的两个问题:随机输入输出的性能和序列输入输出的性能相差很大;还有就是磁盘搜索和旋转延迟比较大。

LFS, short for the Log-structured File System. When writing to disk, LFS first buffers all updates (including metadata!) in an inmemory segment; when the segment is full, it is written to disk in one long, sequential transfer to an unused part of the disk. LFS never overwrites existing data, but ratheralwayswrites segments to free locations.


LFS文件系统的主要算法就是首先把所有的更新(包括元数据)缓存在内存中的成为segment的单位中。当segment填满之后,里面的数据就写入到磁盘中未使用的地方。 特别要注意的是:LFS并不会覆写已有的数据,而是把segment中的数据写入到磁盘中新的位置。(这样在磁盘中就会有old data和new data,在后面会详细讲到)

技术分享

This basic idea, of simply writing all updates (such as data blocks, inodes, etc.) to the disk sequentially, sits at the heart of LFS. If you understand this, you get the basic idea. But as with all complicated systems, the devil is in the details.
技术分享

怎么把所以对文件系统状态的修改更新转换为一个序列操作呢?了解文件系统以及文件系统底层操作的就会知道,当我们在新建文件或文件夹,以及向文件中添加或删除数据时后,不仅仅是文件数据在磁盘的写入,与文件相关的i-node也要写入磁盘,i-node和文件所在磁盘位置相差可能很远。

仅仅只保证数据序列化写入还不够,比如在T时刻在地址A写入一个数据,准备在T+δ时刻往地址A+1写入另一个数据,但是在时间T和δ之间磁盘旋转了,假设磁盘旋转了Trotation时间,那么第二次写入实际上要等待Trotation?δ时间才能真正实施。所以为了保证高性能的写入,除了序列化写入外,还要保证连续性写入

而LFS就通过把数据更新缓存到内存中,然后一起写入到磁盘中,将文件数据和i-node放在磁盘相邻的位置,减少磁盘seek time.

LFS在某一时刻写入的更新单元称为segment.当要往磁盘写入时,LFS把所有的更新缓存到内存中的segment,然后在某一时刻把segment的所有数据一次写入磁盘。只要segment设置的足够大,就能够取得很好的性能。

那么要取多大的缓存呢?可以参看下面原文中的介绍,非常简单。

技术分享

技术分享


下面我们来关注第一个重要的问题,在LFS中如何找到i-node?

技术分享

为什么要关心这个问题呢,因为在传统的文件系统比如UNIX系统中,i-node存在在一个数据中,保存在磁盘中固定的位置。因此只要给定inode的序号以及i-node所在磁盘的地址,就可以通过i-node序号乘以i-node结构的长度,然后再加上磁盘地址就可以得到所需i-node的精确地址。但是在LFS系统中,i-node是分布在磁盘中的,并且由于在LFS系统中并不覆写数据,所以一个文件的最新版本的i-node在磁盘中的位置是不断变化的。

inode map (imap). The imap is a structure that takes an inode number as input and produces the disk address of the most recent version of the inode. Thus, you can imagine it would often be implemented as a simple array, with 4 bytes (a disk pointer) per entry. Any time an inode is written to disk, the imap is updated with its new location.
LFS通过引入imap来解决i-node查找问题。imap是这样的一个结构,输入一个inode number,生成最新版本的inode所在磁盘地址。基于此,imap可以实现为一个数组,每一项是4字节作为磁盘指针,当一个inode写入磁盘后,imap就更新相应的inode地址。


技术分享
LFS需要imap能够持久的保存,这样在系统崩溃的时候就能够找到inode的位置。

如果把imap保存在磁盘固定的位置上的话,每次更新的时候都要访问这个磁盘,就会增加磁盘读写时间,降低了系统性能,所以在LFS中,把更新相关的imap存放在更新数据所写入磁盘位置的右边。

那么现在问题来了,既然imap分散保存在磁盘的各个位置上,那么使用时该如何找到这些imap呢?系统中必须在一个固定磁盘的位置存储这些imap的信息。在LFS中这个固定保存Imap地址信息的块成为CR.


技术分享

在CR中保存指向最新版本的imap的指针,系统就可以通过读取CR查找获取想要的imap信息。CR中的信息周期性的进行更新,可能是30秒一次,所以对系统的性能影响不大。

所以LFS最终在磁盘上的整体结构包含一个CR,imap(将inode number映射为Inode的地址),以及指向文件的inode.


LFS系统如何保存目录数据呢?

技术分享



和UNIX系统类似,一个目录就是(name,inode number)的结构体的集合。举例来说,当在磁盘上创建一个文件,LFS必须写入一个新的inode,新的数据,以及指向这个文件的目录数据和目录inode(目录数据和Inode是被更新了,不过在LFS不会覆写数据,所以这些数据都要在磁盘新的位置中写入)。

LFS还解决了另外一个问题 递归更新问题。

技术分享

举例来说,当一个文件的inode被更新后,它在磁盘上的位置改变了,如果我们不仔细处理这种情况的话,指向这个文件的目录的也会更新,这个目录的目录也会更新,沿着文件系统树向上更新。LFS很漂亮的避免了这个问题。即使一个inode的磁盘位置改变,这个改变不会反映到他的目录中,因为更新的只是inode的imap,目录仍然保存一样的(name,inode number)映射。通过非直接的方法,LFS避免了递归更新问题。

新的问题来了,既然LFS不覆写数据,只是在新的磁盘位置写入数据,磁盘总有写满的时候,同时在磁盘中有大量的老版本的数据,LFS怎么解决这个问题?

技术分享

如原文中所示,假设有个Inode number为k的inode指向磁盘块D0,我们现在更新了文件数据,那么在磁盘中生成一个新的inode和数据磁盘块。结构磁盘中就会出现这样一种情况,inode,数据磁盘块有两个版本,一个老版本,一个新版本。

技术分享

如原文中另外一个事例,如果在一个文件中添加数据,在磁盘中为原始文件增加了一个新的磁盘块用于保存添加的数据,我们可以看到,磁盘中增加了一个新的inode,但是原有的数据块除了被新的inode指向外,还依然被老版本的inode指向。

对于这些老版本的inode,数据块等应该怎么处理?一个方法是保存这些老版本的数据然后允许用户恢复到老版本(比如当如用误删除或覆写了一个文件,用这种方法就会非常方便的恢复)。这样的文件系统称为versioning file system.


在LFS中只保存最新的版本和之前的老版本,LFS周期性的扫描磁盘检测老的版本数据然后清除掉,为后续数据的腾出磁盘空间。

LFS怎么清楚这些老版本数据呢?如果仅仅只是检查时把老版本的inode ,data,imap等占用空间释放掉,那么在磁盘中就会产生许多的hole,磁盘的写入性能会收到极大的影响,因为到后来可能找不到合适大小的磁盘空间来放置新的数据,虽然有许多的空磁盘,但是这些空间都太小。

技术分享

LFS最终还是基于segment进行清理操作。基本的清理过程如下:LFS首先读入一些老版本的segment,然后判断在segment中那些磁盘块中的数据是Live的,在把这些live的数据写入到一个新的segment集合中,释放原有segment占用的磁盘空间。我们期望能够读取M个存在的segment,把他们的live内容压缩到N个新的segment中,然后释放掉老的M个segment占用的磁盘空间,用于后续的写入。

接下来新的问题来了,如何判定segment中哪些磁盘是live的?

Specifically, LFS includes, for each data blockD, its inode number (which file it belongs to) and its offset (which block of the file this is). This information is recorded in a structure at the head of the segment known as thesegment summary block.
在LFS中为每一个磁盘块D记录了新的信息,包含inode number(这个磁盘块属于哪个文件)和offset(属于文件第几个磁盘块).这个信息保存在segment的的头部的一个结构中,称为segment summary block。

Given this information, it is straightforward to determine whether a block is live or dead. For a blockDlocated on disk at addressA, look in the segment summary block and find its inode numberNand offset T. Next, look in the imap to find whereNlives and readNfrom disk (perhaps it is already in memory, which is even better). Finally, using the offsetT, look in the inode (or some indirect block) to see where the inode thinks the Tth block of this file is on disk. If it points exactly to disk addressA, LFS can conclude that the blockDis live. If it points anywhere else, LFS can conclude thatDis not in use (i.e., it is dead) and thus know that this version is no longer needed.

有了这个信息,就可以很直接的判定一个磁盘块是live 或 dead。假设有一个磁盘块D位于地址A,通过查看segment summary block可以找到这个磁盘块属于的inode number N和offset T.接下来,通过查看imap可以找到N对应的inode的地址,根据inode信息找到文件所占用的磁盘块,查看这个文件的第T个磁盘块所在地址,比较通过inode查找得到的地址和segment summary block中记录的地址,如果两个不一样,那么该磁盘块D就是old 版本,不再被使用。

There are some shortcuts LFS takes to make the process of determining liveness more efficient. For example, when a file is truncated or deleted, LFS increases itsversion numberand records the new version number in the imap. By also recording the version number in the on-disk segment, LFS can short circuit the longer check described above simply by comparing the on-disk version number with a version number in the imap, thus avoiding extra reads.

除此之外,还有一些更简单的判断磁盘live的方法。比如,当一个文件增加数据或者被删除了,LFS增加这个文件的版本号,然后记录在Imap中,同时也记录在磁盘的segment中。LFS可以通过比较磁盘块的version number和imap中的version number来判定这个磁盘块是否live.


LFS如何处理数据写入时系统崩溃的情况。

Let’s cover the second case first. To ensure that the CR update happens atomically, LFS actually keeps two CRs, one at either end of the disk, and writes to them alternately. LFS also implements a careful protocol when updating the CR with the latest pointers to the inode map and other information; specifically, it first writes out a header (with timestamp), then the body of the CR, and then finally one last block (also with a timestamp). If the system crashes during a CR update, LFS can detect this by seeing an inconsistent pair of timestamps. LFS will always choose to use the most recent CR that has consistent timestamps, and thus consistent update of the CR is achieved.

为了确保CR数据写入的原子性,LFS其实保存了两个CR,保存在磁盘的两端,CR更新到磁盘中时是随机挑选一个CR写入。LFS同样实现了一个非常小心的协议来更新CR。首先写入一个header(附带写入一个timestamp),然后写入CR的主要内容,然后写入最后一个磁盘块数据以及一个timestamp。如果在CR更新时系统奔溃了,LFS通过观察到一对不一致的timestamp检测到出现了系统崩溃,LFS就会采用含有一致性timestam的离崩溃时间最接近CR。



原文CSDN下载地址







LFS(the Log-structured File System)系统详解