
LevelDB是Google传奇工程师Jeff Dean和Sanjay Ghemawat开源的KV存储引擎,原理基于BigTable(LSM文件树),无索引机制,存储条目为Key-value,适用于保存数据缓存、日志存储、高速缓存等应用。
levelDB并不算是一个数据库,只是KV存储系统,不包含网络服务接口,所以无法像MySQL那样,可以通过客户端来连接并直接读写。其他系统软件可以用它来做存储引擎,通过静态链接或者动态链接的方式集成进来,使用leveldb提供的接口存放数据。
leveldb用C++编写,设计和代码结构都比较精致、规范,适合通过源码阅读学习提升C++编程能力。
本文先对leveldb的设计思路、整体结构进行简单的介绍,能够从大体上有一个整体的认识和感知。
leveldb的一些特点:
1 LevelDb是一个持久化存储的KV系统,和Redis这种内存型的KV系统不同,LevelDb不会像Redis一样狂吃内存,而是将大部分数据存储到磁盘上。
2,LevleDb在存储数据时,是根据记录的key值有序存储的,就是说相邻的key值在存储文件中是依次顺序存储的,而应用可以自定义key大小比较函数,LevleDb会按照用户定义的比较函数依序存储这些记录。
3,像大多数KV系统一样,LevelDb的操作接口很简单,基本操作包括写记录,读记录以及删除记录。也支持针对多条操作的原子批量操作。
4,LevelDb支持数据快照(snapshot)功能,使得读取操作不受写操作影响,可以在读操作过程中始终看到一致的数据。
5,除此外,LevelDb还支持数据压缩等操作,这对于减小存储空间以及增快IO效率都有直接的帮助。
LevelDb结构
首先从调研leveldb的接口进行数据写操作出发,看看数据通过leveldb存储到硬盘要经历哪些过程。先看下图所示:

写入一个KV数据时,会经过三个重要的table和一个log:
内存中的MemTable和Immutable MemTable和磁盘上的SSTable,以及磁盘中的log文件。
KV写操作先会写入到磁盘log文件中,然后再写入内存一个叫做跳跃表的数据结构Memtable,之后写操作返回成功。当Memtable的大小达到一个上限后,那么memtable会变成Immutable MemTable,后者也存在于内存,只不过是只读。同时会产生一个新的memtable来支持新的写操作。Immutable MemTable中的数据内容会定期刷新到磁盘的sstable,实现持久化保存。
由此可见,数据通过leveldb写入时,只需要一次磁盘log文件的顺序写,和一次内存(metable)写即返回成功,具有较好的写效率。
同时对数据持久化、容灾保护都做了较好的设计,若在写入过程中系统复位和断电,重启后读取磁盘中log文件的日志记录重新写入内存表,
数据即可恢复。当KV数据到达sstable后,旧的log文件会被删除,且产生新的log文件,保证写流程的可靠性。
有了一个大体概念后,再看看leveldb的整体设计结构:

从图中可以看出,构成LevelDb静态结构的包括六个主要部分:内存中的MemTable和Immutable MemTable以及磁盘上的几种主要文件:Current文件,Manifest文件,log文件以及SSTable(Sorted String table)文件。当然,LevelDb除了这六个主要部分还有一些辅助的文件,但是以上六个文件和数据结构是LevelDb的主体构成元素。
SSTable:SSTable就是由内存中的数据不断导出并进行Compaction(即数据压缩)操作后形成的,而且SSTable的所有文件是一种层级结构,第一层为Level 0,第二层为Level 1,依次类推,层级逐渐增高,这也是为何称之为LevelDb的原因。
level0是由Imuabletable memtable直接dump到磁盘中的,level1是由level0经过compaction获得,level2是由level1经过compaction获得,以此类推。其中每个文件后缀为.sst, 每个level中的文件数都是有限制的,超过了限制则会被compaction到更高level的层次上去。
其中level0中的单个文件(sst)是有序的,但是文件与文件之间是无序的并且有可能有重合的key,level 1 ~ level n 每一个level中在自己level中都是全局有序的。
Manifest文件:记载了SSTable各个文件的管理信息,比如属于哪个Level,文件名称叫啥,最小key和最大key各自是多少。
Current文件:这个文件的内容只有一个信息,就是记载当前的manifest文件名。因为在LevleDb的运行过程中,随着Compaction的进行,SSTable文件会发生变化,会有新的文件产生,老的文件被废弃,Manifest也会跟着反映这种变化,此时往往会新生成Manifest文件来记载这种变化,而Current则用来指出哪个Manifest文件才是我们关心的那个Manifest文件。
LevelDb简单使用举例:

这里简单写了一个CPP程序,先创建并打开一个leveldb数据库实例,LevelDB提供了Put、Get和Delete三个方法对数据库进行添加、查询和删除,这里先写入再查询,最后打印查询结果。




