前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >leveldb实现分析

leveldb实现分析

作者头像
腾讯Bugly
发布2019-01-30 16:21:58
2K0
发布2019-01-30 16:21:58
举报
文章被收录于专栏:腾讯Bugly的专栏腾讯Bugly的专栏

| 导语  leveldb是google开源的单机key-value存储引擎。基于Log-Structured-Merge Tree的实现。本文先介绍leveldb的总体架构,然后从各个基本操作出发,介绍leveldb的底层实现细节。

一、leveldb的特点

1.key和value可以是任意长度的字节数组 2.数据在磁盘上按key有序存储,调用者可以提供比较函数 3.提供了基本的操作:Put(key,value), Get(key), Delete(key)。支持多个基本操作组合一次批量的原子操作。 4.支持数据库的全景快照。并在此基础上做数据查询。 5.灵活iteration 支持前向遍历,后向遍历,区间范围遍历。 6.数据在磁盘自动使用Snappy压缩存储。

二、leveldb的总体架构

[ leveldb ]

1.Memtable:内存中的数据结构,主要是跳跃表(skipList)的实现。写key-value的操作的时候会把数据写到这里。

2.log文件:写操作的时候会通过顺序写的方式优先写到这个文件,然后再写入上面的memtable。避免机器宕机的时候,内存数据丢失。所以写操作,实际上是一次磁盘操作+一次内存操作。

3.Immutable Memtable:写入数据的时候,会先去检查Memtable的当前大小。当到达指定阈值的时候,会把Memtable置为Immutable Memtable。 Immutable Memtable不会允许数据写入。这时候会主动出发一次compaction,将Immutable Memtable转成level 0的sstable。

4.SSTable:磁盘的存储文件。文件分level存放。每个文件内的数据按key有序存储。level 0的文件是由内存中的Immutable Memtable做campaction导出来的。该层文件之间的key范围可能会存在重叠。其他层的sstable是由本省的文件和上一层的文件做归并排序(compaction)导出来的。文件可能在归并后被删除。除了level0,其他层level内不同文件之间key的范围不会存在重叠。

5.manifest文件:磁盘上的文件。记录sstable在各个level的分布,以及每个sstable最大key和最小key。

6.current文件:磁盘上的文件。记录当前的manifest文件。

三、leveldb的写操作

1、 预备知识

1.slice  slice是leveldb自定义的结构体,对string的封装。能够很方便地跟string进行转化。底层很多操作都是以slice为对象。

2.varint编码  varint是一种紧凑表示数字的方式。对于一个int32类型的数字,最少用一个字节表示数字,最多用5个字节表示数字。

[ varint编码 ] 具体编码方式: (1)原始整数二进制从最低位的bit开始每7个bit进行分组。 (2)每个分组的前面添加一个bit。除了最高位的那个分组添加0,其他的添加1。 (3)从最低位的分组到最高位的分组依次写入字符数组。 leveldb底层对一些key的大小使用了varint的编码方式。

2、实际写操作

(1)写入的key,和value按如下进行编码写入到string:

[ 写入key格式 ]

  • sequence是每次写入分配的全局递增序列号。如果这次写入的是count个key-value,则下次sequence要增加count然后再分配出去。
  • type是固定一个字节的枚举值。只有kTypeValue和kTypeDeletion,用于标识这是一个写入还是删除。可见删除,并不是真正的删掉key而是把key打个标记。后续的compaction会真正删掉key。
  • 前面提到过的leveldb支持批量写入。一次批量写入多少个key-valuve,就有对于的count。以及组织成上图的string。

(2)writebatch入队

[ 任务队列 ]

  • writer结构体除了包含WriterBatch指针外,还包含了sync(同步写盘),done(是否以及完成写入操作的标志)
  • 之后去每次通过条件变量等待被唤醒,然后去检查是否队首的元素是之前自己push进去的元素。

(3)检查memtable是否由可用空间写入

  • 如果level0的个数达到一定阈值,则sleep1000微妙,只发生一次
  • 否则查看memtable当前大小是否小于指定阈值,如果小于,说明有空空间可以写入。
  • 第二步检查发现memtable没有空间的话,就去检查level0文件个数。小于一定阈值,进行等待memtable compaction完成。
  • 到了这里,说明level0是可以增加文件的。就把memtable置为Immutable memtable,然后出发memtable compaction。之后新建memtable和对应的log文件。

(4)队列取任务。

  • 去第一个writer的时候,同时遍历后面的writer,然后合并到(1)的的格式。
  • 去后面writer合并的时候会检查所有字节数的上限,同时如果当前writer的sync标志是true,但是第一个writer是false的时候,不会再继续合并后面的writer。

(5)写入log文件

[ log格式 ]

  • 上面组装出来的slice,会以block(32KB)的大小进行切割,每次切割完得到一个type,用以标识这部分在原来slice的位置。
  • type是个枚举值:kFullType,kFirstType,kLastType,kMiddleType。
  • 切割完组织成一个record,然后再进行刷盘
  • recode的格式是:4字节的crc校验码 | 2字节的长度 | 1字节的type | 被分割的数据

(6)插入memtable

  • 插入memtable的格式:

[ key格式 ]

  • 插入的过程是用internal key做比较,默认按user key升序,相同user key下按sequcence降序,找到插入的位置
  • memtable的底层实现是skiplist:

[ skiplist ]

  • 跳跃表是在链表基础上扩展的数据结构。每个node节点,除了存key以外,还会存一个指针数组。数组的大小是这个节点的高度,数组的值指向下一个node节点。随机生成一个小于最大值的数作为当前要插入节点的高度。
  • 每次插入从最高层去比较找到要插入点的前驱node指针。

四、leveldb的版本管理

[ 版本管理 ]

  1. leveldb版本管理的数据结构是由一个循环的双向链表组成。每个元素是一个version。
  2. version具体指什么?
  • 之前提到的memtable是在内存里面的,之后会经过compaction落到磁盘文件,直接到level0的sst文件。level0的文件里面的key-value做归并排序得到后面level的文件。
  • 每个level里面的每个文件存储按key有序排列。
  • 所谓verison,就是这些所有level的sst文件组成的集合。

3.每次做完compaction后,就是去组成新的version数据结构,然后插到head元素的后面。

[ version ]

1.version是一堆sst的集合,每个sst在version里面如何表示?

  • 上述的数据结构用来描述一个sst文件,由于key-value在文件内有序,所以只需记录最大key和最小key。
  • number是文件的编号,全局递增,通过number就可以找到一个sst文件(dbname/[0-9]+.(sst|ldb))
  • refs是这个结果体的引用技术,为0是这个结构体变量会被delete掉。

[ 生成新的version ]

1.version是如何生成的?

  • 每次做完compaction生成新的文件,会生成一个versionEdit的数据结构,
  • 这个数据结构,描述本次compaction做完后要新增的FileMetaData,以及要被删掉的文件编号。
  • 通过这个builder的类,可以实现Version1+VersionEdit=Version2。由此来产生新的version。

2.有了version就可以来确定一个快照来。

  • 快照的核心点,是确定一个sequence,同时对当前version的引用计数+1,防止这个version被删掉。
  • 有了这个sequence,比如同一个key由多个版本,大于这个sequence的用户看不到,小于sequence的,取最大的,作为用户查询结果。

3.目前我们对leveldb的版本管理做了一个大概的描述,为后续的compaction做铺垫。

五、leveldb的compaction

leveldb 中的compaction分两种:内存中的memtable数据compact到sst文件以及磁盘中的sst文件做归并排序整到到下一层的sst文件。

1、内存到磁盘的compaction

[ sst文件的物理格式 ]

(1)sst的布局如图

  • DataBlock: 主要用来存放key和value
  • MetaBlcok: 存放关于key-value的元信息,leveldb里面主要用来构建bloom filter,可以挡住一部分无效查询。

[filter 0] [filter 1] [filter 2] [filter N-1] [offset of filter 0] : 4 bytes [offset of filter 1] : 4 bytes [offset of filter 2] : 4 bytes [offset of filter N-1] : 4 bytes [offset of beginning of offset array] : 4 bytes lg(base) : 1 byte

bloom filter的原理是对集合里面的每个元素,用k个哈希函数,算出k个位置,然后组成一个字节数组。

leveldb中每对20k的数据进行一次bloom filter的计算,[filter i]对应算出来的结果,[offset of filter 0] 是对应filter的偏移量。

  • MetaIndex Block:key是filter的名称,主要用来存放meta block的索引,这个索引是由block的偏移量和大小组成。
  • Index Block:key是对应块的最大key,value用来存放data block的索引,这个索引是由block的偏移量和大小组成。
  • footer:主要存放上面两个block的索引。

[ 每个block格式 ]

(2)每个block的逻辑分布一样:

  • 每个entry对应到key,value。key采用压缩编码的方式: 跟上一个key的共享部分大小+非共享部分大小+value大小+key的差异部分+value的值。
  • 第一个entry保存的是全量的key,这个叫重启点
  • 放在restart数组里面,后面的key没个一定数量建立一个重启点,放到restart数组里面。
  • 最后的num是restart的个数。
  • 组成上面的bock内容后,会对内容做snap压缩,对metablock的内容不做压缩。最后的type表明压缩方式。
  • 需要注意的是,生成sst文件后,leveldb会把该文件里面的index信息放在缓存中。主要包含indexblock以及meta block的内容。

(3)怎样为从memtable生成的sst文件选择level

  • 如果跟level0的文件有key范围重叠的直接选择level0,
  • 如果没有,则循环去找出level+1跟新生成的文件有key范围重叠,或是level+2的重叠文件总大小大雨一定值的level

2、ss文件直接的compacion

这里指的是当前version 几个sstable之间做归并排序,生成新的sstable,并建立新version的过程。

(1)触发时机:满足一下任意一个条件

  • level0的文件个数太多,超过指定值。(level0 的sst文件是由memmtable做compaction生成的,文件之间的key范围有可能重叠。)
  • 其他level,有一个level总文件的大小超过指定值。(默认level1上限10M,后面每一层是前一层的10倍)
  • 某个文件被查找太多次数,但又没命中。leveldb中每个filemeta中维护一个allowed_seeks。

(2)如何选择做compaction的文件

  • 先找到开始的文件: 1.优先考虑文件个数太多,或是超过指定上限大小的情况。选择该level的第一个lagestkey>compact_pointer(这个是保存上次做compaction所有文件的最大key)的文件。如果没有,直接选择该level的第一个文件。 2.上述1不满足的时候,直接选择allowed_seedk被减到0 的文件,作为开始的文件。
  • 当找到开始文件的时候,要判断一次当前是不是level0。level0比较特殊,文件之间可能会有key范围重叠。所有这时候会把level0中,跟选中文件key范围重叠的文件也加进来。
  • 最终做归并排序的文件要放到下面数据结构的inputs数组里面:
  1. inputs[0] 就是前面找的文件集合。 inputs[1]选取算法:当前level被选中文件的smallkey,lagest_key拿到level+1查找有跟这个范围重叠的文件。
  2. 上述一轮过后,会做一次调整: 重新算出 inputs[0], inputs[1]合一起所有文件的small_key,lagest_key。到level中选出跟这个key范围重叠的文件集合expanded0。 算出expanded0的small_key,lagest_key,并拿到level+1算出新的key重叠文件集合expanded1。 如果expanded1的文件个数跟inputs[1]一样,则用expanded0代替inputs[0],expanded1代替inputs[1]
  3. 上述都做完后算出level+2中,跟上面算出key范围重叠的文件集合放到grandparents_中。

(3)构建迭代器 (4)循环每个key,对于要保留的key,生成sst文件 什么样的key需要做保留?

  • 做compaction之前会取当前最老snapshot的sequence。
  • 遍历key的时候,相同key是按sequence递减的。所以最要保留相同key的seq大于等于snapshotsequence的部分即可。
  • 对于被打了删除标志的key,必须满足key的seq小于snapshot_sequence且后面的level范围包含这个key,才能被保留。
  • 循环期间会有一个判断,如果当前所有遍历过的key在grandparents中重叠范围大于指定的值后,会提前停止compaction。

(5)删除之前做compaction的文件集合,把新的文件集合应用于当前的version(放到level+1),生成新的version。

六、读流程

一个key的读流程:memtable->Immutable Memtable->level0->level1…

  • 到level0这里的时候,会判断包含这个key范围的所有文件。在所有文件内查找。
  • sst table中的查找,会先从缓存中拿出indexhandle 二分查找出所在的block,然后在block里面二分查找restart,最后再找到对应的key
  • bloom filter 会过滤一些无效的key查询

七、恢复流程

每次重启leveldb的时候,会做一次recover

  • 从current文件找到当前的manifest
  • 从manifest读出记录恢复到当前version
  • 找到最小min_log(小于这个num的log都已经明确把memtable导出为sst文件)
  • 大于这个min_log的log文件,读出记录生成memtable。

八、LRUCache设计

[ LRUCache ]

leveldb 把一些数据的索引放到了缓存中。缓存的设计是个多路的lru cache。每个lru cace由HashTable+双向链表来实现。

  • 每个具体的元素由下面的数据结构来描述:
  • 每个LRUCache维护两个双向循环链表:inuse,lru
  • 每个元素维护一个引用计数,刚开始插入的时候为2,放到in_use
  • 每次被擦除时候,引用计数减一,当减到为1的时候放到lru_。

九、内存分配器Arena

leveldb实现一个简单的内存分配器,用于memtable生成一个新节点的时候分配内存。

  • 核心数据结构是std::vector blocks;每个指向1KB数据大小的内存块。
  • 如果要的数据大小大于1KB,则直接分配。如果小于等于1KB,则分出一个1KB大小的内存块放入blocks,然后再在里面分配出去。

参考链接: https://github.com/google/leveldb/blob/master/doc/index.md http://bean-li.github.io/leveldb-sstable/ https://segmentfault.com/a/1190000009707717 http://www.cnblogs.com/haippy/archive/2011/12/04/2276064.html https://yq.aliyun.com/articles/64357?spm=5176.10695662.1996646101.searchclickresult.78696fe4uMnDpN

如果您觉得我们的内容还不错,就请转发到朋友圈,和小伙伴一起分享吧~

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-12-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯Bugly 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、leveldb的特点
  • 二、leveldb的总体架构
  • 三、leveldb的写操作
  • 四、leveldb的版本管理
  • 五、leveldb的compaction
  • 六、读流程
  • 七、恢复流程
  • 八、LRUCache设计
  • 九、内存分配器Arena
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档