Leveldb官方文档(中文版)

Level

Jeff Dean, Sanjay Ghemawat

       leveldb提供一个持久的键值存储,键和值是任意的字节数组。可以根据用户自定义的键值排序规则,来进行键值存储。


打开数据库

       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.error_if_exists = true;

状态

       你可能已经注意到:以上代码中有leveldb::Status类型,这种类型的值被作为大多数leveldb函数的返回值,以用来标记一个错误。你可以用它来判断函数是否执行成功,也可以打印相关的错误信息。

leveldb::Status s = ...;
if (!s.ok()) cerr << s.ToString() << endl;

关闭数据库

       当对数据库操作完毕后,释放database对象,如下所示:

... open the db as described above ...
... do something with db ...
delete db;

读写操作

       leveldb提供了Put,Delete和Get函数来修改/查询数据库,例如,以下代码将key1的value复制到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);

原子更新(Atomic Updates)

       注意,如果一个进程在插入了key2之后并且在删除key1之前终止,相同的值可能会存储在多个键。这样的问题可以通过使用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类包含了对数据库进行的一系列操作,并且这些操作是有序的。注意我们称之为在Put之前Delete,这样是为了如果key1和key2是相同的,我们最终不会错误的删除其对应的值。除了它的原子性的好处外,writebatch也可以用来加快处理大量的updates,在同一批中添加独立的update。


同步写(Synchronous Writes)

       默认情况下,每次写LevelDB是异步的:它在写操作从当前进程传递到操作系统后就返回了,数据从系统内存到底层持久存储是异步发生的。同步标志可以被打开,此时是为了确保写操作的数据被写入到持久存储中后才返回。(POSIX系统,这是通过调用fsync实施(…)或fdatasync(…)或msync(…,ms_sync)写操作返回之前。)

leveldb::WriteOptions write_options;
write_options.sync = true;
db->Put(write_options, ...);
       异步写入速度通常是同步写入的一千多倍,异步写入的缺点是,机器崩溃可能导致最后几次更新丢失。请注意,一个崩溃的写作过程(即,不是一个重新启动)不会造成任何损失,即使同步写入失败了,在数据真正写入持久存储前该更新是先从进程内存传递到操作系统的。

       异步写入可以安全地使用。例如,当往数据库中写入大量数据时,你可以处理丢失的更新通过在崩溃后重启大容量的写入操作。一种混合方案可能是每N个写是同步的,并且在发生崩溃事件时,批量写入重新启动运是在最后的同步写之前完成的。(同步写可以更新一个标记,说明在崩溃的地方重新启动。)

       writebatch提供了一种替代异步写。多个更新可被放在同一个writebatch中一起使用一个同步写(即write_options.sync设置为true)。该同步写的额外成本将被分摊到所有写操作中。


并发(Concurrency)

       一个数据库只能一次被一个进程打开,Leveldb的实现是从操作系统获取一个锁来防止误操作。在单一进程中,这个leveldb::DB对象可以被通过并发的线程安全地共享。即在没有任何外部同步情况下,不同的线程可以写入、读取迭代器或获得相同的数据库(LevelDB实现会自动同步所需的)。然而其他对象(如迭代器和writebatch)可能需要外部同步,如果2个线程共享这样的对象,他们必须使用他们自己的锁定协议来同步访问,更多的细节可在公共头文件。


迭代(Iteration)

       接下来的例子展示如何打印数据库中的所有键值信息:

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是!NULL的话,表示在特定版本的数据库状态上进行读操作;ReadOptions::snapshot是NULL的话,表示在隐式版本的数据库状态上进行读操作。Snapshots是通过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是一个简单的shu结构,它包含一个长度和一个外部字节数组的指针。返回一个Slice比返回一个标准的std::string类型更有效率,因为我们不需要复制潜在的大的键和值。此外,leveldb方法不返回'\0'结尾的C-type字符串,这样的话,leveldb的键值就可以包含'\0'数据了。

       C++的strings和以'\0'结尾的C-type字符串也已很容易转化为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类型是需注意,要保证其内部的字节数组指针指向内容是可用的,以下代码作为错误示例:

leveldb::Slice slice;
if (...) {
	std::string str = ...;
	slice = str;
}
// 此时slice所指内存已经不可用,有可能发生未知错误
Use(slice);

比较器(Comparators)

       前面的例子对键使用的是默认排序函数,即字典序排序。当打开数据库时,你可以提供自定义比较器。例如,假设每个数据库的键包含2个数字,我们首先按照第一个数字进行排序,相同时在按照第二个数字排序。首先,定义一个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 {
		int a1, a2, b1, b2;
		ParseKey(a, &a1, &a2);
		ParseKey(b, &b1, &b2);
		if (a1 < b1) return -1;
		if (a1 > b1) return +1;
		if (a2 < b2) return -1;
		if (a2 > b2) return +1;
		return 0;
	}

	// 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 { }
};
       现在创建一个使用自定义比较器的数据库:

TwoPartComparator cmp;
leveldb::DB* db;
leveldb::Options options;
options.create_if_missing = true;
options.comparator = &cmp;
leveldb::Status status = leveldb::DB::Open(options, "/tmp/testdb", &db);
...

向后兼容

       比较器的名字方法的结果连接到数据库时创建,并在每一个后续数据库打开时进行检查。如果名字改变了,leveldb::DB::Open会调用失败。因此,改变名字当且仅当新的键值格式与现有的数据库,和比较函数是不相容的,它可以抛弃所有现有数据库的内容。
       你仍然可以逐渐发展关键格式随着时间一点点的前期规划。例如,您可以存储一个版本号结束时每个键(一个字节应该足够了对于大多数使用)。当您希望切换到一个新的密钥格式(如。添加一个可选的第三部分,钥匙由TwoPartComparator处理),(a)保持相同的比较器名称(b)增加新密钥版本号(c)改变比较器功能所以它使用中的版本号决定如何解释他们的关键。


性能

       性能可以被调整,通过改变类型中定义的默认值,其在include/ leveldb / options.h中。


块大小(Block size)

       leveldb组相邻键合为同一块,一块是单位转移到和从持久存储。默认的块大小是大约4096个未压缩字节。应用程序主要是做批量扫描数据库的内容可能希望增加这个尺寸。应用程序做很多点读的小值可能希望切换到一个更小的块大小如果性能测量显示有所改善。并没有太多好处使用块小于1 kb,或比几兆字节更大。还要注意,压缩将更有效和更大的块大小。


压缩(Compression)

       每一块进行单独压缩在写入持久存储之前。压缩在默认情况下是启用的,因为默认的压缩方法是非常快,和自动禁用uncompressible数据。在极少数情况下,应用程序可能希望完全禁用压缩,如果基准显示性能改进时才这样做:

leveldb::Options options;
options.compression = leveldb::kNoCompression;
... leveldb::DB::Open(options, name, ...) ....

缓存(Cache)

       数据库的内容存储在文件系统中的文件和每个文件存储一个序列的压缩块。如果选项打开,缓存是空的,则它是用来缓存经常使用的未压缩的块内容。

#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;
       注意缓存包含的是未压缩的数据,因此它的大小应该和应用程序级的数据大小一致,没有任何减少压缩。(缓存压缩块交给操作系统缓冲区缓存,或提供的任何自定义Env实现客户端)。

       当执行批量读取时,应用程序可能希望禁用缓存,数据处理的大部分阅读不最终取代大部分的缓存内容。一个per-iterator选项可以用来实现这一目标:

leveldb::ReadOptions options;
options.fill_cache = false;
leveldb::Iterator* it = db->NewIterator(options);
for (it->SeekToFirst(); it->Valid(); it->Next()) {
	...
}

键布局(Key Layout)(50%)

       注意,磁盘传输和缓存单元是一个块,相邻的键(根据数据库排序顺序)通常会被放置在同一块。因此应用程序可以改善其性能,在临近的地方访问键,不常使用的键在一个单独的区域空间。

       例如,假设我们实现一个简单的文件系统并且之上,我们可能希望存储条目的类型是:

filename -> permission-bits, length, list of file_block_ids
file_block_id -> data
       我们可能想用一个字母前缀文件名键(' / ')和file_block_id以不同的字母键(比如“0”)所以,扫描不强迫我们获取的元数据和缓存的文件内容。
       因为数据在磁盘上存储,并且一个Get()调用可能会涉及多个磁盘读取操作。可选FilterPolicy机制来大大减少磁盘读取的次数。

leveldb::Options options;
options.filter_policy = NewBloomFilterPolicy(10);
leveldb::DB* db;
leveldb::DB::Open(options, "/tmp/testdb", &db);
... use the database ...
delete db;
delete options.filter_policy;

校验和(Checksums)

       leveldb将校验和与所有数据存储在文件系统中,有两个独立的控制提供了如何积极这些校验和验证:

* ReadOptions:verify_checksums可能设置为true,迫使校验和验证的所有数据从文件系统读取代表一个特定的阅读。默认情况下没有这样的验证。

* Options::paranoid_checks可能设置为true之前打开数据库的数据库实现提高一个错误当它检测到一个内部突变。这取决于数据库的部分已经损坏,错误可能发生在数据库打开是,或者是在另一个数据库操作时。默认情况下,强制性检查是关闭的,即       使它的持久存储部分已损坏,该数据库也可使用。

如果数据库已损坏(也许它不能被打开时,偏执的检查是打开的),该LevelDB::repairdb函数可用于恢复尽可能多的数据。

近似大小(Approximate Sizes)

       GetApproximateSizes()函数可以用于获得键值存储所占用的系统空间的近似大小(单位字节)。

leveldb::Range ranges[2];
ranges[0] = leveldb::Range("a", "c");
ranges[1] = leveldb::Range("x", "z");
uint64_t sizes[2];
leveldb::Status s = db->GetApproximateSizes(ranges, 2, sizes);
       上面的代码将设置sizes[0]为键在范围[a..c)上的所用字节数,同理sizes[1]为键在范围[x..z)上的所用字节数。


环境(Environment)

       所有的文件操作(和其他操作系统的系统调用)的发布实施,是通过leveldb::Env对象。你可能希望得到更好的控制自己提供环境的实现,例如,一个应用程序可以在文件IO路径引入人为的延迟限制在系统中的其他活动LevelDB的影响。

class SlowEnv : public leveldb::Env {
	.. implementation of the Env interface ...
};

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

移植(Porting)

       Leveldb可移植到新的平台通过提供平台相关的 types/methods/functions exported by leveldb/port/port.h函数具体实现,具体请看leveldb/port/port_example.h。

       此外,移植新平台可能需要一个新的默认leveldb::Env实现,请看leveldb/util/env_posix.h作为示例。


其他信息(Other Information)

       关于leveldb的具体实现细节请看以下文档(这些都在源码中的doc文件夹下):

leveldb/doc/impl.html

leveldb/doc/table_format.txt

leveldb/doc/log_format.txt


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章