KFS文件系统的MetaServer元数据管理采用的是B+树方式,下面将结合其源码,对KFS MetaServer中元数据的组织形式及有关实现细节进行分析。
KFS MetaServer元数据管理的代码所在目录为kfs-[version]/src/cc/meta,其中,相关的源码文件有:
(1)meta/base.h:KFS元数据metadata中各节点的基础类,包括的类:类Key、类MetaNode,它们分别代表B+树种存储的数据、所有B+树节点的公共基础信息。
(2)meta/meta.h和meta/meta.cc:封装了metadata的基本数据定义,包括:类Meta、类MetaDentry、类MetaFattr和类MetaChunkInfo,它们分别代表文件系统中的目录项、文件或目录的属性项、对于一个文件偏移(file offset)的Chunk信息。
(3)meta/kfstree.h和meta/kfstree.cc:封装了对B+树中内部节点Node的各种操作及Tree的基本操作(与文件系统无关,B+树底层的实现),如插入节点、删除节点等。
(4)meta/kfsops.cc:封装了使用B+树存储KFS文件系统,实现的相关基本操作,如创建文件、删除文件、创建目录、删除目录等(作为Tree的实现)。
(5)meta/request.h和meta/request.cc:封装了对ChunkServer或KfsClient发出的meta data请求的处理,通过Tree metatree执行相应的操作,实现对KFS文件系统各种基本操作的调用。
KFS的文件系统采用的是B+树,那么为什么选用B+树而不是B-树呢?这里做一个简单的分析:
B-树的定义:
B-树是一种平衡多路查找树,一棵m阶的B-树,或者是一颗空树,或者是满足下列特征的m叉树:
(1)树中每个节点至多有m棵字数;
(2)若根节点不是叶子结点,则至少有2棵子树;
(3)除根之外的所有非终端结点,则至少有[m/2]棵子树;
(4)所有的非终端结点中包含下列信息数据:(n, p0, k1, p1, k2, p2, ..., kn, pn),
其中:ki为关键字,且ki<ki+1;pi为指向子树根结点的指针,且满足pi所指子树中所有结点的关键字均大于ki且小于ki+1,pn所指子树中所有结点的关键字均大于kn;
(5)所有叶子结点均在同一层。
B-树的检索:
从根结点开始,对结点内的有序关键字序列进行二分查找,如果命中,则直接结束查找过程;否则,进入查询关键字所属范围的儿子结点进行查找。重复以上过程,直到所对应的儿子指针为空,或已经是叶子结点。
B-树的特性:
(1)关键字集合分布在整颗树中;
(2)任何一个关键字出现且只出现在一个结点中;
(3)搜索有可能在非叶子结点结束;
(4)其搜索性能等价于在关键字全集内做一次二分查找;
(5)自动层次控制。
B+树的定义:
B+树也是一种平衡多路查找树,它是应文件系统所需而出的一种B-树的变型树。一颗B+树满足以下条件:
(1)每个非终端结点至多有m颗子树;
(2)除根结点外,其他每个非终端结点至少有[(m+1)/2]颗子树;
(3)根结点至少有2颗子树;
(4)有n棵子树的结点中含有n个关键字;
(5)所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接;
(6)所有的非终端结点可以看成是索引部分,仅包含其各个子结点中的最大关键字及指向子结点的指针。
通常来说,B+树上有两个头指针,一个指向根结点,一个指向关键字最小的叶子结点。
B+树的检索:
B+树的检索方式分为两种:
(1)一种是从指向关键字最小的叶子结点的头指针开始,进行顺序查找;
(2)一种是从指向根结点的头指针开始,进行随机查找:与B-树基本相同,等价于在关键字全集做一次二分查找,区别是B+树只有达到叶子结点才命中(B-树可以在非叶子结点命中)。
B+树的特性:
(1)所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的;
(2)检索时只有在叶子结点命中,不可能在非叶子结点命中;
(3)非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层;
(4)更适合文件索引系统。
通过对B-树和B+树的定义及特性的了解,对两者进行比较:
(1)占用空间大小方面:
(2)检索路径长短方面:
对于文件系统的设计来说,最关键的瓶颈在于磁盘IO操作,如果占用的磁盘空间少的话,IO操作耗时自然就少。而真正检索内存中的数据结构(如B+树、B-树)的过程中,运算时间相对于磁盘IO操作来说要小的多,即内存的检索时间不是主要的瓶颈之处。
因此,虽然对于同阶的B-树和B+树,B+树的树高和平均检索长度均大于B-树 ,但实际上,检索过程中,最耗时的操作是磁盘IO操作,而B-树占用的空间相对较大,IO操作时劣势明显。由于B+树的非叶子结点无记录信息,只有索引,同样大小磁盘空间就可以存放更多的索引信息,检索访盘次数反而少,速度也就比B-树快。
B+树比B-树更适合实际应用中操作系统的文件索引和数据库索引,原因在于:
(1)磁盘读写代价低:即使B-树的运算时间相对于B+树来说较短,但由于磁盘IO操作方面的劣势,导致其总体上效率不如B+树。
(2)查询效率更稳定:B+树中任何关键字的查找都必须经历从根结点到叶子结点,因此所有关键字查询的路径长度相同,每一个数据的查询效率相当。
在KFS文件系统MetaServer元数据的实现中,有图示几种类型的B+树节点:
(1)MetaNode: 所有叶节点和内部节点的公共基础类,其中记录了不同树节点的类型信息。
(2)Node:表示内部节点,其中记录了树种内部节点的各种操作。
(3)Meta: 表示叶节点,而具体来说,不同的叶节点有:
成员变量:
MetaType type; //节点类型值
int flagbits; //标志位
构造函数:
MetaNode(MetaType t) //初始化type=t, flagbits=0
MetaNode(MetaType t, int f) //初始化type=t, flagbits=f
成员变量:
int count; //孩子节点个数
Key childKey[NKEY]; //孩子的key
MetaNode *childNode[NKEY]; //孩子节点
Node *next; //下一个相邻节点
构造函数:
Node(int f) //初始化MetaNode中的节点类型type=KFS_INTERNAL,flagbits=f
成员变量:
fid_t fid; //文件fid
构造函数:
Meta(MetaType t, fid_t id) //初始化MetaNode中的节点类型信息type=t,及自身的fid=id
成员变量:
fid_t dir; //父目录的fid
string name; //目录项的名称
fid_t fid; //目录项的文件id
构造函数:
MetaDentry(fid_t parent, string fname, fid_t myID)
举例说明:通过Dentry结构实现/root/1.txt的查找过程:
(1) 获取”/”的fid=2
dir=2, name=“/”, fid=2
(2) 获取”root”的fid=8
dir=2, name=“root”, fid=8
dir=2, name=“usr”, fid=6
(3) 获取”1.txt”的fid=12
dir=8, name=”1.txt”, fid=12
dir=8, name=”2.txt”, fid=13
dir=8, name=”3.txt”, fid=14
由以上查找过程可知,/root/1.txt的fid为12。
成员变量:
FileType type; //类型(文件或目录)
int16_t numReplicas; //一个文件要求的备份数
struct timeval mtime; //修改时间
struct timeval ctime; //属性变更时间
struct timeval crtime; //创建时间
longlong chunkcount; //chunk数目
off_t filesize; //文件大小
构造函数:
MetaFattr(FileType t, fid_t id, int16_t n)
MetaFattr(FileType t, fid_t id, struct timeval mt, struct timeval ct, struct timeval crt, longlong c, int16_t n)
成员变量:
chunkOff_t offset; //chunk在文件中的偏移
chunkId_t chunkId; //chunk的标识符id
seq_t chunkVersion; //chunk的版本号
构造函数:
MetaChunkInfo(fid_t file, chunkOff_t off, chunkId_t id, seq_t v)
MetaChunkInfo(fid_t file, chunkOff_t off, chunkId_t id, seq_t v, CLVector &m)