导读:LevelDB是Google开源的持久化KV数据库,在其高性能的背后,将数据拆分成多层进行存储。本文作者深入分析了LevelDB存储模块的设计和源码实现,快速了解LevelDB高性能背后的原理。
作者 codedump codedump.info 博主,多年从事互联网服务器后台开发工作。可访问作者博客阅读 codedump 更多文章。
本文基于leveldb 1.9.0代码。
整体架构
如上图,leveldb的数据存储在内存以及磁盘上,其中:
memtable:存储在内存中的数据,使用skiplist实现。
immutable memtable:与memtable一样,只不过这个memtable不能再进行修改,会将其中的数据落盘到level 0的sstable中。
多层sstable:leveldb使用多个层次来存储sstable文件,这些文件分布在磁盘上,这些文件都是根据键值有序排列的,其中0级的sstable的键值可能会重叠,而level 1及以上的sstable文件不会重叠。
在上面这个存储层次中,越靠上的数据越新,即同一个键值如果同时存在于memtable和immutable memtable中,则以memtable中的为准。
另外,图中还使用箭头来表示了合并数据的走向,即:
以下将针对这几部分展开讨论。
Log文件
写入数据的时候,最开始会写入到log文件中,由于是顺序写入文件,所以写入速度很快,可以马上返回。
来看Log文件的结构:
一个Log文件由多个Block组成,每个Block大小为32KB。
一个Block内部又有多个Record组成,Record分为四种类型:
Full:一个Record占满了整个Block存储空间。
First:一个Block的第一个Record。
Last:一个Block的最后一个Record。
Middle:其余的都是Middle类型的Record。
Record的结构如下:
32位长度的CRC Checksum:存储这个Record的数据校验值,用于检测Record合法性。
16位长度的Length:存储数据部分长度。
8位长度的Type:存储Record类型,就是上面说的四种类型。
Header部分
数据部分
memtable
memtable用于存储在内存中还未落盘到sstable中的数据,这部分使用跳表(skiplist)做为底层的数据结构,这里先简单描述一下跳表的工作原理。
如果数据存放在一个普通的有序链表中,那么查找数据的时间复杂度就是O(n)。跳表的设计思想在于:链表中的每个元素,都有多个层次,查找某一个元素时,遍历该链表的时候,根据层次来跳过(skip)中间某些明显不满足需求的元素,以达到加快查找速度的目的,如下图所示:
在以上这个跳表中,查找元素6的流程,大体如下:
构建一个每个链表元素最多有5个元素的跳表。
由于6大于链表的第一个元素1,因此如果存在必然在1之后的元素中,因此进入元素1的指针数组中,从上往下查找元素4:
第一层:指向的指针为Nil空指针,不满足需求,继续往下查找;
第二层:指向的指针保存的数据为4,小于待查找的元素4,因此如果元素6存在也必然在4之后,因此指针跳转到元素4所在的位置,继续从上往下开始查找。
到了元素4所在的指针数组,开始从上往下继续查找:
第一层:指向的指针保存的数据为6,查找完毕。
从上面的分析过程中可以看到:
跳表是一种以牺牲更多的存储空间换取查找速度,即“空间换时间”的数据结构。
跳表的每一层也都是一个有序链表。
如果一个元素出现在第i层的链表中,那么也必然会在第i层以下的链表中出现。
链表的每个节点中,垂直方向的数组存储的数据都是一样的,水平方向的指针指向链表的下一个元素。
最底层的链表包含所有元素,也就是说,在最底层数据结构退化为一个普通的有序链表。
sstable文件
大体结构
首先来看sstable文件的整体结构,如下图:
sstable文件中分为以下几个组成部分:
data block:存储数据的block,由于一个block大小固定,因此每个sstable文件中有多个data block。
filter block以及metaindex block:这两个block不一定存在于sstable,取决于Options中的filter_policy参数值,后面就不对这两部分进行讲解。
index block:存储的是索引数据,即可以根据index block中的数据快速定位到数据处于哪个data block的哪个位置。
footer:脚注数据,每个footer数据信息大小固定,存储一个sstable文件的元信息(meta data)。
可以看到,上面这几部分数据,从文件的组织来看自上而下,先有了数据再有索引数据,最后才是文件自身的元信息。原因在于:索引数据是针对数据的索引信息,在数据没有写完毕之前,索引信息还会发生改变,所以索引数据要等数据写完;而元信息就是针对索引数据的索引,同样要依赖于索引信息写完毕才可以。
block
上面几部分数据中,除去footer之