LevelDB基础原理(1) SSTable

1. 介绍

1.1 描述

SSTable(Sorted String Table)是一个通常放在磁盘上的,排序的字符串表, 用来高效存储大量的键值对数据, 同时搭配上优化实现IO操作的高吞吐量.

1.2 背景

当我们要运行一系列的Map-Reduce任务时,因为输入的数据量很大,所以消耗在IO的时间上很多.如果不考虑随机读取的场景的话,我们可以通过一系列流式处理输入数据,再将数据流式输出到磁盘上,最大程度的减少磁盘IO成本.
所以SSTable的主要用于交换大量的,排好序的数据片段的数据结构,他的使用场景在于

  • 需要高效的存储大量KV数据
  • 数据需要顺序写入
  • 高效的进行顺序读写
  • 没有随机读取或者随机读取性能不高

1.3 组成结构

抽象的看,SSTable的结构主要由两部分组成,索引和键值对数据.

  • 索引由多个key-offset的键值对组成,每个键值对包含了对应的key在sstable的位置,如果要读取某个键对应的值,需要通过索引获取对应的offset来定位
  • 键值对数据是将所有排好序的key和value紧凑的存放在一起

2. 设计

实际设计中,将SSTable分为数据区和索引区, 每个区又主要由多个block组成,block可以看作是存储信息的一个单元。其中数据区由多个data block组成,其中存储了具体的KV数据内容。
索引区是做什么的,为什么数据区要区分多个data block,全部放在一个data block会有问题吗?由于SSTable是存放在磁盘上的数据结构,而其中的数据又是排序好按照顺序存放的。当我们在随即查询一条key的时候,如果没有索引,只有一块完整的数据块,就只能顺序的遍历整个数据块来查询了,因此将数据区进行了分块的操作,并且通过索引区可以获得每个数据块的一些具体信息,查询时可以通过索引信息快速定位到对应范围内的数据块进行查找,能大大减少查询开销。
image.png

2.1 block

block是SSTable里面存储信息的一个单元,主要由数据区record和restarts组成,

  • restarts的目的主要是获取每组的起始位置,block一般给一定数量的key-value分组(默认是16),restarts数组记录了每组的起始位置,当进行随机查询的时候,可以通过restart数组来对数据进行二分查询,快速获取对应的key所在的组.

  • 基础的record结构可以用key-length|key-data|value-length|value-data这样的结构顺序排布,但是sstable中的key是相邻存储的,实际上有很大的概率key的前缀出现重复,所以存储时采用前缀压缩,后一个key存储与前一个key不同的部分(每组的起始位置key进行完整存储)

  • type表示数据是否压缩,crc32是校验码

2.2 数据区

数据区由多个data block组成,

  • data block用于存储key-value记录,分为Data, type, CRC三部分

2.3 索引区

索引由filter block, metaindex block, index block, footer四部分组成

  • filter block: 用于快速从data block判断key value是否存在(默认没有使用)
  • metaindex block: 记录filter block的相关信息
  • index block: 描述一个data block, 存储对应data block的最大key值以及data block在文件中的偏移量和大小
  • footer:索引的索引,记录metaindex block和index block在SSTable中的偏移量和大小

2.3.1 index block

index block是data block的索引,记录每个data block最大的key和起始位置以及大小

  • 理论上的存储是以每个data block的最大key作为key,起始位置和大小作为value, 这样可以二分定位key所在的block
  • 实际上SSTable在存储Key的时候做了优化,存储时不保存最大的key,而是保存能够分割data block的最短key,例如data block1的最大key为abcdefg, datablock2的最大key为abefg, 则index保存abd, 只要能够保证这个串大于data block1中的所有Key,小于data block2中的所有Key, 就能达到优化的目的, 这么做可以有效缩减索引长度,减少比较次数

2.3.2 filter block

filter block是一个bloom filter(布隆过滤器), bloom filter通过对每个值计算多个无偏hash值映射到数组上,然后查询的时候看查询值的所有hash值在数组内是否都被映射,来判断是否key已经出现过
每个bloom filter是对data block的key进行多次hash形成的字节数组, 多少个data block就会对应对少个bloom filter
filter block的作用在于判断查询key是否在block内存在,如果不存在,就不用通过restarts的方式查找,能够提高效率

2.3.3 metaindex block

只有一条记录,key 为filter. + filter_policy的name, value是filter大小和起始位置

footer位于SSTable文件尾部, 占用空间固定为48字节,

  • 末尾8字节是一个magic number, 用于校验
  • metaindex_block_handle和index_block_handle记录了metaindex block和index block的起始位置和大小,分别以一个不定长64位整数存储,总共4个,因为不定长的64位整数最多需要10个字节来存储,所以最多是40个字节(为什么是10个而不是8个呢,不定长的整数在每个字节增加了一个标志位,导致每个字节只能存储7个比特的信息,这个在后续对LevelDB的源码剖析(2)中有详细解释)
  • 因为footer是定位文件的起始索引,所以定位的方式实际是通过访问文件结尾的固定长度的内容,如果因为不定长整数导致footer的长度不固定,自然就无法准确定位footer的开头,所以为了规避这个问题,对于block hndle不到40比特的文件,将在后面通过padding来补齐到40个比特
  • magic number用来校验文件的正确性,规定占用8个字节

3. 总结

3.1 特点

  1. **形成之后数据不可变: **由于所有键值对是有序的存放在一起的,所以SSTable在序列化成文件之后是不可变的,否则进行插入和删除都需要移动一大片数据,开销很大.
  2. **主要作用于顺序读取场景: **由于随机读取是通过多层索引层层查找的,所以sstable主要作用在完全顺序读取或随机读取不多的场景下

3.2 随机查找流程流程

  1. 先读取footer
  2. 根据footer中的meta index block handler记录的布隆过滤器信息,读取布隆过滤器记录的二进制数据
  3. 根据布隆过滤器的记录的二进制数据,查询key是否存在,如果不存在则直接返回
  4. 根据footer中的index block handler记录的 index block的起始位置和大小,读取index block
  5. 通过index block查询key所在的data block
  6. 在data block内通过restarts进一步确定key所在的group

3.3 整体结构图

image.png

posted @ 2022-11-27 01:39  Hugh_Locke  阅读(677)  评论(0编辑  收藏  举报