LevelDB设计与实现 - 基础篇

以RocksDB为存储引擎的MyRocks是近几年流行起来的MySQL分支,在Facebook等公司广泛使用,网易杭研的数据库内核团队从2018年初开始系统研究MyRocks,目前已在内部进行小范围推广使用,效果比较理想。

在调研、测试和试用期间,积累了一些经验,包括技术实现等。本系列想跟大家分享我们组内小伙伴做的LevelDB调研输出,这是第一篇,简要介绍架构和基本概念。


LevelDB是Google的 Jeff Dean和Sanjay Ghemawat设计开发的key-value存储引擎。LevelDB底层存储利用了LSM tree的思想, RocksDB是Facebook基于LevelDB开发的存储引擎,针对LevelDB做了很多优化,但是大部分模块的实现机制是一样的。

LevelDB是一个持久化存储的KV系统,和Redis这种内存型的KV系统不同,LevelDB不会像Redis一样狂吃内存,而是将大部分数据存储到磁盘上。LevleDB在存储数据时,是根据记录的key值有序存储的,就是说相邻的key值在存储文件中是依次顺序存储的,而应用可以自定义key大小比较函数,LevleDB会按照用户定义的比较函数依序存储这些记录。

像大多数KV系统一样,LevelDB的操作接口简单,基本操作包括写记录、读记录以及删除记录。另外,LevelDB支持数据快照(snapshot)功能,使得读取操作不受写操作影响,可以在读操作过程中始终看到一致的数据。除此之外,LevelDB还支持数据压缩等操作,这对于减小存储空间以及增快IO效率都有直接的帮助。

基本概念

  1. Memtable

DB数据在内存中的存储方式,写操作会先写入memtable,memtable有最大限制(write_buffer_size)。LevelDB/RocksDB的memtable的默认实现是skiplist。当memtable的size达到阈值,会变成只读的memtable(immutable memtable)。后台compaction线程负责把immutable memtable dump成sstable文件。RocksDB增加了column family的概念,不同的column family不共享memtable,其他memtable机制与LevelDB一样。

  1. sstable

DB数据持久化文件,内部key是有序的,文件内部前面是数据,后面是索引元数据。sstable文件之间逻辑上是分层的,LevelDB最大支持7层。

  1. SequenceNumber

LevelDB中每次写操作(put/delete)都有一个版本,由sequence number来标识,整个DB有一个全局值保存当前使用的SequenceNumber,key的排序以及snapshot都要依赖它。

  1. Version

将每次compact后的最新数据状态定义为一个version,也就是当前DB的元信息以及每层level的sstable的集合。跟version有关的一个数据结构是VersionEdit,记录了一次version的变化,包括删除了哪些sstable,新增了哪些sstable。old version + versionedit= new version。整个DB存在的所有version被VersionSet数据结构保存,这个数据结构包含:全局sequencenumber、filenumber、tablecache、每个level中下一次compact要选取的start_key。

  1. FileNumber

DB创建文件时将FileNumber加上特定的后缀作为文件名,FileNumber在内部是一个uint64_t类型,并且全局递增。不同类型的文件的拓展名不同,例如sstable文件是.sst,wal日志文件是.log。LevelDB有以下文件类型:

enum FileType {
  kLogFile,
  kDBLockFile,
  kTableFile,
  kDescriptorFile,
  kCurrentFile,
  kTempFile,
  kInfoLogFile  // Either the current one, or an old one
};
  1. Key

LevelDB对于用户输入的key做了不同的处理,user_key表示用户输入的key,internal_key是DB内部使用的key,在uers_key的基础上添加了sequencenumber和valuetype。

  1. compact

DB有一个后台线程负责将memtable持久化成sstable,以及均衡整个DB各个level层的sstable。compact分为minor compaction和major compaction。memtable持久化成sstable称为minor compaction,level(n)和level(n+1)之间某些sstable的merge称为major compaction。

架构

图1是LevelDB的架构图,黄线上面是内存组件,黄线下面是磁盘组件。内存组件包括memtable、immutable memtable、log,磁盘组件包括:CURRENT文件、MANIFEST文件、.log文件、LOG文件、LOCK锁文件。

操作接口Put是写数据的接口,数据先写入log文件,然后写入memtable,如果memtable大小超过了设定的阈值,则memtable转成immutable memtable,immutable memtable只能读不能写。Immutable memtable会compact到磁盘上形成sstable文件。操作接口Get是读数据的接口,读数据的顺序是:1.memtable 2.immutable memtable 3.sst file。

sstable文件是用户数据在磁盘上持久化存储的文件,逻辑上按层存储,内部按key排序,除了L0层外,其他层sstabel文件的key之间没有重叠。LevelDB支持多版本,MANIFEST文件记录了版本变化的信息,随着sstable文件增多,MANIFEST文件也是会增多的,所以CURRENT文件指向当前的MANIFEST文件。除了immutable memtable会compact成sstable文件,磁盘上相邻层之间也会发生compaction操作。

WAL(Write-ahead Log)

现在关系型数据库几乎都提供了WAL机制,WAL机制即先写日志数据,后写用户数据,这样保证了用户数据持久化。在数据库意外宕机时,可以利用WAL恢复到宕机前的状态。LevelDB的日志格式如图2所示。

与log相关的代码如下:

db/log_format.h

db/log_reader.h

db/log_reader.cc

db/log_writer.h

db/log_writer.cc

LevelDB的日志格式如图2所示,value有可能太大,因此无法存放在一个block,会跨block存储,因此leveldb提供了RecordType枚举类型。payload的格式如图3所示:

LevelDB的删除操作是标记删除,并没有真正删除原来的数据,真正的删除操作通过compaction删除,为了区分写入的数据类型,LevelDB定义了ValueType枚举类型:

enum ValueType {

kTypeDeletion = 0x0,

kTypeValue = 0x1

};


未完待续...

发布于 2018-12-02

文章被以下专栏收录