元数据主要包括3类,第一类是文件系统自身的元数据,例如Block大小、UUID、OSDUUID、版本号等,这部分数据通常保存在超级块(Superblock)中,BlueFS也是这么做的。第二类是目录文件的inode数据以及文件目录间的层次关系,这部分内容通常以一定的结构持久化在磁盘,读取文件时根据路径(例如,/etc/ceph/ceph.conf)从根目录开始一层一层查找到目标文件的inode数据在磁盘中的位置。BlueFS并没有这么做,而是将对文件的所有操作都记录到日志文件,挂载文件系统时Replay日志,以此来得到这部分元数据。也就是说,所有的元数据都会加载到内存。第三类是用于描述磁盘哪些地方空闲哪些地方被占用的磁盘元数据。BlueFS的文件元数据包含该文件对应的磁盘空间,通过Replay日志获得所有文件的元数据后,就获得了整块磁盘的使用情况。
磁盘规划
如上图所示,BlueFS将磁盘空间划分为3部分:Label部分,大小4K,具体作用暂未看到。Superblock,大小4K,用于保存文件系统自身的元数据,挂载文件系统时先读取这部分内容。最后一部分是数据区域,用于保存普通文件和日志文件的内容。日志文件的元数据保存在超级块内,普通文件的元数据保存在日志文件。
日志文件
BlueFS的一个重要概念是日志文件,所有操作都记录到日志文件,挂载文件系统时先Replay日志,日志文件有两个作用:首先,保证数据和元数据的一致性。普通文件的元数据保存在日志文件,通过Replay日志获取普通文件的元数据。BlueFS只支持对文件的追加操作,不支持覆盖写操作,从而就没有WAL操作的必要了。追加操作先将数据写到磁盘,然后再更新元数据,即将日志文件落盘。如果在追加数据时掉电,元数据没有更新,那么旧的元数据所描述的数据仍旧是正确的,不会出现数据和元数据不一致的问题。其次,通过Replay所有日志来获取文件目录的层级结构。BlueFS没有像Ext2文件系统那样在磁盘中持久化inode数据结构,只是在普通文件被修改时将对应的元数据作为日志项记录到日志文件。另外,文件的元数据保存了,为文件分配的磁盘空间的信息。Allocator根据这部分信息来确定磁盘中哪些空间已分配哪些空间空闲,从而构建一文提到的树形结构。因此,如果追加数据过程中主机掉电,来不及更新元数据,那么新分配的磁盘空间将被自动回收。
如何加载日志文件? 要加载日志文件,要先知道日志文件对应的磁盘空间,这部分信息记录在日志文件的元数据,而日志文件的元数据存储在Superblock超级块。Superblock是一个地址被硬编码的磁盘空间,起始位置为4K,长度为4k,挂载文件系统时首先加载这块固定区域的内容。从这块区域中读取磁盘Block大小、版本号、UUID、OSDUUID以及日志文件的元数据。日志文件的元数据内部包括:
- 存储日志文件内容的磁盘,BlueFS同时支持3块不同的磁盘
- 日志文件内容所在磁盘空间的位置信息
- 文件修改时间、大小以及inode索引号,每个文件的索引号是唯一的,便于在内存中管理文件结构
根据日志元数据的前两项内容,就能够知道从哪块磁盘的什么位置读取日志文件了,然后调用Libaio接口去读取文件即可。
Compact日志。 Inode索引号0和1,由BlueFS内部使用,并且都用于日志文件。正常情况下,日志文件只使用索引号1,只有在Compact日志时才使用临时的索引号0。日志文件只支持Append操作,时间长了,日志文件会占用较大的磁盘空间,并且Replay日志时间也会变久。实际上,日志文件中有用的数据只有,目录文件的元数据以及目录和文件的对应关系。Compact日志的目是,提取这部分有用数据写入到新磁盘空间,并丢掉原来的日志数据,减少日志文件大小,释放磁盘空间。
Compact日志时先使用索引号0的新日志文件将有用数据写入到磁盘的新位置,然后用新日志文件的元数据更新超级块(除索引号外),将超级块Flush到磁盘。从目前实现来看,修改超级块是个危险系数很高的操作,一方面是因为超级块的重要程度,如果超级块数据出错所有文件将都无法使用,另一方面似乎没有对超级块做数据保护,只提供CRC检验。不过,正常情况下极少修改超级块的数据,写磁盘时也会绕过本地缓存。