LevelDb 源码阅读--SSTable

SSTable介绍

当memtable内存表增长到一定程度时(memtable.size> Options::write_buffer_size),原先的Memtable就成为Immutable Memtable。LevelDb后台调度会将Immutable Memtable的数据导出到磁盘,形成一个新的SSTable文件。

SSTable(Sorted String Table)就是由内存中的数据不断导出并进行Compaction操作后形成的,而且SSTable的所有文件是一种层级结构,第一层为Level 0,第二层为Level 1,依次类推,层级逐渐增高,这也是为何称之为LevelDb的原因。除此之外,Compact动作会将多个SSTable合并成少量的几个SSTable,以剔除无效数据,保证数据访问效率并降低磁盘占用。

LevelDb不同层级有很多SSTable文件(以后缀.sst为特征),所有.sst文件内部布局都是一样的。Log文件是物理分块的,SSTable也一样会将文件划分为固定大小的物理存储块,但是两者逻辑布局大不相同。根本原因是:

  • Log文件中的记录是Key无序的,即先后记录的key大小没有明确大小关系,
  • .sst文件内部则是根据记录的Key由小到大排列的。

SSTable存储结构

整体结构

SSTable存储结构可以分位数据存储区和数据管理区。

  • 数据存储区存放实际的Key:Value数据。
  • 数据管理区则提供一些索引指针等管理数据,目的是更快速便捷的查找相应的记录。管理数据又分为四种不同类型:
    • Filter Block:过滤器数据块
    • MetaBlock Index:元数据块,当前存放过滤器索引
    • Index block:索引块,用于用户数据快速定位
    • SSTable的注脚区:
      • Meta Index Block Handle指出了Meta Index Block的起始位置和大小。(最大10字节)
      • Index Block Handle指出了Index Block的起始地址和大小。(最大10字节)
      • 一个填充区。(补齐上面的20字节)
      • 魔数字。(固定8字节)

数据索引区(index block)

数据索引区的每条记录是对某个Data Block建立的索引信息,每条索引信息包含三部分内容。

  • key:index block的key取值分为两种情况。
    • 非最后一个block,例如上图datablock_i。使用FindShortestSeparator("the best", "the xxxx")获取,key等于"the c".
    • 最后一个block,datablock_end。使用FindShortSuccessor("xyz")获取,key等于"y"。
  • offset:第二个字段指出数据块i在.sst文件中的起始位置,
  • size: 第三个字段指出Data Block i的大小(有时候是有数据压缩的)。
    • 通过offset - size可以获取到数据块在文件中的位置。


数据存储区(datablock结构)

数据存储区分位一个个的datablock。每个Block分为三个部分(.sst的物理布局):

  • 第一列是数据部分。内部又分为两部分:
    • Entry 列表,即一个个KV记录,其顺序是根据Key值由小到大排列的。
    • 在data block尾部则是一些“重启点”(Restart Point),相当于是一些指针,指出data block内容中的一些记录位置。
  • 第二列Compreesion Type区用于标识数据存储区是否采用了数据压缩算法(Snappy压缩或者无压缩两种)
  • 第三列CRC部分则是数据校验码*,用于判别数据是否在生成和传输中出错。

Block内容里的KV记录是按照Key大小有序的。这样的话,相邻的两条记录很可能Key部分存在重叠,比如key i=“the Car”,Key i+1=“the color”,那么两者存在重叠部分“the c”,为了减少Key的存储量,Key i+1可以只存储和上一条Key不同的部分“olor”,两者的共同部分从Key i中可以获得。记录的Key在Block内容部分就是这么存储的,主要目的是减少存储开销。“重启点”的意思是:在这条记录开始,不再采取只记载不同的Key部分,而是重新记录所有的Key值,假设Key i+1是一个重启点,那么Key里面会完整存储“the color”,而不是采用简略的“olor”方式。

在Block内容区,每个KV记录的内部结构是怎样的?每个记录包含5个字段:key共享长度,比如上面的“olor”记录,其key和上一条记录共享的Key部分长度是“the c”的长度,即5;key非共享长度,对于“olor”来说,是4;value长度指出Key:Value中Value的长度,在后面的Value内容字段中存储实际的Value值;而key非共享内容则实际存储“olor”这个Key字符串。


相关代码

​```
//block_builder.h/block_builder.cc
void BlockBuilder::Add(const Slice& key, const Slice& value); //向datablock增加entry
void BlockBuilder::Finish(); //追加Restart points
size_t BlockBuilder::CurrentSizeEstimate(); //预估大小,超过一定大小后,写入文件

//table_builder.h/table_builder.cc
void TableBuilder::Add(const Slice& key, const Slice& value); //写入entry
TableBuilder::Flush(); //flush到文件
void TableBuilder::WriteBlock(BlockBuilder* block, BlockHandle* handle); //block->Finish、压缩
void TableBuilder::WriteRawBlock(const Slice& block_contents,
                                 CompressionType type, BlockHandle* handle); //datablock写入文件,添加压缩方式、crc。
Status TableBuilder::Finish() //剩余datablock写入文件,并生成管理区。

//BlockHandle 用于表示当前块的offset_及size_。
​```

发布于 2019-08-30