原文地址http://leveldb.googlecode.com/svn/trunk/doc/index.html
特征:
1. 高效的kv持久化存储系统,key为任意字节的数组,顺序读写的qps可达几百K;
2. 数据按key 有序存储, 支持自定义比较函数;
3. 支持数据压缩,默认为snappy 压缩;
4. 支持前向后向迭代器
打开一个数据库:
一个leveldb数据库有一个对应存储目录的名字,所有数据都存储在这个目录下。下面的例子展示了如何打开一个数据库,如果目录不存在就创建它:
#include <assert>
#include "leveldb/db.h"
leveldb::DB* db;
leveldb::Options options;
options.create_if_missing = true;
leveldb::Status status = leveldb::DB::Open(options, "/tmp/testdb", &db);
assert(status.ok());
...
如果你想在目录存在的情况下报错,如何设置options:
options.error_if_exists = true;
Status:
绝大多数function 会返回类型为leveldb::Status的值,你可以检查返回值判断函数是否出错,还可以打印出错信息:
leveldb::Status s = ...; if (!s.ok()) cerr << s.ToString() << endl;
关闭一个数据库完成对数据库的操作后,调用一下操作关闭一个数据库:
delete db;
读写操作
数据库提供了Put,Delete和Get方法来修改和查询数据库。下面的例子把key1的值移动到key2下.
std::string value; leveldb::Status s = db->Get(leveldb::ReadOptions(), key1, &value); if (s.ok()) s = db->Put(leveldb::WriteOptions(), key2, value); if (s.ok()) s = db->Delete(leveldb::WriteOptions(), key1);
原子更新注意,上面的操作如果在Put key2后挂掉,相同的值就会存储在多个key中。这种问题可以通过WriteBatch实现原子更新来避免:
#include "leveldb/write_batch.h" ... std::string value; leveldb::Status s = db->Get(leveldb::ReadOptions(), key1, &value); if (s.ok()) { leveldb::WriteBatch batch; batch.Delete(key1); batch.Put(key2, value); s = db->Write(leveldb::WriteOptions(), &batch); }
WriteBatch 包含一系列操作,这些操作会按顺序执行。注意我们先调用Delete,在调用Put,这可以避免在key1==key2时,value被错误地删掉。除了原子性外,WriteBatch 也可以通过把多个单个的操作组成批量操作来加快速度。
同步写机制
默认情况,leveldb是异步写:操作提交给操作系统后返回。由操作系统内存写入底层持久化存储异步进行。可以打开sync标签,可以是写操作写入持久化存储后返回。(在Posix系统上,通过调用fsync() or fdatasync() or msync()实现)
leveldb::WriteOptions write_options; write_options.sync = true; db->Put(write_options, ...);
异步写会比同步写快1000倍。异步写的负面是当机器crash是会丢失最近的几个操作。注意如果是进程crash不会造成任何数据丢失,数据已经由进程内存写入操作系统内存。
异步写通常是安全的。例如当load大量数据到数据时,你可以通过重启批量load来解决crash时的更新丢失问题。一种混合的方案也是可行的,每n次执行一次同步写,当crash时,从最后一次同步写后执行批量load重启,同步写会更新一个标记,用来描述crash是的重启位置。
批量写是另一种代替异步写的机制。多次更新可以通过一个批量操作完成,使用同步写。同步写的额外消耗被分摊到多次写上。
并发
一个数据库同一时间只能被一个进程打开。leveldb会从系统中请求一个锁,防止数据库被滥用。在一个进程中,相同的DB对象可以被多个并发线程安全共享。不同的线程可以写入或者获得迭代器或者调用Get在同一个数据库上,而不需要同步(leveldb会自动进行同步)。但是其他对象,比如Iterator和writeBatch需要额外的同步。如果两个线程共享这样的对象,他们必须用自己的加锁协议来保护对他们的访问。
Iteration
下面展示如何遍历数据库中的kv对:leveldb::Iterator* it = db->NewIterator(leveldb::ReadOptions()); for (it->SeekToFirst(); it->Valid(); it->Next()) { cout << it->key().ToString() << ": " << it->value().ToString() << endl; } assert(it->status().ok()); // Check for any errors found during the scan delete it;
下面展示如何访问[start,limit)间的内容
:for (it->Seek(start); it->Valid() && it->key().ToString() < limit; it->Next()) { ... }
你也可以逆序遍历数据库中的内容for (it->SeekToLast(); it->Valid(); it->Prev()) { ... }
Snapshots:
快照提供了一致性只读视图。Readoptions::snapshot 不为空表示读操作在DB的某个状体进行。如果为空读操作默认为DB的当前状态。
通过DB::GetSnapshot()创建快照:
leveldb::ReadOptions options; options.snapshot = db->GetSnapshot(); ... apply some updates to db ... leveldb::Iterator* iter = db->NewIterator(options); ... read using iter to view the state when the snapshot was created ... delete iter; db->ReleaseSnapshot(options.snapshot);
如果一个快照不需要了,可以通过DB::ReleaseSnapshot 释放。Slice:
it-key() 和 it->value()的返回值是leveldb::Slice 类型。 Slice是一个包含一个指向字节数据的指针和字节数据长度的简单结构体。返回一个Slice是返回string的廉价替代,避免了复制大的keys和values。另外,leveldb 的方法不返回null结尾的C-style 字符串,因为leveldb允许键和值包含'\0'。
C++ 字符串和C-style 字符串都可以简单的转换为Slice:
leveldb::Slice s1 = "hello"; std::string str("world"); leveldb::Slice s2 = str;
Slice 转换为C++ string:std::string str = s1.ToString(); assert(str == std::string("hello"));
应该确保在使用Slice时,Slice指向的字节数据仍然有效.下面的使用方法是错误的:leveldb::Slice slice; if (...) { std::string str = ...; slice = str; } Use(slice);
当if语句结束时,str会被析构,slice指向的内存也会失效。