lmdb简介

lmdb是openLDAP项目开发的嵌入式(作为一个库嵌入到宿主程序)存储引擎。其主要特性有:

  • 基于文件映射IO(mmap)
  • 基于B+树的key-value接口
  • 基于MVCC(Multi Version Concurrent Control)的事务处理
  • 类bdb(berkeley db)的api

实现

底层读写的实现

lmdb的基本思路是使用mmap访问存储,不管这个存储实在内存上还是在持久存储上。

lmdb的所有读取操作都是通过mmap将要访问的文件只读地装载到宿主进程的地址空间,直接访问相应的地址,这减少了硬盘、内核地址控件和用户地址空间之间的拷贝,也简化了平坦的“索引空间”上的实现,因为使用了read-only的mmap,规避了因为宿主程序错误将存储结构写坏的风险。IO的调度由操作系统的页调度机制完成。

而写操作,则是通过write系统调用进行的,这主要是为了利用操作系统的文件系统一致性,避免在被访问的地址上进行同步。

基于MVCC的存储引擎

我们在前面提及,lmdb上的读取操作,直接读取了mmap所装载的内存地址,那么,如果所读取的内容被修改了,不是出现了不一致的结果吗?事实是,lmdb上的所有内容都不会被修改。

lmdb用MVCC处理并发读写访问的问题。其要点在于:

  • 每一个变更对应一个版本
  • 变更发生的时候不修改原来的版本
  • 读者进入的时候会取得一个版本,它只读取这个版本的内容

对于一个树形数据结构,当它的一个节点上发生变更的时候,就创建一个新的节点,在新的节点上容纳这些变更,由于这个节点的父节点也要发生变更(从指向原来的节点变更为指向新的这个节点),那么重复上述过程,即,实际发生变更的节点通往根节点路径上的所有节点都必须重新创建一份,当变更工作完成的时候,我们通过一个原子操作提交这个变更版本。大体是这样一个过程:

如上图所示,每个新的版本就会产生一个新的跟节点,按照上述处理,最终的存储中就会保留历史上所有的版本,当然,所有版本中就包括了当前所有读者所读的版本,因此,变更不会对读者产生任何影响,所以,写可以不被读阻塞。

上面,我们讨论了读的情况,上述方法承诺给每一个读一个一致的版本(就是它进入时所得到的那个版本),但没有承诺给它一个最新的版本,我们考虑在一个事务中,依据一个值变更另一个值的情况,很显然,当我们想要提交变更的时候,很可能我们进入时所得到的版本已经不是最新的,也就是说,在我们的进入和提交之间发生了另一个提交,这种情况下,如果提交了变更就会发生不一致的状况,譬如一个单调递增的计数器,就因此可能“吃掉”多个递增。为了解决这个问题,我们只要在提交时检查我们进入的版本是否最新版本即可,这常常可以通过一个CAS原子操作完成,如果这个操作失败,就重新进入存储,重做整个事务。这样,读也可以不被(可能的)写阻塞。

按照上述描述,我们的存储中保存了所有的历史版本,这是否必要呢?事实上,我们所以保存历史版本,是因为有可能有读者读它,新的读者总是读到最新的版本,老的版本就没有用了,如果一个版本上没有任何读者(和写者),那它就没有必要存在了。我们可以依据上述原理实现旧版本的回收,不过lmdb做了一些改进:

  • 一个基本的事实是,新的读者总是去读最新的版本,因此,保留所有版本的根节点是不必要的,我们只需要保存一个最新版本的根节点和一个用于提交更加新的变更的节点即可。
  • 所有读者进入的时候,都复制当前的根节点的一个snapshot(因为它随后很可能被改变)
  • 当一个变更被提交的时候,它清楚因为这个提交,哪些节点迟早会变成没用的---所有因为这次变更发生过修改的节点,都会在相应版本的读者退出后变成无用的节点,因此可以回收
  • 因为上述理由,最新的版本可以收集所有需要被回收的节点和它们所属的版本
  • 维护一个读者的slot,可以从这里面查到最小的版本,比这个版本小的版本所属的可回收节点都可以进行回收

如上所述,我们现在只有两个根节点,所有变更最终都要修改这个根节点,这样,所有的写事实上要被序列化。这并没有降低性能,理由是这样的,如我们上面所述的,当两个变更并发进行的时候,确切的说,是进入同一个版本,并依据这个版本进行了某些变更,然后要提交这些变更,两者中必有一个事务必须重做,因为在它的提交和进入之间有别的提交,这个结论可以推广到多个并发的情况。也就是说,变更事实上是序列化的,由于不同的变更之间没有阻塞,MVCC的方案消耗了更多的计算资源(所有失败的提交都要被重做)。因此,lmdb用一把锁序列化了所有的变更操作。

以上就是lmdb实现中大部分要点。

原文链接:http://www.jianshu.com/p/yzFf8j

 

Leveldb和lmdb

Caffe生成的数据分为2种格式:Lmdb和Leveldb。
它们都是键/值对(Key/Value Pair)嵌入式数据库管理系统编程库。
虽然lmdb的内存消耗是leveldb的1.1倍,但是lmdb的速度比leveldb快10%至15%,更重要的是lmdb允许多种训练模型同时读取同一组数据集。

因此lmdb取代了leveldb成为Caffe默认的数据集生成格式。

LevelDb有如下一些特点:
   首先,LevelDb是一个持久化存储的KV系统,和Redis这种内存型的KV系统不同,LevelDb不会像Redis一样狂吃内存,而是将大部分数据存储到磁盘上。
   其次,LevleDb在存储数据时,是根据记录的key值有序存储的,就是说相邻的key值在存储文件中是依次顺序存储的,而应用可以自定义key大小比较函数,LevleDb会按照用户定义的比较函数依序存储这些记录。
   再次,像大多数KV系统一样,LevelDb的操作接口很简单,基本操作包括写记录,读记录以及删除记录。也支持针对多条操作的原子批量操作。

   另外,LevelDb支持数据快照(snapshot)功能,使得读取操作不受写操作影响,可以在读操作过程中始终看到一致的数据。

除此外,LevelDb还支持数据压缩等操作,这对于减小存储空间以及增快IO效率都有直接的帮助。LevelDb性能非常突出,官方网站报道其随机写性能达到40万条记录每秒,而随机读性能达到6万条记录每秒。总体来说,LevelDb的写操作要大大快于读操作,而顺序读写操作则大大快于随机读写操作。

lmdb简介——结合MVCC的B+树嵌入式数据库的更多相关文章

  1. sqlite嵌入式数据库简介及特性

    p.p1 { margin: 0; font: 12px "Helvetica Neue"; color: rgba(69, 69, 69, 1) } p.p2 { margin: ...

  2. 数据持久化之嵌入式数据库 SQLite(三)

    阿里P7Android高级架构进阶视频免费学习请点击:https://space.bilibili.com/474380680 SQLite 是 D. Richard Hipp 用 C 语言编写的开源 ...

  3. sqlite嵌入式数据库C语言基本操作(1)

    sqlite嵌入式数据库C语言基本操作(1) :first-child{margin-top:0!important}img.plugin{box-shadow:0 1px 3px rgba(0,0, ...

  4. B树在数据库索引中的应用剖析

    引言 关于数据库索引,google一个oracle index,mysql index总 有大量的结果,其中很多的使用方法推荐,**索引之n条经典建议云云.笔者认为,较之借鉴,在搞清楚了自己的需求的基 ...

  5. Java免费开源数据库、Java嵌入式数据库、Java内存数据库

    Java免费开源数据库.Java嵌入式数据库.Java内存数据库 http://blog.csdn.net/leiyinsu/article/details/8597680

  6. SQLite/嵌入式数据库

    SQLite/嵌入式数据库 的项目要么不使用数据库(一两个文配置文件就可以搞定),要么就会有很多的数据,用到 postgresql,操练sqlite的还没有.现在我有个自己的小测试例子,写个数据库对比 ...

  7. Java嵌入式数据库H2学习总结(一)——H2数据库入门

    一.H2数据库介绍 常用的开源数据库有:H2,Derby,HSQLDB,MySQL,PostgreSQL.其中H2和HSQLDB类似,十分适合作为嵌入式数据库使用,而其它的数据库大部分都需要安装独立的 ...

  8. B+树在数据库中的应用

    B+树在数据库中的应用 flyfish 2015-7-6 B+树在数据库中的应用重要是实现索引 应用方式一 ID为表的主键,利用主键建立一棵B+树 叶子结点存储记录的地址 应用方式二 ID为表的主键. ...

  9. [Sqlite]-->嵌入式数据库事务理解以及实例操作

    引子: 1. Sqlite在Windows.Linux 和 Mac OS X 上的安装过程 2,嵌入式数据库的安装.建库.建表.更新表结构以及数据导入导出等等具体过程记录 SQLite 事务(Tran ...

随机推荐

  1. Quartz.Net 调度框架配置介绍

    在平时的工作中,估计大多数都做过轮询调度的任务,比如定时轮询数据库同步,定时邮件通知等等.大家通过windows计划任务,windows服务等都实现过此类任务,甚至实现过自己的配置定制化的框架.那今天 ...

  2. MongoDB入门教程之C#驱动操作实例

    实体类: using MongoDB.Bson; namespace WindowsFormsApp { class User { //public ObjectId _id; //BsonType. ...

  3. iOS - LocalCache 本地数据缓存

    1.自定义方式本地数据缓存 1.1 自定义缓存 1 沙盒路径下的 Library/Caches 用来存放缓存文件,保存从网络下载的请求数据,后续仍然需要继续使用的文件,例如网络下载的离线数据,图片,视 ...

  4. iOS - UISwitch

    前言 NS_CLASS_AVAILABLE_IOS(2_0) __TVOS_PROHIBITED @interface UISwitch : UIControl <NSCoding> @a ...

  5. for循坏的穷举与迭代,while、do while循环

    for循环的穷举:所有情况走一遍,使用if筛选出符合的情况. 单位发一张150元购物卡,到超市买三种洗化用品,洗发水15元,香皂两元,牙刷5元,刚好花完150元,有多少种买法,每种买法各买几样. 百鸡 ...

  6. ora-01033:oracle initialization or shutdown in progress 解决方法

    今天研究Oracle遇到了这个问题ora-01033:oracle initialization or shutdown in progress,经过分析研究终于解决了,写下来纪念一下.我的库是ora ...

  7. python中模块sys与os的一些常用方法

    sys模块提供了访问或操作与python解释器相关方法与对象. 我们就列举出常用到的知识,以后,随着学习,不断补充. 几个常用到的动态对象: sys.argv,这是一个列表,它包含了所有传递给脚本的命 ...

  8. Android 设置进度条背景

    Android 设置进度条背景 直接上代码 <ProgressBar android:id="@+id/progressBar" android:layout_width=& ...

  9. eclipse右击打war包class没打上去的问题

    今天,遇到个诡异的问题,一个工程eclipse打war包class就是打不上,别的能打上,发现这样选择tomcat,打包成功,谨此备注.

  10. mybatis动态sql中的trim标签的使用

    trim标记是一个格式化的标记,可以完成set或者是where标记的功能,如下代码: 1. select * from user <trim prefix="WHERE" p ...