什么是 leveldb ?
leveldb 是由 google 的jeff dean 开发的in-memory 的key-value 数据库, 提供了基本的
Put
,Get
,Delete
操作。
leveldb 基本结构
leveldb 的基本结构如下所示:
由上图可以知道:
- memtable 和 immutable memtable :这两个是 leveldb 用来在 memory 里存储数据的结构。leveldb 首先在 memtable里存储数据, 当
memtable
使用memory 的大小超过options_.write_buffer_size
时,将其作为immutable memtable 并写入disk, 然后释放immutable memtable
。 - LOG : 记录 leveldb 运行中进行的操作,可以用来分析数据库状态
- LOCK: 当
Recover
或者DestroyDB
时用来锁定文件 - MANIFEST- : 记录了当前 leveldb 的一些属性, 格式与下面介绍的
log
一致 - CURRENT : 记录了当前
MANIFEST-*
文件的FileNumber
, 可以用来查找当前MANIFEST-*
文件 *.log
文件, 记录当前memtable
里的数据*.ldb
文件, 记录 leveldb 存储的key-value
, 文件分多层来组织, 由kNumLevels
来控制, 默认7
了解 leveldb 基本操作之前, 下面先介绍其使用的数据结构
memtable
memtable 是用来在 in-memory 中存储key-value 的数据结构, 其实质是 skiplist 。存储的数据的结构如下所示:
由上图可知:
userkey
和tag
组成了internal_key
- 其中 8 byte 的
tag
是由 7 byte 的sequence_number
和 1 byte 的type
组合而成的 sequence_number
是一个递增的无符号数据, 每次对 leveldb 的Put
,Get
,Delete
操作都会使其加一type
可取的值分别是:kTypeValue
表示当前数据有效,kTypeDeletion
表示当前数据已删除。 需要注意的是, 当使用Delete
操作时, memtable 实际只是插入一个type
为kTypeDeletion
的空值。
skiplist 内部数据是有序的, 也就是我们存储在 memtable 里的数据会根据userkey
和 sequence_number
来排序, 基本原则是先比较userkey
,之后再根据sequence_number
大的在前。
write batch 格式
WriteBatch
可以用来保证一系列操作的完整性,里面的操作要么都成功, 要么都失败。当调用Put
或Delete
时, leveldb 上会将其操作放到 WriteBatch
对象中。另外 leveldb 在实际执行WriteBatch
里操作之前,会尝试将几个WriteBatch
合并一起,可以减少write lock
的消耗。WriteBatch
里的数据格式如下图所示:
当合并几个WriteBatch
时,取其中最大的SequenceNumber
作为新的SequenceNumber
。
log 格式
为了保证数据安全, 在每一个WriteBatch
里操作在memtable
里执行之前, 会将WriteBatch
里的数据写入到log
文件里保存。当memtable
写入到sstable
文件里时,log
文件会打开一个新的文件。log
文件保存数据格式如下图所示:
由上图可知,log
文件是以kBlockSize
来存储数据, 其中一个kBlockSize
多个kHeader
和data
, 只有一个kBlockSize
剩下不到kHeader
的空间才会填充trailer(ox00)
。存储的data
就是上面介绍的WriteBatch
的数据。其中type
可能值是:
kFullType
: 表示data
全部存储在当前kBlockSize
内kFirstType
: 表示当前kBlockSize
存储的是data
的第一部分kMiddleType
:表示当前kBlockSize
存储的是data
的中间部分kLastType
:表示当前kBlockSize
存储的是data
的最后一部分
sstable 格式
当memtable
占用内存空间大小超过options_.write_buffer_size( default = 4MB)
时, leveldb 会将memtable
写到sstable
文件中。sstable
文件存储的基本格式如下图所示:
由上图可知, sstable
文件中是以options_.block_size(default = 4KB)
来组织数据的, 首先存储数据库数据。 如果有使用FilterPolicy
, 接下来就存储FilterPolicy
的数据。然后接下来存储数据库的信息,接下来就是用来索引data block
的index block
。文件最后会存储footer
。上述各自具体格式下面会详细介绍。
block 格式
上面不管是data block
, index block
还是 metaindex block
, 其基本结构都如下图所示:
其中,
crc
是对block
中除crc
之外数据的校验type
表示数据是否压缩num_restarts
表示restarts
数组的个数,restarts
数组记录的是对应的使用共享编码的key-value
的偏移shared_bytes
表示当前key
相同的部分大小,unshared_bytes
是key
里差异的大小,value_length
是value
的长度,key_delta
是差异的key
值,value
就是实际的值
对于index block
和metaindex block
来说, num_restart_interval
都是1也就是说所有shared_bytes = 0
, 而对于data block
来说, 是受options_.num_restart_interval
控制的, 默认是16, 也就是16个key-value
会进行共享编码。
footer 格式
footer
保存在sstable
文件最后,其保存数据及其格式如下图所示:
其中分别保存了metaindex_handle
和index_handle
对象, 可以通过这两个对象来访问sstable
里metaindex block
和index block
。
metaindex block 格式
metaindex block
当前只保存了filter name
及filter_block_handle
, 具体格式参见下图:
通过filter_block_handle
可以访问sstable
里的filter block
。
index block 格式
index block
是用来索引sstable
里的data block
, 具体格式如下:
其中
last_key
是其所对应的data block
所保存的数据中最后一个key-value
的key
值, 由于memtable
里值是有序的, 所以last key
也是有序的offset
和size
则分别是对应的data block
在sstable
的偏移值和大小。
filter block 格式
filter block
和data block
不一样,不是利用block
的结构来存储数据的, 而是使用下面的格式:
其中
crc
是整个filter block
除了crc
部分的校验type
, 表示是否压缩,存储的是nocompressed
kFilterBaseLog
, 表示是对多大数据创建filter
, 默认是2KB (2^kFilterBaseLg
)array_offset
, 表示offset
数组在filter block
里的偏移offset[N]
和filter[N]
,offset
数组分别记录的是对应filter
数组在filter block
的偏移