分享

(3)自己动手构建Key

 dongsibei 2014-04-25
    之前做过key-value store相关的工作,一直没有时间进行详细的总结,在面试的时候被问到该项目的时候,因为过了很长时间,回答起来总是捉襟见肘,下面就把当初的设计整理了下来。

在启动设计自己的Key-Value Store的时候,遇到了如下的一些问题:
1)key-value是一对一,还是一对多,假设为1:n,这里的n是变化的还是固定的呢?
2) key-value的长度是固定的还是变化的?
3)key-value本身是分布式的还是单节点的?
如果系统做得越灵活,系统的复杂性也就越高,在复杂性的设计中需要各种策略。这也给我很大的Motivation去阅读包括MemCached、Redis在内的in-momory key-value store,以及基于列存储的HBase。接下来blog中将新开一个专题来讨论分布式存储的设计策略。

ok,由于这是我们设计的第一个Key-Value Store系统,我们尽量简化设计的难度,做如下的规定:
1)设置的key-value pair的长度固定,不足则补零,插入的key-value过长则提交失败。
2)对于不同的模式的key-value pair(长度、value count)
每个文件key-value个数对比确定,例如1:2,key,value的长度固定。这样不同类型的key-value,定向存储到不同的文件中。
3)Key使用了uint8_t 数组结构(与char实现类型转换),在每一个instance中value的类型一致。

自己动手构建Key-Value <wbr>Store系统

图1 系统整体框架图
系统与其外部环境交互以及主要的构架如图1所示,底层依赖于文件系统,上层提供用户使用的API,同时包含数据组织部分、索引的维护部分、查询处理部分三个部分,同时正交相关的功能模块还包括:metric模块和Buffer management模块。

User API,说明KVS组件提供给用户的API接口,描述如下:
 1 
 9 void
10 kvs_insert_tuple(instance* pInstance, const uint8_t* key, int keyLength, int vcard, const unsigned char** value, int* valLength);
11
12
17 void
18 kvs_delete_tuple(instance* pInstance, const uint8_t* key, int length);
19
20
28 void
29 kvs_query_obj (instance* pInstance, const uint8_t* key, int keLength, int* vcard, unsigned char** value, int** vaLen);
30
31
40 instance*
41 kvs_open_instance (const char* filePath);
42
43
47 void
48 kvs_drop_instance (const char* filePath);
49
50
55 void
56 kvs_stop_instance (instance* pInstance,const char* filePath);
57
58
62 void
63 kvs_set_cmp_operator(bool(*)(const uint8_t* lhs, int llen, const uint8_t* rhs, int rlen));
64
65
69 void
70 kvs_set_hash_operator(int(*)(const uint8_t* key, int keyLength, enum keyType));

用户操作主要包括:
1)初始化一个instance,每一个instance的key-value的schema是相同的,默认在磁盘文件中存储。 2)插入、删除key-value pair。
3)为key设置Hash函数。

数据组织(data organization):
自己动手构建Key-Value <wbr>Store系统
图2 kvs 存储结构图

每一个instance都会有唯一的一个文件对应。
通过index maintenance可以找到每一个key对应的key_pos,通过key_pos即可找到对应的key-value pair。

索引与要素对象分离的存储方式 每一个KVP(元组)的地址包含两部分:该元组位于的磁盘块的磁盘块ID和磁盘块内部的偏移地址offset。 其内存中数据结构表示为


    1 typedef struct  
    
2 {
3 uint32 id, // 无符号的磁盘块ID
4 uint16 offset // 采用16bits的偏移主要是考虑到内存对齐
5 } kvp_pos;((PACKED));
由图2 kvs存储结构图,每一个diskpage的大小为4k,kvs_pos标识磁盘块ID是32位无符号整型,则整个文件可以支持的最大空间为:4K*2^32 = 16TB,4k磁盘快内部寻址使用了16-bit的offset,正常情况下2^12=4096B已经满足了寻址的要求,这里考虑对齐,采用了16bit的方式。
struct cardinality {
uint16_t dirty:1; 
uint16_t n_card:15;
}
使用2个字节表示该diskpage的属性,dirty=1 最高位为1时,将数据写回磁盘,dirty=0,不处理该磁盘块。
n_card 当前kvp的个数,插入kvpair时,+1,删除kvpair时不改变它的内容。
由于一个文件内kvpair的schema是固定的,即key的类型、长度,value的类型、长度在初始化kvs实例时确定,所以在每一个diskpage中,它所能支持kvpair的个数也是固定的,则仅挨着cardinality后面是bitmap[var],用0 1 来标识它是否存在kvpair,例如,删除一个key-value pair,则标示0,显然当所有位都被置0,就表示该diskpage上的所有kvpair都被删除,设置diskpage的n_card=最大容量,这个值在metainfo中有定义。
由kv_pos定位一个kvpair的计算过程:
id*pageSize + 每块前面确定的冗余空间(包括 2个byte的cardinality和m个bitmap的位置) + offset*sizeof(struct kvpair).

在4k metainfo元数据之后,会存在一个4k的bitmap,它标示了4*1024*8 *4k = 128MB的连续的磁盘块。这里bitmap的每一位0 1代表了该磁盘块是有空洞的,还是满的。在4k的磁盘块的key-value全部填充完毕,将与此对应bitmap位置1,在删除其中一个key-value pair之后,该位将置0,同时如果在diskpage内部的bitmap位全部为0,且它目前的长度为metainfo中指定的最大kvpair的个数,就可以将该diskpage作为下一个分配的page。设置一个阈值,当条件符合时,就扫描空洞,适时地回收资源。

数据磁盘块的结构如下:

 1 
    5 unsigned char Dbitmap[var]; 
6
7
12 struct card{ 13 uint16_t dirty:1; //
14 uint16_t n_card:15; 15 };
16
20 typedef struct{
21 unsigned char* key;
22 uint32 key_length;
23 unsigned char** value;
24 uint32* val_length;
25 } kvp;
26
27 typedef struct{
28 int next_empty_id; //下一个空磁盘块,如果没有就为-1
29 struct card num; //当前磁盘块中要素的数量,对应图4中的Cardinality, 最高位为表示当前磁盘块是否Dirty, 即需要将内存数据写回磁盘
30 unsigned char* Dbitmap;
31 kvp* data;
32 }diskPage;
到此,底层的数据存储系统就已经完成了。那么,我们如何插入一个key-value pair,并且如何查询key的value呢?这些过程需要依靠B+树来完成。
经过hash函数的运算,每一个key可以映射成一个long long int的整形数,最初的B+树只有一个父节点,在插入key-value pair的过程,首先会获取key的hash值,然后在B+树上查找,例如B+数的根节点为50,则左子树就包含hash(key)= [0~50)的节点的kv_pos的信息,右子树包含hash(key) = [50,>50)节点的kvs_pos的信息。因此,在插入key的过程中,树的规模会不断扩大,在叶子节点会保存一个hash(key)有序数组,当search(key)的时候,在叶子节点中使用二分查找,找到对应的kvs_pos的index.系统设定最大的B+树的高度。树的非叶子节点存在内存中,叶子节点会部分存在内存,存在一个缓存模块来实现index文件与内存之间的映射。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多