LevelDB
LevelDB is a fast key-value storage library written at Google that provides an ordered mapping from string keys to string values.
Features
-
键和值是任意的字节数组。
-
数据按键存储。
-
调用者可以提供一个定制的比较函数来覆盖排序顺序。
-
基本操作是Put(key,value), Get(key), Delete(key)。
-
在一个原子批处理中可以进行多个更改。
-
用户可以创建一个瞬态快照来获得数据的一致视图。
-
数据支持向前和向后迭代。
-
使用Snappy compression库自动压缩数据。
-
外部活动(文件系统操作等)通过虚拟接口中继,以便用户可以自定义操作系统交互。
适用场景
LevelDB是Google开源的持久化KV单机数据库,具有很高的随机写,顺序读/写性能,但是随机读的性能很一般,也就是说,LevelDB很适合应用在查询较少,而写很多的场景。
LevelDB应用了LSM (Log Structured Merge) 策略,lsm_tree对索引变更进行延迟及批量处理,并通过一种类似于归并排序的方式高效地将更新迁移到磁盘,降低索引插入开销,关于LSM,本文在后面也会简单提及。
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。但是数据不能太大,否则磁盘读取的延迟将很大,此时应该使用分布式缓存。(当然,分布式缓存是用于解决分布式环境中数据同步、一致性的问题,不仅仅是数据量过大的问题)
特性
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、非关系型数据模型(NoSQL),不支持sql语句,也不支持索引;
2、一次只允许一个进程访问一个特定的数据库;
3、没有内置的C/S架构,但开发者可以使用LevelDB库自己封装一个server;
ps: TIDB 就是基于 k-v 来映射 SQL,所以技术的本质是相通的。
案例推演
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缓冲文件的存储方案。
其他方案
LevelDB是google的实现,官方只提供了C++版的客户端,java客户端比如上述的iq80(还有fusesource 项目的leveldbjni)是来自社区的。
不过BigTable的设计思想和LevelDB的特性被社区延续了下去,比如相对比较完善和性能更加优秀的RocksDB,我们建议在实际的开发工作中采用它。
快速开始
jar
<dependency>
<groupId>org.iq80.leveldb</groupId>
<artifactId>leveldb</artifactId>
<version>0.10</version>
</dependency>
<dependency>
<groupId>org.iq80.leveldb</groupId>
<artifactId>leveldb-api</artifactId>
<version>0.10</version>
</dependency>
还有 junit4
测试代码
import org.iq80.leveldb.DB;
import org.iq80.leveldb.DBFactory;
import org.iq80.leveldb.Options;
import org.iq80.leveldb.WriteOptions;
import org.iq80.leveldb.impl.Iq80DBFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import static org.iq80.leveldb.impl.Iq80DBFactory.asString;
import static org.iq80.leveldb.impl.Iq80DBFactory.bytes;
public class LevelDbTest {
private DB db;
@Before
public void before() throws IOException {
String path = "/Users/houbinbin/cache/data/leveldb";
DBFactory factory = new Iq80DBFactory();
Options options = new Options();
options.createIfMissing(true);
db = factory.open(new File(path), options);
}
@After
public void after() throws IOException {
db.close();
}
@Test
public void operateTest() {
db.put(bytes("Tampa"), bytes("rocks"));
String value = asString(db.get(bytes("Tampa")));
System.out.println(value);
// 这里的 sync 其实也就是在写入的时候,不只是写入到内存中,同时也会同步写入到文件中持久化存储。
WriteOptions writeOptions = new WriteOptions().sync(true);
db.delete(bytes("Tampa"), writeOptions);
}
}