tair是taobao的KV数据库,支持插拔的插件开发和文件和内存两种存储引擎,以下是对他文件存储引擎的原理剖析。
CURD的数据都是以item的形式给出,每个item的结构如下:
主要由几部分构成:
1) 元数据部分,bucket_index表示了key的hashcode在索引文件bucket段中间的位置。
Meta_offset表示了该key的meta数据在索引文件的meta_pools段中的偏移量。
2) meta部分,表示了该key将写入索引文件的meta_pools段的内容。其中size表示该key写入数据文件的信息的长度,这些信息包括data段,key,value。
3) data段包括数据的描述元信息,如修改时间,创建时间,版本,padsize等等。
4) key和value。
物理文件存在索引文件和数据文件两种,每种文件都有特定的IMAGE头做标识。
索引文件如下:
索引文件分为三部分
1) 元数据,包括了item的数量,其中有几个参数非常重要:
Idx_file_size:表示当前的meta的offset,当没有delete数据的前提下,该数据表示下一个item的meta数据在meta_pools中的offset。
Dat_file_size:表示了数据文件中的下一个item在data中的offset。
Free_idx_header:当某些key delete的时候,其key会在meta_pools中回收,这个时候free_idx_header维持着一条free meta链条的指针(因为meta的长度是固定的)。
2) meta信息索引区:这个区域的数量基本上是bucket count*4,先用key的hashcode%bucketCount来取得一组同义词(hashcode%bucketCount相等)的中首个同义词在meta_pools中的offset,也就是item中的meta_offset.然后用进行同义词树遍历查找。知道找到该hashcode==key.hashcode的item进行update或者找到树的叶子节点判断为该key的数据为null,必须new。
3) 元描述数据存储区:主要是用来存储item结构中的meta信息段,因为meta信息和写入数据文件的信息不同的是,其长度是固定的,所以,同义词的meta信息会用meta信息中的left和right来表示树的左子节点和右子节点。其中二叉树是有序的二叉树。
数据文件:
主要包括两部分:
1) IMAGE信息。
2) 数据的存储区,存储的数据包括item的数据区,key和value。
其中新增数据的场景如下:
关于回收
1)索引文件的回收。当key被删除的时候,meta_pools中的meta会被回收,所有被回收的meta空间被left和right串行起来组成链表,回收的时候,将会将index元信息中的free_idx_header作为指针指向链表的端点,当下一次有key进行new的时候,优先分配这个回收链表中的meta空间。
//首先判断时候有回收的空间。
if(header->free_idx_head != 0) {
fdb_item temp_item;
temp_item.meta_offset = header->free_idx_head;
log_debug("read from freeindex, offset: %llu",
temp_item.meta_offset);
if(read_meta(temp_item) && (temp_item.meta.size == 0)) {
log_debug("set new item's meta offset to: %llu",
temp_item.meta_offset);
ret_item.meta_offset = temp_item.meta_offset;
header->free_idx_head = temp_item.meta.left;
}
}
PROFILER_END();
//其次再从meta_pools的尾巴上开始分配
if(ret_item.meta_offset == 0) {
ret_item.meta_offset = header->idx_file_size;
log_debug("set meta offset to index file: %llu",
header->idx_file_size);
header->idx_file_size += FDB_ITEM_META_SIZE;
}
2)数据文件的回收,数据文件的回收分为update和delete两种,update的时候,如果原来的value分配的空间小于更新的value的空间,那么要将原来的data的空间回收,另外分配新的数据空间。Delete也是同样的道理。回收后的空间,将被放入到数据文件的一个pool中进行统一管理。
free_block.size = old_item.meta.size;
free_block.offset = old_item.meta.offset;
//进行统一管理。
fb_manager->insert(free_block);
在分配数据空间的时候,会先从这个free pool中看是否能够找到足够容纳下的连续的数据空间。否则则按照index文件的元数据dat_file_size后面进行分配。
if(fb_manager->search(ret_item.meta.size, fb)) {
ret_item.meta.offset = fb.offset;
ret_item.meta.size = fb.size;
}
else {
ret_item.meta.offset = header->dat_file_size;
header->dat_file_size += ret_item.meta.size;
}