NoSQL数据库知多少--KV存储数据库LevelDB

 👩‍💻博客主页:大家好我是poizxc2014的博客主页

✨欢迎关注🖱点赞🎀收藏⭐留言✒
📖个人主页:poizxc2014的博客_CSDN博客-数据库,mysql,java领域博主

💻首发时间:🎞2022年05月02日🎠

🎨你做三四月的事,八九月就会有答案,一起加油吧
🔥💖🔮😘🔏🀄🎧如果觉得博主的文章还不错的话,👍请三连支持一下博主哦🤞

如果觉得博主的文章还不错的话,请三连支持一下博主哦

最后的话,在很多方面还做的不好的地方,欢迎大佬指正,一起学习哦,冲冲冲 

背景

Log Structured Merge Tree,简称LSM-Tree。

2006年,Google发表了 BigTable的论文。这篇论文提到BigTable单机上所使用的数据结构就是LSM-Tree。

很多存储产品使用LSM-Tree作为数据结构,比如 Apache HBase,Apache Cassandra,MongoDB的Wired Tiger存储引擎,LevelDB存储引擎,RocksDB存储引擎等。 简单地说,LSM-Tree的设计目标是提供比传统的B-Tree/B+Tree更好的写性能。

LSM-Tree通过将磁盘的随机写转化为顺序写来提高写性能 ,而付出的代价就是牺牲部分读性能、写放大(B-Tree/B+Tree 同样有写放大的问题)。

如何优化写性能

如果我们对写性能特别敏感,我们最好怎么做?

Append only:所有写操作都是将数据添加到文件末尾。这样顺序写的性能是最好的,大约等于磁盘的理论速度(无论是SSD还是HDD,顺序写性能都要明显由于随机写性能)。

注:GFS就是采用append only。

但是append only的方式会带来一些问题:

1、不支持有序遍历。

2、需要垃圾回收(清理过期数据)。

所以,纯粹的append only方式只能适用于一些简单的场景:

1、存储系统的 WAL。

2、能知道明确的offset的查询,比如 Bitcask。

注:HDFS,Kafka都是利用append only提高写效率的。

如何优化读性能

如果我们对读性能特别敏感,一般我们有两种方式:

有序存储,比如B-Tree/B+Tree。但是B-Tree/B+Tree会导致随机写。

哈希存储:不支持有序遍历,适用范围有限。

读写性能的权衡

如何获得接近append only的写性能,而又能拥有不错的读性能呢?以LevelDB/RocksDB为代表的LSM-Tree存储引擎给出了一个参考答案。 LevelDB 的写操作(Put/Delete/Write)主要由两步组成:

1、写日志(WAL,顺序写)。

2、写MemTable(内存中的SkipList)。

所以,正常情况下,LevelDB的写速度非常快。 内存中的 MemTable写满后,会转换为Immutable MemTable,然后被后台线程compact成按key有序存储的SSTable(顺序写)。

SSTable按照数据从新到旧被组织成多个层次(上层新下层旧),点查询(Get)的时候从上往下一层层查找,所以LevelDB的读操作可能会有多次磁盘IO(LevelDB通过table cache、block cache和bloom filter等优化措施来减少读操作的I/O次数)。

后台线程的定期compaction负责回收过期数据和维护每一层数据的有序性。在数据局部有序的基础上,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效率都有直接的帮助。

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一样。

sstable

DB数据持久化文件,内部key是有序的,文件内部前面是数据,后面是索引元数据。

sstable文件之间逻辑上是分层的,LevelDB最大支持7层。

SequenceNumber

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

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。

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

};

Key

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

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。

对比

RocksDB与LevelDB对比:

1、增加了column family,这样有利于多个不相关的数据集存储在同一个db中,因为不同column family的数据是存储在不同的sst和memtable中,所以一定程度上起到了隔离的作用。

2、采用了多线程同时进行compaction的方法,优化了compact的速度。

3、增加了merge operator,优化了modify的效率。

4、将flush和compaction分开不同的线程池,能有效的加快flush,防止stall。

5、增加了对write ahead log(WAL)的特殊管理机制,这样就能方便管理WAL文件,因为WAL是binlog文件。

架构

上图简单展示了 LevelDB 的整体架构。

MemTable:内存数据结构,具体实现是SkipList。接受用户的读写请求,新的数据会先在这里写入。

Immutable MemTable:当MemTable的大小达到设定的阈值后,会被转换成 Immutable MemTable,只接受读操作,不再接受写操作,然后由后台线程flush到磁盘上—这个过程称为minor compaction。

Log:数据写入MemTable之前会先写日志,用于防止宕机导致MemTable的数据丢失。一个日志文件对应到一个MemTable。

SSTable:Sorted String Table。分为level-0到level-n多层,每一层包含多个SSTable,文件内数据有序。除了level-0之外,每一层内部的SSTable的key范围都不相交。

Manifest:Manifest文件中记录SSTable在不同level的信息,包括每一层由哪些SSTable,每个SSTable的文件大小、最大key、最小key等信息。

Current:重启时,LevelDB会重新生成Manifest,所以Manifest文件可能同时存在多个,Current记录的是当前使用的Manifest文件名。

TableCache:TableCache用于缓存SSTable的文件描述符、索引和filter。

BlockCache:SSTable的数据是被组织成一个个block。BlockCache用于缓存这些block(解压后)的数据。

原理

写流程

LevelDB的写操作包括设置key-value和删除key两种。需要指出的是这两种情况在LevelDB的处理上是一致的,删除操作其实是向LevelDB插入一条标识为删除的数据。下面我们先看一下LevelDB插入值的整体流程,具体如图所示。 

读流程

读流程要比写流程简单一些,核心代码逻辑如图所示。

首先,生成内部查询所用的Key,该Key是由用户请求的UserKey拼接上Sequence生成的。其中Sequence可以用户提供或使用当前最新的Sequence,LevelDB可以保证仅查询在这个Sequence之前的写入。然后,用生成的Key,依次尝试从 Memtable,Immtable以及SST文件中读取,直到找到。

从SST文件中查找需要依次尝试在每一层中读取,得益于Manifest中记录的每个文件的key区间,我们可以很方便的知道某个key是否在文件中。Level0的文件由于直接由Immutable Dump 产生,不可避免的会相互重叠,所以需要对每个文件依次查找。对于其他层次,由于归并过程保证了其互相不重叠且有序,二分查找的方式提供了更好的查询效率。

可以看出同一个Key出现在上层的操作会屏蔽下层的。也因此删除Key时只需要在Memtable压入一条标记为删除的条目即可。被其屏蔽的所有条目会在之后的归并过程中清除。

应用场景

Level适用于写多读少的场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值