LevelDB学习与实践
LevelDB是一个基于本地文件的存储引擎,非分布式存储引擎,原理基于BigTable(LSM文件树),无索引机制,存储条目为Key-value。适用于保存数据缓存、日志存储、高速缓存等应用,主要是避免RPC请求带来的延迟问题。在存取模型上,顺序读取性能极高,但是对于随机读取的情况延迟较大(但性能也不是特别低),比较适合顺序写入(key),随机的key写入也不会带来问题。数据存量通常为物理内存的3~5倍,不建议存储过大的数据,在这个数据量级上,leveldb的性能比那些“分布式存储”要高(即本地磁盘存取延迟小于RPC网络延迟)。
1)如果你的log日志或者视频片段需要暂存在本地,稍后再批量发给远端的数据中心,那么这种需求非常适合使用leveldb做数据缓冲。(这些缓存的数据被切分成多个小的chunks,以key-value的方式保存在leveldb中)
2)如果你希望构建一个本地cache组件,但是cache的数据可能比内存容量要大,此时我们就可以使用leveldb做支撑,leveldb将一部分热区数据保存在内存,其他数据保存在磁盘上,可以并发的、随机读取key-value。但是数据不能太大,否则磁盘读取的延迟将很大,此时应该使用分布式缓存。(当然,分布式缓存是用于解决分布式环境中数据同步、一致性的问题,不仅仅是数据量过大的问题)
一、原理
1、Files
leveldb的实现类似于Bigtable中的一个tablet(Google),只不过底层的文件组织形式稍有不同。
每个Database有一系列本地文件组成,这些文件有不同的类型:
Log文件
log文件存储了一序列的最近更新操作,每个更新(update)都会append到当前log文件的尾部,当log文件的尺寸达到预设定的大小时,将会把此log文件转换成一个sorted table(.sst)文件,然后滚动创建一个新的log文件来保存此后的updates操作,主要是用于数据恢复。
当前log文件的数据copy被保存在一个内存结构中,称为memtable,任何update首先会被写入到memtable中,然后在写入log文件。每个read操作都会首先访问memtable,如果memtable中没有的话再触发磁盘检索(如果开启了cacheSize,则在磁盘检索之前会查看cache),因此这些update数据都可以在read操作中反应出来。
memtable中的数据按照key顺序存储,即有序存储(基于跳跃表实现)。默认memtable的大小为4M,由参数“writeBufferSize”决定,需要在leveldb打开db文件时指定。
Sorted tables(简称SST)
当memtable的数据量达到阀值时,则会被刷新写入到磁盘,生成一个Sorted table(.sst)文件,此文件存储了一序列按照key排序的entries,每个entry可以是key-value,或者是一个key的删除标记(marker),文件和memtable一样也是根据key排序的。(删除标记可以屏蔽掉先前sst文件中保存的较旧的数据,即如果一个key被标记为删除,那么先前的sst文件中关于此key的数据,将不会被read到)
Sorted tables按照层次(level)进行组织,由log文件生成的SST将会放置在一个特殊的young level中--即level-0,当young level中SST文件的个数超过一个阀值(4个),这些young文件将会与level-1中的那些有数据重叠的文件合并,并生成一序列新的level-1文件(每个新文件大小位2M)。
备注:“重叠”意义为key区间在两个文件中都存在。keys在SST文件中保存是严格排序的。同时需要注意,sst文件中还包含BloomFilter内容,bloomFilter可以快速判断key是否存在于此sst文件,有效的提高了read的效率。
young level中的文件可能包含重叠的keys,不过其他level中的SST文件只会包含不同的“非重叠”的keys区间。假如level-L,其中L >= 1,当level-L中SST文件的总大小达到(10^L)MB时(例如level-1位10MB,level-2位100MB),那么level-L中的一个文件,将会和level-(L+1)中那些有keys重叠(覆盖)的文件merged,并生成一组新的level-(L+1)文件。这些merge,只通过批量的文件读写操作,即可将最新的updates数据从young level迁移到最高的level。同一个level中,不会有key重叠的sst文件;但是不同level可能会有!
level的级别越低,数据新鲜程度越高。遍历数据时,从level 0开始向高level推进。
Manifest(清单)
manifest文件中列举了构成每个level的SST文件列表,以及相应的key区间,还包括一些重要的metadata。当database被reopened时,都会创建一个新的manifest文件(文件命中包含一个新的number序列号)。manifest文件的格式像log,“serving data”的变更(比如SST文件的创建、删除)操作都会被append到此log中。
Current
CURRENT文件是一个简单的文本文件,保存了当前最新的manifest文件的名称。
其他:略
2、Level 0
当log文件的尺寸增长到一定的大小(默认1M):
- 创建一个新的memtable和log文件,用来保存此后的updates操作。
- 在后台:将旧的memtable写入到文件生成新的SST文件,然后销毁此memtable。删除旧的log文件,然后将此新的SST文件添加到young level组织中。
3、Compactions
当level-L的尺寸达到了它的限制,我们将使用一个后台线程对它进行Compaction。压缩时,将会从level-L中选择一个文件,同时选择level-(L+1)中所有与此文件key有重叠的文件。如果level-L中一个文件只与level-(L+1)中某个文件的一部分重叠,那么level-(L+1)中的此文件作为压缩时的输入,在压缩结束后,此文件将被抛弃。不过,level-0比较特殊(文件中的keys可能互相重叠),对于level-0到level-1的压缩我们需要特殊处理:level-0中文件中互相重叠的话,那么将可能一次选择多个level-0的文件作为输入。
压缩将选择的文件内容重新输出到一序列新的level-(L+1)文件中(多路合并),当每个输出文件达到2M时将会切换一个新的文件,或者当新输出的文件中key区间覆盖了level-(L+2)中多于10个文件时,也会切换生成新文件;第二个规则保证此后level-(L+1)的压缩时无需选择太多的文件。
当level-(L+1)中的新文件加入到“serving state”时,那么旧的文件将会被删除(包括level-L和level-(L+1))。
压缩时,将会抛弃那些“overwritten”的值;如果遇到删除标记,且对应的key在更高的level中不存在,也会直接抛弃。
Timing
level-0将会读取4个1M的文件(每个1M,level-0最多4个文件),最坏的情况是读取level-1的所有文件(10M),即我们读写各10MB。
和level-0不同,对于其他level-L,我们将读取2M的一个文件,最坏的情况是它与level-(L+1)中12文件有重叠(10个文件,同时还有2个处于边界的文件);那么一次压缩将读写26MB数据。假定磁盘IO速率位100M/S,那么一次压缩耗时大约0.5秒。
如果我们对磁盘速率受限,比如10M/S,那么压缩可能耗时达到5秒。
文件个数
每个SST文件的大小为2M(level-0的除外),事实上我们可以通过增大此值(需要重新编译源文件),来减少文件的总数,不过这会导致压缩更加耗时(读取的文件尺寸更大,磁盘密集操作);另外,我们可以将不同的文件放在多个目录中。
4、数据恢复
1)从CURRENT中读取最新的manifest文件的名字。
2)读取manifest文件。
3)清理那么过期的文件。
4)我们可以打开所有的SST文件,不过通常lazy更好。
5)将log存留文件转存成新的level-0中的SST文件。
6)引导write操作到新的log文件中。
7)回收垃圾文件。
每次压缩和recovery操作后,将会调用DeleteObsoleteFiles():从database中查询出所有的file的名字,然后将当前log文件之外的其他log文件全出删除,删除那些所有level中都不包含的、以及压缩操作没有引用的SST文件。
二、使用
leveldb为一个本地化的K-V存储数据库,设计思想类似于Bigtable,将key按照顺序在底层文件中存储,同时为了加快读取操作,内存中有一个memtable来缓存数据。
根据leveldb官网的性能基准测试,我们大概得出其特性:
1)leveldb的顺序读(遍历)的效率极高,几乎接近文件系统的文件顺序读。比BTree数据库要快多倍。
2)其随机读性能较高,但和顺序读仍有几个量级上的差距。leveldb的随机读,和基于BTree的数据库仍有较大差距。(个人亲测,其随机读的效率并不像官网所说的如此之高,可能与cache的配置有关)随机读,要比BTree慢上一倍左右。
3)顺序写,性能极高(无强制sync),受限于磁盘速率;随机写,性能稍差,不过性能相对于其他DB而言,仍有极大的优势。无论是顺序写还是随机写,性能都比BTree要快多倍。
4)leveldb为K-V存储结构,字节存储。属于NoSql数据库的一种,不支持事务,只能通过KEY查询数据;支持批量读写操作。
5)leveldb中key和value数据尺寸不能太大,在KB级别,如果存储较大的key或者value,将对leveld的读写性能都有较大的影响。
6)leveldb本身没有提供索引机制,所以随机读性能稍差。它存储的key、value可以为任意字节数组。
因为leveldb本身尚不具备“分布式”集群架构能力,所以,我们将有限的数据基于leveldb存储(受限于本地磁盘)。
案例推演:
1)leveldb具备“cache + 磁盘持久存储”特性,且不支持RPC调用,那么leveldb需要和application部署在同一宿主机器上。类似于“嵌入式”K-V存储系统。
2)如果存储数据较少,3~5G,且“读写比”(R:W)较高,我们可以让leveldb作为本地cache来使用,比如Guava cache + leveldb,这种结合,可以实现类似于轻量级redis。即作为本地缓存使用。通常LevelDB存储的数据是内存大小的3~5倍(现代的操作系统配置),不建议用leveldb存储过大的数据,否则性能将下降很大。
3)如果数据较多,通常为“顺序读”或者“顺序写”,我们可以将leveldb作为Hadoop HDFS的“微缩版”,可以用来缓存高峰期的消息、日志存储的缓冲区。比如我们将用户操作日志暂且存储在leveldb中,而不是直接将日志发送给remote端的Hadoop(因为每次都直接调用RPC,将会对系统的吞吐能力带来极大的影响),而是将这些频繁写入的日志数据存储在本地的leveldb中,然后使用后台线程以“均衡”的速度发送出去。起到了“Flow Control”(流量控制)的作用。
其中ActiveMQ即采用leveldb作为底层的消息数据存储,性能和容错能力很强。在很多情况下,leveldb可以作为本地log、IO缓冲文件的存储方案。
三、API简析(JAVA版)
原生leveldb是基于C++开发,java语言无法直接使用;iq80对leveldb使用JAVA语言进行了“逐句”重开发,经过很多大型项目的验证(比如ActiveMQ),iq80开发的JAVA版leveldb在性能上损失极少(10%)。对于JAVA开发人员来说,我们直接使用即可,无需额外的安装其他lib。
1、pom.xml
<dependency> <groupId>org.iq80.leveldb</groupId> <artifactId>leveldb</artifactId> <version>0.7</version> </dependency> <dependency> <groupId>org.iq80.leveldb</groupId> <artifactId>leveldb-api</artifactId> <version>0.7</version> </dependency>
2、代码样例
boolean cleanup = true; Charset charset = Charset.forName("utf-8"); String path = "/data/leveldb"; //init DBFactory factory = Iq80DBFactory.factory; File dir = new File(path); //如果数据不需要reload,则每次重启,尝试清理磁盘中path下的旧数据。 if(cleanup) { factory.destroy(dir,null);//清除文件夹内的所有文件。 } Options options = new Options().createIfMissing(true); //重新open新的db DB db = factory.open(dir,options); //write db.put("key-01".getBytes(charset),"value-01".getBytes(charset)); //write后立即进行磁盘同步写 WriteOptions writeOptions = new WriteOptions().sync(true);//线程安全 db.put("key-02".getBytes(charset),"value-02".getBytes(charset),writeOptions); //batch write; WriteBatch writeBatch = db.createWriteBatch(); writeBatch.put("key-03".getBytes(charset),"value-03".getBytes(charset)); writeBatch.put("key-04".getBytes(charset),"value-04".getBytes(charset)); writeBatch.delete("key-01".getBytes(charset)); db.write(writeBatch); writeBatch.close(); //read byte[] bv = db.get("key-02".getBytes(charset)); if(bv != null && bv.length > 0) { String value = new String(bv,charset); System.out.println(value); } //iterator,遍历,顺序读 //读取当前snapshot,快照,读取期间数据的变更,不会反应出来 Snapshot snapshot = db.getSnapshot(); //读选项 ReadOptions readOptions = new ReadOptions(); readOptions.fillCache(false);//遍历中swap出来的数据,不应该保存在memtable中。 readOptions.snapshot(snapshot);//默认snapshot为当前。 DBIterator iterator = db.iterator(readOptions); while (iterator.hasNext()) { Map.Entry<byte[],byte[]> item = iterator.next(); String key = new String(item.getKey(),charset); String value = new String(item.getValue(),charset);//null,check. System.out.println(key + ":" + value); } iterator.close();//must be //delete db.delete("key-01".getBytes(charset)); //compaction,手动 db.compactRange("key-".getBytes(charset),null); // db.close();
LevelDB是google的实现,官方只提供了C++版的客户端,java客户端比如上述的iq80(还有fusesource 项目的leveldbjni)是来自社区的。不过BigTable的设计思想和LevelDB的特性被社区延续了下去,比如相对比较完善和性能更加优秀的RocksDB,我们建议在实际的开发工作中采用它。
参考文献:
1、LevelDB实现原理:http://leveldb.googlecode.com/git-history/1.17/doc/impl.html
2、LevelDB性能测试:http://leveldb.googlecode.com/git-history/1.17/doc/benchmark.html?r=1.17
相关推荐
总的来说,这个源码包为Windows开发者提供了一个良好的起点,用于学习和研究leveldb的实现细节,或者将其集成到自己的Windows应用程序中。通过深入阅读源码和实践编译,可以进一步提升对分布式存储系统、数据结构和...
总的来说,这个“mnist-leveldb.7z”压缩包为Windows用户提供了一个方便的途径,让他们能在本地环境中使用Caffe框架进行MNIST数据集的深度学习实践,无论是初学者还是有经验的研究者,都能从中获益。通过这个压缩包...
Go-levigo是针对Google开源的键值存储引擎LevelDB的一款高效、全面的Go语言封装库。LevelDB是由Google开发的轻量级、高...通过深入学习和实践,开发者可以充分利用Go-levigo和LevelDB的优势,解决各种数据存储问题。
在这个过程中,理解Leveldb如何存储和检索数据,以及如何配置Caffe的网络结构和参数,对于深度学习实践者来说至关重要。不断地实践和优化,可以提升模型的识别精度,进一步探索深度学习的潜力。
**MNIST 数据集详解** MNIST 数据集是机器学习领域最为经典的图像识别数据集,主要用于手写数字识别任务。...理解和掌握这两种数据存储系统对于深入研究和实践机器学习,尤其是深度学习的模型训练,具有重要的价值。
在Tair中使用LevelDB作为storage_engine配置集群时,可能会遇到一系列问题,这些问题通常涉及到配置错误、资源限制以及系统兼容性...此外,持续学习和了解Tair及LevelDB的最佳实践将有助于预防未来可能出现的类似问题。
总之,Caffe结合MNIST数据集和LevelDB格式提供了一个有效的深度学习平台,用于学习和实践卷积神经网络。从构建网络结构到训练模型,再到评估性能,这个过程涵盖了深度学习的基本流程,对于理解和掌握深度学习技术...
总之,"LevelDB数据库 v1.23.zip"压缩包提供了深入了解和实践LevelDB数据库的机会,无论是进行毕业设计论文研究,还是开发系统软件工具,都能从中受益。通过深入源代码,你可以掌握其内部工作流程,提升自己的编程...
通过阅读和理解这个文件,我们可以深入学习LevelDB的工作原理和最佳实践。 4. idct_blk_neon.c:这个文件名可能与Inverse Discrete Cosine Transform(IDCT)相关,通常用于视频编码解码过程。Neon是ARM架构的一种...
《图解leveldb源码和中文注释》是一份深度剖析开源数据库系统leveldb的资源,其中包含了详尽的代码分析和中文注释,旨在帮助开发者更好地理解和...这份中文注释的源码分析,无疑为学习和实践leveldb提供了极大的便利。
LRU (Least Recently Used) 缓存是一种常用的内存管理策略,用于存储系统中,当内存空间有限时,根据数据的访问频率和时间来决定...对于学习计算机科学,尤其是存储系统和数据结构的人来说,这是一个很好的学习资源。
这个DEMO对于理解JNI的工作原理和实践,以及Java如何与C/C++库集成,特别是与LevelDB这样的数据库系统交互,具有很好的学习价值。开发者可以通过分析和修改这个DEMO,来适应自己的需求,比如使用不同的数据库或者...
在这个项目中,我们探讨的是如何使用WebRTC、LevelDB和WebSocket技术来构建一个实时的、基于浏览器的...这个项目是一个很好的实践平台,通过它你可以深入学习这些技术,并实现一个完全在浏览器中运行的实时聊天应用。
7. **学习资源与实践**: 对于想学习 JavaScript 和 LevelDB 结合的开发者,这是一个很好的实战案例,可以从中学习如何使用 JavaScript 编写数据库后端服务。 通过深入了解这些知识点,你可以更好地理解 zag-backend...
这个项目可能是作者对 LevelDB 进行实践、学习或扩展的一个平台。 【描述】中的 "我在玩 leveldb 方面的工作" 暗示了这个项目是作者个人对 LevelDB 技术的研究和实验。可能包含了使用 LevelDB 的示例代码、自定义...
总结来说,“我爱记单词”项目涵盖了C++类的高级使用、MFC对话框的构建以及Leveldb数据库的集成,是学习和实践这三个关键领域的理想案例。通过深入研究这个项目,开发者不仅可以提升编程技能,还能体验到跨学科知识...
在IT行业中,数据库管理...通过学习和实践Grafton项目,你可以深入了解如何在JavaScript环境中利用LevelDB的强大力量。这不仅提升了你的技能,也使你能够更好地理解和应用键值存储技术,为未来的开发工作打下坚实基础。
【正文】 本篇学习笔记主要介绍了如何在Caffe框架下搭建和训练用于...对于想要深入学习深度学习和Caffe框架的初学者,这是一个很好的实践案例。理解并掌握这些知识有助于进一步探索更复杂的网络结构和大规模的数据集。
**MNIST数据集详解** MNIST(Modified National Institute of Standards and Technology)数据集是计算机视觉领域最...无论是新手还是经验丰富的从业者,MNIST都提供了一个绝佳的平台,用于探索和实践深度学习技术。
- 在实践中,可能还需要对数据进行归一化处理,以符合深度学习模型对输入数据的预设格式要求。 - 深度学习模型的训练和测试是一个反复调整和优化的过程,可能需要通过多次实验来调整网络结构和参数设置,以达到更好...