直接读取 value table_这可能是全网最详细的LevelDB SSTable结构解析(配合读取过程源代码讲解)...

这学期选的数据库实现课程的期末大作业是修改leveldb的源代码来实现某些特定的功能,我在班里大佬的给Hint的情况下大约4天撸完了整个作业(大佬反而花了很多时间,因为他是先行者,而且自己造了很多轮子,膜拜orz),因为我懒,不想自己造轮子写功能,所以只能通过看代码来寻找到相应的函数,在这个过程中对于sstable这个结构也有了更深入的了解.

SSTable 格式

45112d664c472744adfc79b20dff370f.png
  1. footer: 整个sstable文件的头部, 用来存放metaindex blockindex block这两个块的索引
  2. index block: 指向所有的data block,保存所有data block的索引
  3. metaindex block: 指向所有的meta bock,保存所有meta block的索引
  4. data block: 用于存储数据
  5. meta block:在正常情况下为空, 如果在打开数据库时声明使用bloom filter,那么此处他们将会保存bloom filter的相关数据.

Block 的结构

a4c5369b7f816041bc61e438f094ea7b.png

所有的block的结构都是如此,包含了若干个数据项和若干个重启点. 在这里你可能会困惑于什么是重启点. 想要搞清楚这个,必须先了解数据项的结构.

fc20e6fb55fc7ede50797620ceec33a0.png
entry 结构

为了压缩数据,leveldb 的开发人员使用了一种(在我看来)非常tricky的技术, 他们在保存数据的key时并不直接存储整个字符串,而是使用一种共享字段技术.

举个例子, 我有两个key,分别是“abcd“和 ”abcep“,如果以”abcd“为基准,那么此时第二个key,也就是"abcep"的存储格式将会如下.

452cf9ead617bb4a51c405c1e13994d4.png

共享长度3 是 “abc”的长度, 非共享长度10 是 “ep”(2) + seq number 和 value type的编码.

介绍完这些就可以了解什么是重启点了,重启点其实就是刷新每个基准的点, 例如前16个entry 都是以 “abcd”这个key作为基准, 而之后的16个entry又是以“acfg”这个key作为基准,那么每16个entry中的第一个entry就是一个重启点.

详细解读

  1. footer:

8d32ee150676d1bbc02f64066fea62bc.png

Footer的固定大小为48个bytes,最后的8个bytes为magic number,相当于是让系统知道它是footer的一个令牌, 最开始是两个handle, handle之后是一段填充字节(全为0).

class 

上面代码为format.h 中的Footer class代码, kEncodedLength 就是Footer的size, 而kMaxEncodedLength则是一个 BlockHandle可以达到的最大size(这里为10+10=20),priave变量中保存了两个handle的信息, 均可以通过public函数进行访问.

这里再补充一下Handle的结构,Handle 本质上就是一个指针,下面以IndexBlock handle为例子.

d662f18b66992fec14633797f4c9e7e0.png

他由两部分组成,分别是表示相当于sstable文件启始流偏移的Offset和表示所指向文件大小的Size.这两个变量的最大Size为20(注意上文中提到的10+10).

footer的读取:

*

上述代码为http://table.cc 中的 Open()函数.

#1 处首先进行了对于文件的size进行了判断,如果文件大小小于48个字节的话,说明肯定发生了某些错误,因为哪怕你什么都不存,一个footer的大小为48bytes.

#2 处则对文件进行了读取,通关参数传递将对于的footer数据存储于footer中. file->Read()函数中第一个参数为所要读取数据的偏移,在这里就是 文件的size - 48 个bytest,第二个参数为所要读取的长度.

#3 处则是对读取出来的footer_input数据进行解码处理, 在这里要注意, 为了存储效率,leveldb对于所有刷盘的数据都进行了Encode处理,要读出可以理解的数据,都需要进行Decode处理.

读取完Footer之后,随即开始解析其中的index block handle.

BlockContents 

可以看出这里调用了ReadBlock函数直接读取Index block的内容并将相应的数据存储在index block contents这个变量当中.

if 

成功读取之后就利用Table::Rep这一结构体来保存一些变量(绝大多数与sstable相关的内容都保存于此),从代码中可以看出保存的有改sstable 的文件指针,metaindex handle, index block等内容.

#1处的代码可以发现leveldb首先默认将filter data 和filter 置为空值, 这是因为如果在打开DB 是没有设置filter policy的话,整个于Filter相关的数据(即Meta data)都将不会被用到

至此,Footer的使用价值已经基本结束

2. Index Block

在调用了Table::Open()之后我们已经获取了sstable的的大致信息,接下来就可以就可以使用其中的index block来寻找具体的数值了.

接下来可以把注意力放到http://table.cc 中的 InternalGet()函数.

Status 

首先在在这个函数中,Table 中的index_block会新建一个迭代器来确定所要寻找的key所在的data block.

Seek 会在restart point数组上进行二分查找以找到确定的datablock(注意, index_block 记录的是指向data block的指针,而并非数据本身)

if 

#1 处代码中的逻辑其实非常值得细细品味, 它非常成功得体现出了写着一行代码人的功力(膜拜orz)

这里的核心思想其实就是利用bloom过滤器判断这个data block中是否有可能包含这个key.

当且仅当filter不为空且handle value成功decode并且filter中的KeyMayMatch不命中,那么才说明这个data block绝对不包含在合格key值,可以跳过. (难以理解的朋友可以先去看一下bloom 过滤器的知识)

如果上述有一个条件不满足,例如没有开启bloom过滤器(即filer == nullptr)或者KeyMayMatch为真,那么程序就会跳转到#2处, 这个data block 都会被遍历读取.(一般来说handle.DecodeFrom(&handle_value).ok()这个条件不会出现问题,他是将index block 中保存的offset值decode 出来)

接下来再来看#2 处的代码

else 

首先是调用BlockReader()这一函数, 它接受三个参数,分别是调用它的table(this), Readoptions(options)以及未经过decode的handl->value(iiter->value()),并且返回一个读取block的迭代器.

接下来便使用这个迭代器去寻找需要的key值, 寻找的方法也是二分查找,具体代码我们按下不表.如果成功找到(block_iter->Valid()=ok()),那么就会调用一个函数将寻找到的key,value保存下来.

#1 处的函数指针实际上是一个save value的函数,不必要太过于深入追究,重要的是成功的读出了key 和 value的值(block_iter->key(), block_iter->value())

至此,利用sstable 读取数据的过程便彻底结束.

reference:

LevelDB 完全解析(3):SSTable

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值