leveldb 原理解析

官网
github
自己加了 comment 的github分支

本文是根据https://blog.csdn.net/qq_26499321/article/details/78063856 并加上自己看代码的笔记整理的 。
很多地方有源码信息。可以据此快速定位源码查看。
图尽量符合拿来主义原则
.

概览

LevelDB is a fast key-value storage library written at Google that provides an ordered mapping from string keys to string values.

Features

  • Keys and values are arbitrary byte arrays.
  • Data is stored sorted by key.
  • Callers can provide a custom comparison function to override the sort order.
  • The basic operations are Put(key,value), Get(key), Delete(key).
  • Multiple changes can be made in one atomic batch.
  • Users can create a transient snapshot to get a consistent view of data.
  • Forward and backward iteration is supported over the data.
  • Data is automatically compressed using the Snappy compression library.
  • External activity (file system operations etc.) is relayed through a virtual interface so users can customize the operating system interactions.

LevelDB 是由 Google 开发的 key-value 非关系型数据库存储系统,是基于 LSM(Log-Structured-Merge Tree) 的典型实现,LSM 的原理是:当写数据库时,首先纪录写操作到 log 文件中,然后再操作内存数据库,当达到 checkpoint 时,则写入磁盘,同时删除相应的 log 文件,后续重新生成新的内存数据库和 log 文件。

详见
SSTable and Log Structured Storage

整体结构

todo
如图所示,整个结构包含内存中的 Memtable 和 Imutable(immutable Memtable)。他们用来缓存用户的写入操作。
其它的结构是都存在于硬盘中。
sstable(SST) 是一个个数据表,储存着用户数据,它是不可修改的。sstable 作为落盘的存储结构,每个 sstable 最大 2MB,从宏观来看,它属于分层的结构,即:

  • level 0:最多存储 4 个 sstable
  • level 1:存储不超过 10MB 大小的 sstable
  • level 2:存储不超过 100MB 大小的 sstable
  • level 3 及之后:存储大小不超过上一级大小的 10 倍

之所以这样分层,是为了提高查找效率,也是 LevelDB 名称的由来。当每一层超过限制时,会进行 compaction(见compaction章节) 操作,合并到上一层。

Log 文件(又叫WAL:write ahead log)用来记录db 的更新日志,系统恢复的时候可以使用它恢复未持久化到 SST 中的更新。是追加式顺序写入,速度很快。
Manifest 文件记录了所有 SST 的元信息,包含文件名称、level、最大最小 key等。同时Manifest也记录了未处理的 Log 文件号。 启动后,从此获取 log 文件名,实施恢复。
Current 用来记录最新的Manifest文件名称。因为在运行的过程中 SST 信息和 log 文件都在改变。而Manifest文件本身是不可变的,因此会不断生成新的Manifest文件,而Current文件则用来追踪最新的Manifest文件名。
在这里插入图片描述
SSTables and Log Structured Merge Trees:

  1. On-disk SSTable indexes are loaded into memory
  2. All writes go directly to the MemTable index
  3. Reads check the MemTable first and then the SSTable indexes
  4. Periodically, the MemTable is flushed to disk as an SSTable
  5. Periodically, on-disk SSTables are “collapsed together”

Memtable

Memtable是内存中的一个结构(MemTable::Table)。
底层存储结构是 Skiplist。

SkipList<const char*, KeyComparator> Table

在这里插入图片描述

Skiplist 的 key(也是 entry) 为

memtable key = {klength}{userkey}{tag}{vlength}{value} 
klength = userkey.size()+sizeof(tag)
tag = (SequenceNumber<< 8)|(type)
sizeof(tag) == 8

因此Skiplist的 key 实际上包含了应用插入的 k 和 v 信息。
但是KeyComparator只比较前三项: {klength}{userkey}{tag}
也就是按 user_key升序 - SequenceNumber(数据版本号)降序 - type降序 排列, 同一个 user_key 不同 SequenceNumber 的 entries 会在一起。在查找的时候需要指定user_key 和 SequenceNum,找到的 key 会是小于等于指定 SequenceNum 的最大 SequenceNum的key。也就是符合指定版本条件的最新的 key。这是实现 snapshot 的基础,见 snapshot 章节。

Immutable Memtable

当Memtable大小达到配置的限制时,它就会变成 Immutable Memtable。这两个实际上是一样的数据结构。只是使用了两个不同的指针。Memtable接受写入。 Immutable Memtable不接受写入,只接受读取,并会被后台线程 dump 到 level0的 SSTable。

SSTable 文件(SST)

SSTable is a simple abstraction to efficiently store large numbers of key-value pairs while optimizing for high throughput, sequential read/write workloads.

SSTable是 leveldb 数据库的核心数据结构。每个SSTable都是一个完整的数据表。SSTable 内部最核心的则是数据、索引和过滤器

SSTable的物理结构

在这里插入图片描述

SSTable 由 DataBlock, MetaBlock,MetaIndexBlock,IndexBlock,Footer 这5部分构成。这5部分在逻辑上存储不同的内容,但是物理上存储方式只有3种。其中

  • DataBlock/MetaIndexBlock/IndexBlock 拥有相同的物理结构 leveldb::Block,构造方法 leveldb::BlockBuilder
  • MetaBlock 目前只有一种,物理结构是 FilterBlock, 构造方法 leveldb::FilterBlockBuilder
  • Footer 很简单的一个结构。

它们的逻辑内容分别是

  • DataBlock(Block物理结构), 存储用户数据,kv 信息
  • IndexBlock(Block物理结构), 存储索引信息
  • MetaBlock(FilterBlock物理结构), 存储Filter(布隆过滤器)
  • MetaIndexBlock(Block物理结构), 存储MetaBlock的索引(偏移量)
  • Footer, SST的根部,从这里才能找到其它 Block

下面详细介绍各种结构

Block 物理结构

它对应的是leveldb::Block。
在这里插入图片描述
注意这是物理存储结构,Entry并非一定是用户写入的kv。对于DataBlock来说确实是用户写入的kv,但是对于MetaIndexBlock/IndexBlock来说就不是了。比如对于IndexBlock来说,key存储的是索引key,value存储的是DataBlock的偏移量。

分为3个部分:

  • 前面是一个个Entry为KV记录,其顺序是根据Key值由小到大排列的。
  • 然后是“重启点”(Restart Point)偏移量列表 int32 Restart[N]。为压缩key信息而设置,见下面。Restart[i]是第 i 个重启点Entry的offset,该 Entry.key.shared_bytes=0。Restart[]的末尾是重启点数量。num_restarts=Restart.size()
  • 最后是Tailer,包含一个字节的压缩类型,和4个字节的校验和。当前有不压缩和 Snappy 压缩两种 type。

节省 key 占用空间

由于在 SSTable的所有 DataBlock,以及每个 DataBlock中,kv Entry都是按照 key 有序存储的。因此连续的 Entry 的 key 是很有可能有重合的前缀,如果一个key只记录它与前一个key的“重合前缀长度+不重合后缀内容”,则能节省空间。

Restart[i]记录了第 i 个重启点的offset。为了节省 key 空间,对于offset 范围属于[Restart[i],Restart[i+1])的一批 Entry[], Entry[i+1]只记录了和Entry[i].key 一样的前缀长度 shared_bytes、独有的后缀长度 unshared_bytes,和独有的后缀 key_delta。据此就能构造一个完整的 Entry[i+1].key。

添加 key:

void BlockBuilder::Add(const Slice& key, const Slice& value)

在 block 内查找一个 key: 迭代器(Block::Iter)

Block::Iter 迭代器提供了Next(),Prev(),Seek(key) 这三个主要方法。因此支持对所有的 Entry 进行遍历。以及跳到指定的 key。

  • Iter::Next() :迭代的过程中保存当前迭代位置的 key。Next() 就能根据key和key_delta构造新的 key 了。
  • Iter::Prev():对于Prev()则会麻烦点,需要先找到前面最靠近的一个重启点,再一个个遍历后面的 key,直
  • 7
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值