Leveldb源码分析--1
【前言:看了一点oceanbase,没有意志力继续坚持下去了,暂时就此中断,基本上算把master看完了,比较重要的update server和merge server代码却没有细看。中间又陆续研究了hadoop的源码,主要是name node和写入pipeline。主要的目的是想看看name node对namespace的管理,以及hadoop在写入操作时,client、data node和name node之间是如何交互的,特别是涉及到namenode的,以及写入出现错误时的处理逻辑。没办法,和分布式存储扯不开了。
其后看到了Leveldb,除去测试部分,代码不超过1.5w行。这是一个单机k/v存储系统,决定看完它,并把源码分析完整的写下来,还是会很有帮助的。我比较厌烦太复杂的东西,而Leveldb的逻辑很清晰,代码不多、风格很好,功能就不用讲了,正合我的胃口。 BTW,分析Leveldb也参考了网上一些朋友写的分析blog,如巴山独钓。】
Leveldb源码分析
2012年1月21号开始研究下leveldb的代码,Google两位大牛开发的单机KV存储系统,涉及到了skip list、内存KV table、LRU cache管理、table文件存储、operation log系统等。先从边边角角的小角色开始扫。
不得不说,Google大牛的代码风格太好了,读起来很舒服,不像有些开源项目,很快就看不下去了。
开始之前先来看看Leveldb的基本框架,几大关键组件,如图1-1所示。
图1-1
Leveldb是一种基于operation log的文件系统,是Log-Structured-Merge Tree的典型实现。LSM源自Ousterhout和Rosenblum在1991年发表的经典论文<<The Design and Implementation of a Log-Structured File System >>。
由于采用了op log,它就可以把随机的磁盘写操作,变成了对op log的append操作,因此提高了IO效率,最新的数据则存储在内存memtable中。
当op log文件大小超过限定值时,就定时做check point。Leveldb会生成新的Log文件和Memtable,后台调度会将Immutable Memtable的数据导出到磁盘,形成一个新的SSTable文件。SSTable就是由内存中的数据不断导出并进行Compaction操作后形成的,而且SSTable的所有文件是一种层级结构,第一层为Level 0,第二层为Level 1,依次类推,层级逐渐增高,这也是为何称之为LevelDb的原因。
1 一些约定
先说下代码中的一些约定:
1.1 字节序
Leveldb对于数字的存储是little-endian的,在把int32或者int64转换为char*的函数中,是按照先低位再高位的顺序存放的,也就是little-endian的。
1.2 VarInt
把一个int32或者int64格式化到字符串中,除了上面说的little-endian字节序外,大部分还是变长存储的,也就是VarInt。对于VarInt,每byte的有效存储是7bit的,用最高的8bit位来表示是否结束,如果是1就表示后面还有一个byte的数字,否则表示结束。直接见Encode和Decode函数。
在操作log中使用的是Fixed存储格式。
1.3 字符比较
是基于unsigned char的,而非char。
2 基本数据结构
别看是基本数据结构,有些也不是那么简单的,像LRU Cache管理和Skip list那都算是leveldb的核心数据结构。
2.1 Slice
Leveldb中的基本数据结构,它包括length和一个指向外部字节数组的指针。和string一样,允许字符串中包含’\0’。
提供一些基本接口,可以把const char*和string转换为Slice;把Slice转换为string,取得数据指针const char*。
2.2 Status
Leveldb 中的返回状态,将错误号和错误信息封装成Status类,统一进行处理。并定义了几种具体的返回状态,如成功或者文件不存在等。
为了节省空间Status并没有用std::string来存储错误信息,而是将返回码(code), 错误信息message及长度打包存储于一个字符串数组中。
成功状态OK 是NULL state_,否则state_ 是一个包含如下信息的数组:
state_[0..3] == 消息message长度
state_[4] == 消息code
state_[5..] ==消息message
2.3 Arena
Leveldb的简单的内存池,它所作的工作十分简单,申请内存时,将申请到的内存块放入std::vector blocks_中,在Arena的生命周期结束后,统一释放掉所有申请到的内存,内部结构如图2.3-1所示。
图2.3-1
Arena主要提供了两个申请函数:其中一个直接分配内存,另一个可以申请对齐的内存空间。Arena没有直接调用delete/free函数,而是由Arena的析构函数统一释放所有的内存。
应该说这是和leveldb特定的应用场景相关的,比如一个memtable使用一个Arena,当memtable被释放时,由Arena统一释放其内存。
2.4 Skip list
Skip list(跳跃表)是一种可以代替平衡树的数据结构。Skip lists应用概率保证平衡,平衡树采用严格的旋转(比如平衡二叉树有左旋右旋)来保证平衡,因此Skip list比较容易实现,而且相比平衡树有着较高的运行效率。
从概率上保持数据结构的平衡比显式的保持数据结构平衡要简单的多。对于大多数应用,用skip list要比用树更自然,算法也会相对简单。由于skip list比较简单,实现起来会比较容易,虽然和平衡树有着相同的时间复杂度(O(logn)),但是skip list的常数项相对小很多。skip list在空间上也比较节省。一个节点平均只需要1.333个指针(甚至更少),并且不需要存储保持平衡的变量。
如图2.4-1所示。
图2.4-1
在Leveldb中,skip list是实现memtable的核心数据结构,memtable的KV数据都存储在skip list中。
2.5 Cache
Leveldb内部通过双向链表实现了一个标准版的LRUCache,先上个示意图,看看几个数据之间的关系,如图2.5-1。
图2.5-1
接下来说说Leveldb实现LRUCache的几个步骤,很直观明了。
S1 定义一个LRUHandle结构体,代表cache中的元素。它包含了几个主要的成员:
void* value; 这个存储的是cache的数据;
void (*deleter)(const Slice&, void* value);这个是数据从Cache中清除时执行的清理函数;
后面的三个成员事关LRUCache的数据的组织结构:
> LRUHandle *next_hash;
指向节点在hash table链表中的下一个hash(key)相同的元素,在有碰撞时Leveldb采用的是链表法。最后一个节点的next_hash为NULL。
> LRUHandle *next, *prev;
节点在双向链表中的前驱后继节点指针,所有的cache数据都是存储在一个双向list中,最前面的是最新加入的,每次新加入的位置都是head->next。所以每次剔除的规则就是剔除list tail。
S2 Leveldb自己实现了一个hash table:HandleTable,而不是使用系统提供的hash table。这个类就是基本的hash操作:Lookup、Insert和Delete。Hash table的作用是根据key快速查找元素是否在cache中,并返回LRUHandle节点指针,由此就能快速定位节点在hash表和双向链表中的位置。
它是通过LRUHandle的成员next_hash组织起来的。
HandleTable使用LRUHandle **list_存储所有的hash节点,其实就是一个二维数组,一维是不同的hash(key),另一维则是相同hash(key)的碰撞list。
每次当hash节点数超过当前一维数组的长度后,都会做Resize操作:
LRUHandle** new_list = new LRUHandle*[new_length];
然后复制list_到new_list中,并删除旧的list_。
S3 基于HandleTable和LRUHandle,实现了一个标准的LRUcache,并内置了mutex保护锁,是线程安全的。
其中存储所有数据的双向链表是LRUHandle lru_,这是一个list head;
Hash表则是HandleTable table_;
S4 ShardedLRUCache类,实际上到S3,一个标准的LRU Cache已经实现了,为何还要更近一步呢?答案就是速度!
为了多线程访问,尽可能快速,减少锁开销,ShardedLRUCache内部有16个LRUCache,查找Key时首先计算key属于哪一个分片,分片的计算方法是取32位hash值的高4位,然后在相应的LRUCache中进行查找,这样就大大减少了多线程的访问锁的开销。
LRUCache shard_[kNumShards]
它就是一个包装类,实现都在LRUCache类中。
2.6 其它
此外还有其它几个Random、Hash、CRC32、Histogram等,都在util文件夹下,不仔细分析了。
PS:CSDN的编辑器,真心不好用。
Leveldb源码分析--1相关推荐
- leveldb源码分析:数据插入续(跳表)
leveldb数据的插入-跳表 本文主要是接着上一篇文章,继续深入探索Write函数调用插入之后的流程. status = WriteBatchInternal::InsertInto(updates ...
- leveldb源码分析:Open启动流程
leveldb概述 Leveldb 是一个持久化的KV存储系统,主要将大部分数据存储在磁盘上,在存储数据的过程中,根据记录的key值有序存储,当然使用者也可以自定义Key大小比较函数,一个leveld ...
- leveldb源码分析:数据查询
leveldb数据查询 查询的示例代码如下: string res; status = db->Get(ReadOptions(), "KeyNameExample", &a ...
- LevelDB 源码分析
本文基于leveldb 1.9.0代码. 整体架构 如上图,leveldb的数据存储在内存以及磁盘上,其中: memtable:存储在内存中的数据,使用skiplist实现. immutable me ...
- leveldb源码分析:数据插入与删除(Put与Delete)
leveldb数据的插入与获取 leveldb提供的数据的交互接口如下: // Set the database entry for "key" to "value&qu ...
- Leveldb源码分析--6
5 操作Log 2 5.3 读日志 日志读取显然比写入要复杂,要检查checksum,检查是否有损坏等等,处理各种错误. 5.3.1 类层次 先来看看读取涉及到的类图,如图5.3-1. Reader主 ...
- Leveldb源码分析--3
4 Memtable之1 Memtable是leveldb很重要的一块,leveldb的核心之一.我们肯定关注KV数据在Memtable中是如何组织的,秘密在Skip list中. 4.1 用途 在L ...
- Leveldb源码分析--15
9 LevelDB框架之2 9.4 版本控制 当执行一次compaction后,Leveldb将在当前版本基础上创建一个新版本,当前版本就变成了历史版本.还有,如果你创建了一个Iterator,那么该 ...
- Leveldb源码分析--22
14 DB的查询与遍历之2 14.4 DBIter Leveldb数据库的MemTable和sstable文件的存储格式都是(user key, seq, type) => uservalue. ...
最新文章
- 会数据分析的人别再低调了,我怕你会因此错失100万奖金
- 从属关系mysql_关系型数据库基础概念:MySQL系列之开篇
- 模拟京东快递单号的查询效果
- python小课文件_[Python]小甲鱼Python视频第028课(文件:因为懂你,所以永恒)课后题及参考解8...
- python写接口自动化需要rsa加密_RSA加密,请问如何用Python实现该加密过程
- React-Native填坑之TextInput value属性
- Git学习笔记01--初始化设置
- [ZJOI2007]时态同步 树形DP
- ajax请求 304解决方案:
- nmap中文使用手册
- python登录界面源码_基于Python的自媒体小助手---登录页面的实现代码
- scrapy异步写入mysql_scrapy之异步写入数据库
- winNTsetup安装器安装系统教程
- 共享计算机桌面需要密码,win10局域网共享文件需要输密码怎么办?_win10访问共享文件需要密码的解决办法-爱纯净...
- ARRL在线电台日记(LOTW)申请
- 羽毛球高远球技术动作要领解码
- STM32内部RAM在线调试配置方法及详细说明(基于Keil开发工具)
- 将apk和所需库文件编译打包到system/priv-app路径下
- IntelliJ IDEA(2017)安装和破解。
- 安卓(调试)有线投屏、wifi投屏到电脑笔记