LevelDB深入浅出之整体架构
LevelDB是一个可持久化的KV数据库引擎,由Google传奇工程师Jeff Dean和Sanjay Ghemawat开发并开源。无论从设计还是代码上都可以用精致优雅来形容,非常值得细细品味。本文将从整体特性、架构和使用等几方面做一个解释,试图通过本文的介绍让大家对LevelDB有个整体的认识并能够使用。
设计思路
做存储的同学都很清楚,对于普通机械磁盘顺序写的性能要比随机写大很多。比如对于15000转的SAS盘,4K写IO, 顺序写在200MB/s左右,而随机写性能可能只有1MB/s左右。而LevelDB的设计思想正是利用了磁盘的这个特性。
LevelDB的数据是存储在磁盘上的,采用LSM-Tree的结构实现。LSM-Tree将磁盘的随机写转化为顺序写,从而大大提高了写速度。为了做到这一点LSM-Tree的思路是将索引树结构拆成一大一小两颗树,较小的一个常驻内存,较大的一个持久化到磁盘,他们共同维护一个有序的key空间。写入操作会首先操作内存中的树,随着内存中树的不断变大,会触发与磁盘中树的归并操作,而归并操作本身仅有顺序写。如下图所示:
图1 数据存储原理图中2个红色区域是要进行归并的数据块,计算出顺序后会存储到如图下面的磁盘空间,而这种存储方式是追加式的,也就是顺序写入磁盘。
随着数据的不断写入,磁盘中的树会不断膨胀,为了避免每次参与归并操作的数据量过大,以及优化读操作的考虑,LevelDB将磁盘中的数据又拆分成多层,每一层的数据达到一定容量后会触发向下一层的归并操作,每一层的数据量比其上一层成倍增长。这也就是LevelDB的名称来源。主要特性
下面是LevelDB官方对其特性的描述,主要包括如下几点:
key和value都是任意长度的字节数组;entry(即一条K-V记录)默认是按照key的字典顺序存储的,当然开发者也可以重载这个排序函数;提供的基本操作接口:Put()、Delete()、Get()、Batch();支持批量操作以原子操作进行;可以创建数据全景的snapshot(快照),并允许在快照中查找数据;可以通过前向(或后向)迭代器遍历数据(迭代器会隐含的创建一个snapshot);自动使用Snappy压缩数据;可移植性;编译和使用
LevelDB的编译也是比较简单的,可以从官网直接克隆代码(请自己找下载地址,sx百度提示广告)。具体操作步骤如下(可以参考源代码中的README文件):

完成上述几步,就可以编译出一个静态库、一个动态库和一些测试程序。我们可以自己写一个测试代码进行测试。比如我们在leveldb目录下面创建一个test目录, 然后将静态库libleveldb.a拷贝进来,然后在其中创建一个名为test.cpp的文件,文件内容如下:

测试程序功能很简单,就是向KV数据库添加1000个KV数据。后续我们还会用到这个程序,这里了解一下就行。具体通过如下命令可以编译成可执行程序。
g++ -o leveldbTest test.cpp libleveldb.a -lpthread -lsnappy
如果运行一下该程序,可以看到在当前目录会生成一个名为testbd1的目录,其中的内容如下所示:
图2 LevelDB的主要文件整体结构
对LevelDB有一个整体的认识之后,我们分析一下它的架构。这里面有一个重要的概念(或者模块)需要理解,分别是内存数据的Memtable,分层数据存储的SST文件,版本控制的Manifest、Current文件,以及写Memtable前的WAL。这里简单介绍各个组件的作用和在整个结构中的位置,更详细的介绍本号将另外写文章进行介绍。在介绍之前,我们先看一下整体架构示意图:
图3 LevelDB整体架构图如图3所示,对于写数据,接口会同时写入内存表(MemTable)和日志中。当内存表达到阈值时,内存表冻结,变为Immutable MemTable,并将数据写入SST表中,其中SST表时在磁盘上的文件。下面是涉及到主要模块的简单介绍:
Memtable:内存数据结构,跳表实现,新的数据会首先写入这里;Log文件:写Memtable前会先写Log文件,Log通过append的方式顺序写入。Log的存在使得机器宕机导致的内存数据丢失得以恢复;Immutable Memtable:达到Memtable设置的容量上限后,Memtable会变为Immutable为之后向SST文件的归并做准备,顾名思义,Immutable Mumtable不再接受用户写入,同时会有新的Memtable生成;SST文件:磁盘数据存储文件。分为Level 0到Level N多层,每一层包含多个SST文件;单层SST文件总量随层次增加成倍增长;文件内数据有序;其中Level0的SST文件由Immutable直接Dump产生,其他Level的SST文件由其上一层的文件和本层文件归并产生;SST文件在归并过程中顺序写生成,生成后仅可能在之后的归并中被删除,而不会有任何的修改操作。Manifest文件: Manifest文件中记录SST文件在不同Level的分布,单个SST文件的最大最小key,以及其他一些LevelDB需要的元信息。Current文件: 从上面的介绍可以看出,LevelDB启动时的首要任务就是找到当前的Manifest,而Manifest可能有多个。Current文件简单的记录了当前Manifest的文件名,从而让这个过程变得非常简单。写流程简析
了解了整体流程和架构后,我们分析两个基本的流程,也就是LevelDB的写流程和读流程。我们这里首先分析一下写流程,毕竟要先有数据后才能读数据。
LevelDB的写操作包括设置key-value和删除key两种。需要指出的是这两种情况在LevelDB的处理上是一致的,删除操作其实是向LevelDB插入一条标识为删除的数据。下面我们先看一下LevelDB插入值的整体流程,具体如图4所示。
图4 LevelDB写数据流程具体代码请自行阅读,本文不贴过多的代码。这里需要重点说明的是DBImpl::Write函数,如图5是该函数的代码片段,从这段代码中我们可以很清楚的看到数据被分别写入日志和内存2个地方。其它代码都比较简单,大家请自行对照流程图阅读。
图5 DBImpl::Write代码片段读流程简析
读流程要比写流程简单一些,核心代码逻辑如图6所示。首先,生成内部查询所用的Key,该Key是由用户请求的UserKey拼接上Sequence生成的。其中Sequence可以用户提供或使用当前最新的Sequence,LevelDB可以保证仅查询在这个Sequence之前的写入。然后,用生成的Key,依次尝试从 Memtable,Immtable以及SST文件中读取,直到找到。
图6 LevelDB读流程从SST文件中查找需要依次尝试在每一层中读取,得益于Manifest中记录的每个文件的key区间,我们可以很方便的知道某个key是否在文件中。Level0的文件由于直接由Immutable Dump 产生,不可避免的会相互重叠,所以需要对每个文件依次查找。对于其他层次,由于归并过程保证了其互相不重叠且有序,二分查找的方式提供了更好的查询效率。可以看出同一个Key出现在上层的操作会屏蔽下层的。也因此删除Key时只需要在Memtable压入一条标记为删除的条目即可。被其屏蔽的所有条目会在之后的归并过程中清除。好了,今天就先到这,后续我们在深入的介绍LevelDB的其它部分的,并且逐步深入,理解其内部的精髓。
LevelDB深入浅出之整体架构的更多相关文章
- LevelDB系列之整体架构
LevelDb本质上是一套存储系统以及在这套存储系统上提供的一些操作接口.为了便于理解整个系统及其处理流程,我们可以从两个不同的角度来看待LevleDb:静态角度和动态角度.从静态角度,可以假想整个系 ...
- 【深入浅出jQuery】源码浅析--整体架构
最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...
- 【深入浅出jQuery】源码浅析--整体架构(转)
最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...
- LevelDB 整体架构
[LevelDB 整体架构] 从图中可以看出,构成LevelDb静态结构的包括六个主要部分:内存中的MemTable和Immutable MemTable以及磁盘上的几种主要文件:Curren ...
- jQuery整体架构源码解析(转载)
jQuery整体架构源码解析 最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性, ...
- jQuery整体架构源码解析
最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...
- 蓝牙 BLE 协议学习: 001-BLE协议栈整体架构
背景 在深入BLE协议帧之前,我们先看一下BLE协议栈整体架构. 转载自:<深入浅出低功耗蓝牙(BLE)协议栈> 架构 如上图所述,要实现一个BLE应用,首先需要一个支持BLE射频的芯片, ...
- 大家都能看得懂的源码(一)ahooks 整体架构篇
本文是深入浅出 ahooks 源码系列文章的第一篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 第一篇主要介绍 ahooks 的背景以及整体架构. React h ...
- Underscore 整体架构浅析
前言 终于,楼主的「Underscore 源码解读系列」underscore-analysis 即将进入尾声,关注下 timeline 会发现楼主最近加快了解读速度.十一月,多事之秋,最近好多事情搞的 ...
随机推荐
- Maven 三种archetype说明--转载
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 原文链接:https://blog.csdn.net/cx1110162/article/deta ...
- 分布式爬虫-bilibili评论
实属课程需要,不然早就放弃在半路了.维持了断续半个多月的 bug 调试,突然就实现了.很是欣慰.网上关于分布式爬虫的都是一些介绍,实战的不多并且都很相似,说的云来雾去的,只是项目的流程.可能是项目一路 ...
- 页面Header自适应屏幕
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8& ...
- Codeforces 1187 F - Expected Square Beauty
F - Expected Square Beauty 思路:https://codeforces.com/blog/entry/68111 代码: #pragma GCC optimize(2) #p ...
- 求输入数字的阶乘 及加和 #s=1!+2!+3!+…..+n!
#s=1!+2!+3!+…..+n! from functools import reduce def factorial(n): result=0 for i in range(1,n+1): re ...
- 2018 CERC 混合博弈
N堆石子 先手最多拿A个 后手最多拿B个 每次都至少要拿一个 谁先取完谁赢 如果A和B相等直接就是一个bash博弈 如果一个石堆的石子数少于min(A,B) 则是个nim游戏 我们先讨论只有N=1且A ...
- Java类的调用(实现数组排序和遍历输出)
两个类文件: Test1.java /** *同一个src下的两个类,主类在这里,调用另一个文件里的Public类 */ import java.lang.*; public class Test1 ...
- tomcat web的URL解析(web.xml)
1.一个tomcat可以配置多个host: 2.一个host可以包含多个应用:context: 3.一个应用可以包含多个servlet:servlet-path; 4.一个servlet可以包含多个r ...
- Nginx防盗链模块ngx_http_referer_module
ngx_http_referer_module⽤用来阻⽌止Referer⾸首部⽆无有效值的请求访问,可防⽌止盗链指令:12.1 valid_referers定义 referer ⾸首部的合法可⽤用值, ...
- redis过期机制及排行榜
redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略.redis 提供 6种数据淘汰策略: volatile-lru:从已设置过期时间的数据集(server.db[i].expire ...