设计key-value数据的存储查询模块
(百度2011)单机存储100亿大数据量的key-value数据,要求能够支持插入和查询操作,单条数据长度不定,平均约1024字节,假设可用内存10G,磁盘空间不限,请设计一个存储查询模块,支持按照key来获取对应的value,设计目标以查询性能为先,尽量节约资源,查询可以理解为网民的检索行为。
1) 说明该设计方案和主要思路,以及优缺点
2) 请详细说明该设计思路下查询和插入的操作流程
3) 如果增加更新操作,请评估前面的设计方案是否可行,需要做怎样的修改,不可行则指明主要问题点。
分析:
1)数据量大小为:
data_size=100亿*1024Byte=10^10*10^3Byte=10^13Byte=10^4G
假设每个key的长度不超过128Byte。
使用2种文件存储数据:索引文件和数据文件。
索引文件采用类似B+树的结构化存储,一条数据对应一条记录,一条记录包括:数据的key、存储该数据的数据文件ID以及数据文件偏移量,占用空间:
record_size=key_size+data_file_ID_size+offset_size=128+4+8=140Byte。
B+树共有3层,内节点只存储最多M个关键字key和相应的M棵子树的位置,第i个关键字是第i棵子树中的最小关键字,叶节点存储最多M个key-value数据。每个节点最多包含M个key,M^3=100亿=10^10,M=2200。
内节点有2种形式:一是存储在索引文件中,包含的域有:关键字key,相应的子节点的索引文件ID和文件偏移量,占用空间:
inner_node_size_in_file=(key_size+index_file_ID_size+file_offset_size)*M=(128+4+8)*2200
=30800Byte=300K。
二是存储在内存中,除了上面提到的域,还包括指向子节点的指针域,占用空间:
Inner_node_size_in_memory*M=(inner_node_size_in_file+pointer_size)*M
=144*2200Byte=316800Byte=310K。
第一层和第二层占用空间:
(M+1)*inner_node_size_in_momery=666M,
可以全部存储在内存中。
内节点按顺序写入索引文件,采用惰性空间分配策略,即使内节点没有满,也分配最大空间inner_node_size_in_file。这样保证内节点可以在原地更新,保持有序状态。假设索引文件最大index_file_size=2G,一个索引文件可以容纳内节点数目:
Inner_count_in_file=index_file_size/ inner_node_size_in_momery
=6990>inner_node_count=M+1
一个索引文件足够了。
叶节点包含key-value数据,占用空间:
Leaf_size_in_memory=M*data_size=2200*1024=2200K
叶节点以在文件尾追加方式写入数据文件,不需要保持有序,因为B+树的第二层内节点记录了所有叶节点的数据文件ID和文件偏移量。假设一个数据文件大小是data_file_size=2G,数据文件数目为
data_file_count=data_size/data_file_size=5000
优点:
1) B+树采用3层,第1层和第2层是内节点,是索引节点,存储在索引文件,可以全部存储在内存,第3层是叶节点,存储在数据文件,部分缓存在内存。插入和查找一条数据,最多需要访问硬盘一次。
2) 索引文件采用惰性空间分配策略保证内节点可以在原地更新,保持有序状态。
3) 数据文件的更新一直是在尾部追加,不需要移动数据。
缺点:
1) 索引文件浪费了部分空间。
第二问:
查找:在根节点二叉查找关键字,下降到第二层某一个节点,二叉查找关键字,下降到第三层某一个关键字,若该条数据不在内存中,从数据文件读入,若在内存中,直接返回数据。
插入:与查找类似,找到该条数据应该在的位置,添加到最后一个数据文件的末尾。
第三问:若增加更新操作,前面的方案可行,需要做下列修改:
将数据按照大小分类存储到不同的数据文件中。假设数据大小平均分布在512Byte到1536Byte之间,步长平均为128Byte,数据文件分类为存储512Byte的,存储512+128Byte的,存储512+128*2Byte的,……,类似内存池的做法。更新数据时,将数据写到能容纳该条数据的最小分类的数据文件,将该位置记为空闲,加到该数据文件的空闲位置链表。链表的指针可以用空闲位置的前4个字节表示,文件的最开始4个字节表示链表头部指针。当然,这样会浪费部分空间。可以监控每种文件的空间使用情况,做出调整。