您的位置:首页 > 其它

leveldb学习记录

2014-12-06 16:05 274 查看


转自:http://blog.marchtea.com/archives/205

关于leveldb

性能对比

安装

使用

性能调整


关于leveldb

leveldb是google开发的一套用于持久化数据的高性能类库。其特性有:

key-value方式存取

key-value都是二进制数据流

数据以key排序存储

操作简单: Get,Put,Delete,同时支持原子操作.

支持快照(snapshot),读不受写的影响.

自动压缩

高性能

需要注意的是,不同于
redis
或者
mangodb
,LevelDB并不是
NoSQL
.其不支持sql语句,也不支持索引.
此外,只有单进程能访问数据库.另外,leveldb并不是一种
服务
,用户需要自行实现server.

根据上面的描述,LevelDB还是有很多限制.但是关键在于相比于sqlite等其他数据库,其具有非常高的写性能的同时,也保持了不错的读性能(参考下面的性能对比表). 在一些需要持久化key-value数据的场景下具有应用价值.这也弥补了
memcached
不能持久化的劣势.


性能对比

环境:
OS:  Mac OSX 10.9.3 Darwin Kernel Version 13.2.0
Machine:  retina MacBook ME664
CPU:  Intel Core i7 2.4 GHz
Memory:  8 GB
Storage:  APPLE SSD SD256E
LevelDB:  version 1.15
Keys:  16 bytes each
Values:  100 bytes each (50 bytes after compression)
Entries:  1000000
RawSize:  110.6 MB (estimated)
FileSize:  62.9 MB (estimated)

SQLite:  version 3.7.13
Keys:  16 bytes each
Values:  100 bytes each
Entries:  1000000
RawSize:  110.6 MB (estimated)

benchmark由leveldb提供




从表中可以看出,leveldb的写性能相对于sqlite要高的多.但读性能较低.至于原因,之后可以通过对源码分析就可以看出.


安装

前面已经说明了,leveldb为一套类库.所以相对来说,安装过程更为简单.

首先下载压缩包:
https://code.google.com/p/leveldb/downloads/list


或使用
git
:
git clone https://code.google.com/p/leveldb/[/code] 
解压缩之后,执行
make
,即可生成静态及动态库.

summertekiMacBook-Pro:leveldb summer$ ls -l

total 4880

-rw-r–r–+ 1 summer staff 590552 Jun 5 11:13 libleveldb.a

lrwxr-xr-x 1 summer staff 21 Jun 5 11:13 libleveldb.dylib -> libleveldb.dylib.1.15

lrwxr-xr-x 1 summer staff 21 Jun 5 11:13 libleveldb.dylib.1 -> libleveldb.dylib.1.15

-rwxr-xr-x+ 1 summer staff 315656 Jun 5 11:13 libleveldb.dylib.1.15

…..

如果想要在系统全局都可以使用,执行:
sudo cp -r include/leveldb /usr/include
sudo cp libleveldb.dy* /usr/lib


linux的动态链接库的后缀会有所不同,请酌情修改.

That’s it.安装完成了.


使用:


第一个例子

先写代码test.cpp:
#include <assert.h>
#include <string.h>
#include <leveldb/db.h>
#include <iostream>

int main(){
leveldb::DB* db;
leveldb::Options options;
options.create_if_missing = true;
leveldb::Status status = leveldb::DB::Open(options,"/tmp/testdb", &db);
assert(status.ok());

//write key1,value1
std::string key="key";
std::string value = "value";

status = db->Put(leveldb::WriteOptions(), key,value);
assert(status.ok());

status = db->Get(leveldb::ReadOptions(), key, &value);
assert(status.ok());
std::cout<<value<<std::endl;
std::string key2 = "key2";

//move the value under key to key2

status = db->Put(leveldb::WriteOptions(),key2,value);
assert(status.ok());
status = db->Delete(leveldb::WriteOptions(), key);

assert(status.ok());

status = db->Get(leveldb::ReadOptions(),key2, &value);

assert(status.ok());
std::cout<<key2<<"==="<<value<<std::endl;

status = db->Get(leveldb::ReadOptions(),key, &value);

if(!status.ok()) std::cerr<<key<<"  "<<status.ToString()<<std::endl;
else std::cout<<key<<"="<<value<<std::endl;

delete db;
return 0;
}


编译命令:
g++ -o test test.cpp -I/PATH/TO/LEVELDB/include -L/PATH/TO/libleveldb -lleveldb


如果前面将库拷贝到/usr/include及/usr/lib中,则可以省略后面的-I及-L

执行效果:
summertekiMacBook-Pro:leveldb summer$ ./test
value
key2=value
key  NotFound:


在上述代码中,我们执行了:

打开leveldb

对key写入了value

对key2写入了value

删除了key所对应的值

查看了key2对应的值

查看了key对应的值

可以看到输出结果和我们预料的相同.


各种操作


Status

大部分函数返回值为
Status
.可以通过
s.ok()
来查看是否正常,也可以通过
s.toString()
来查看对应错误.


关闭数据库

直接删除数据库对象即可将数据库关闭
... open the db as described above ...
... do something with db ...
delete db;


读写

leveldb提供了三个基本操作:
Get
,
Put
,
Delete
.
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);


原子更新(事务)

leveldb提供了原子更新的功能.通过使用
WriteBatch
,可以将多个操作绑定,使得多个操作在一个batch内完成.类似于关系型数据库中的事务.
#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);
}


同步写

默认情况下,leveldb的写是异步写(asynchronous).即其调用了write交给内核之后就直接返回.由于磁盘速度很慢,内核会对
write
做一定的缓冲,在合适的时候存到磁盘中.

通常来说这种方式执行的效果很好.不过如果在数据存到磁盘前,主机掉电了,那数据就丢失了.所以如果对数据很敏感,可以设置
sync
标志,强制操作系统写入磁盘中.(在
write
返回前,调用
fsync
或者
fdatasync
msync
)
leveldb::WriteOptions write_options;
write_options.sync = true;
db->Put(write_options, ...);


由于磁盘的限制,同步是一个很慢的过程(参考上面的表格).考虑到效率,可以将多个写操作放在一个writebatch中,然后执行同步写.这样同步的cost就可以被分担到多个写操作中.


并发性

leveldb是单进程的,只有一个进程能访问数据库.leveldb使用了
文件锁(fcntl)
来保证不会有多个进程访问同一个数据库.从而导致数据库内容被破坏.另外,其也不允许在进程内多次打开相同数据库.所以下列的代码中,第二次打开会失败.另外,由于DB的
复制构造函数
operator=
都为私有,所以也不能对DB进行复制.
leveldb::DB* db;
leveldb::Options options;
options.create_if_missing = true;
leveldb::Status status = leveldb::DB::Open(options,"/tmp/testdb", &db);
assert(status.ok());

leveldb::DB* adb;
status = leveldb::DB::Open(options, "/tmp/testdb", &adb);
assert(status.ok());


leveldb的实现提供了同步处理,因此,多个线程操作同一个DB对象时是不需要加锁来同步的.而对于
Iterator
WriteBatch
,对非
const
函数操作时需要注意加
.


Iteration

leveldb提供了迭代器,使得可以遍历数据库中的所有
key-value
值.
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;


也可以遍历范围内的值[‘aa’,’cc’):
for (it->Seek(start);
it->Valid() && it->key().ToString() < limit;
it->Next()) {
...
}


当然也可以逆序遍历
for (it->SeekToLast(); it->Valid(); it->Prev()) {
...
}


Slice

使用迭代器时,其返回的
it->value
,
it->key
的类型都为Slice,甚至
Seek
使用的也是
Slice
.Slice的实现很简单:
class Slice {
public:
...
// Create a slice that refers to the contents of "s"
Slice(const std::string& s) : data_(s.data()), size_(s.size()) { }

// Create a slice that refers to s[0,strlen(s)-1]
Slice(const char* s) : data_(s), size_(strlen(s)) { }
...
private:
const char* data_;
size_t size_;
};


仅仅就是一个
指针
及一个
size_t
.从构造函数上来看,使用起来也很容易.当然也包含了坑.其仅仅把string或者const
char* 的指针复制到
data_
中,没有其他的控制,所以一不小心容易出现问题.
leveldb::Slice s1 = "hello";
std::string str("world");
std::string str = s1.ToString();

leveldb::Slice slice;
if (...) {
std::string str = ...;
slice = str;
}
Use(slice);//ops! Memory which data_ points is gone!


使用Slice是基于性能考虑,可以减少拷贝的发生.


比较函数

leveldb默认的比较是基于字母序,同时也支持自定义比较器,用于比较key的大小.只需要继承
leveldb::Comparator
即可.
class TwoPartComparator : public leveldb::Comparator {
public:
// Three-way comparison function:
//   if a < b: negative result
//   if a > b: positive result
//   else: zero result
int Compare(const leveldb::Slice& a, const leveldb::Slice& b) const {
//do comparison,return -1, 0 or 1
}

// Ignore the following methods for now:
const char* Name() const { return "TwoPartComparator"; }
void FindShortestSeparator(std::string*, const leveldb::Slice&) const { }
void FindShortSuccessor(std::string*) const { }
};


快照(Snapshot)

在leveldb中,可以获得某个时刻的快照,使得读数据不受到写数据的影响.
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);


在使用完毕后,一定要记得调用
ReleaseSnapshot
,否则该数据就会一直留在磁盘中.


性能调整

leveldb提供了一些选项及接口,改善leveldb在不同环境下的性能.所有的选项都定义在
include/leveldb/options.h
中.


blocksize

leveldb将相邻key的数据保存在一个block中,其默认大小为4096B.可以通过
options.block_size
进行修改.如果应用中的数据较大,则可以调大数值,数据较小则相反.
不过太大太小的都不大好.可以通过leveldb提供的benchmark来测试修改的结果.


压缩

leveldb默认采用也是google出品的snappy库进行压缩.据称有很高的性能.在数据被持久化之前,数据将会被压缩,而后放入磁盘.当然,压缩不是强制的,可以被关闭.
leveldb::Options options;
options.compression = leveldb::kNoCompression;
... leveldb::DB::Open(options, name, ...) ....


缓存

leveldb将数据存储在文件中,为了改善性能,其将未压缩的数据缓存在内存中.缓存的大小可以设置.
#include "leveldb/cache.h"

leveldb::Options options;
options.cache = leveldb::NewLRUCache(100 * 1048576);  // 100MB cache
leveldb::DB* db;
leveldb::DB::Open(options, name, &db);
... use the db ...
delete db
delete options.cache;


Environment

leveldb的实现中,将所有的文件操作接口及系统调用都封装在了
leveldb::Env
对象中.所以使用自己编写的evn是完全有可能的.
class SlowEnv : public leveldb::Env {
.. implementation of the Env interface ...
};

SlowEnv env;
leveldb::Options options;
options.env = &env;
Status s = leveldb::DB::Open(options, ...);


具体实现可以参考
util/env_posix.cc
.


后记

我很早之前已经听过leveldb,当时还是想找好的c++代码来学习的时候得知了这个强大的类库.就名声而言,leveldb的名气远没有其兄弟-BigTable tablet来得大.不过这仍然掩盖不了其的闪光点:key-value支持,持久化,高性能的写以及并不差的读性能.

key-value的数据库现在已经有很多,常见的就有:

HBase

memcached

redis

mongodb

Cassandra

除此之外,各个公司也在开源项目的基础上分支了很多出来.之前在腾讯实习的时候,就听说了多款内部在使用的memcached的分布式版本.除此之外,还有部门针对业务开发的kv型数据库.

可以说,在大数据时代下,NoSQL渐渐的成为了主流.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: