Leveldb 使用说明文档

原作者:Jeff Dean, Sanjay Ghemawat

翻译:乌合之众solym@sohu.com

英文原文地址https://rawgit.com/google/leveldb/master/doc/index.html

leveldb库提供持久性键值存储。 键和值可以是任意字节数组。 根据用户指定的比较函数,在键值存储器内对键进行排序。

打开一个数据库

leveldb打开数据库需要指定一个文件系统目录。 数据库的所有内容都存储在此目录中。 以下示例显示如何打开数据库,如有必要,可创建该数据库:

  #include <cassert>
#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());
...

如果要在数据库 已存在时返回错误 ,请在leveldb::DB::Open调用之前添加以下行:

  options.error_if_exists = true;

状态 Status

你也许已经注意到了上面的leveldb::Status类型。leveldb中可能遇到错误的大多数函数都返回此类型的值。 您可以检查这样的结果是否正确,并打印相关的错误消息:

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

关闭数据库

使用完数据库后,只需删除数据库对象。 例:

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

读和写 Reads And Writes

数据库提供PutDeleteGet方法来修改/查询数据库。 例如,以下代码将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);

原子更新 Atomic Updates

注意,如果进程在Put Key2 之后、Delete key1之前结束(挂了),会使得value被同时保存在多个key下。使用WirteBatch类进行原子更新一组操作,可以避免这些问题:

  #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也可以用于通过将大量的单个操作放入同一批次来加速批量更新。

异步写 Synchronous Writes

默认情况下,每次写入leveldb都是异步的:将写入从进程推送到操作系统后立即返回。从操作系统内存到底层持久存储的传输是异步的。 对于特定的写操作,可以打开同步标志,以使写操作不会返回,直到正在写入的数据真正写入永久存储器。 (在Posix系统上,这是通过在写操作返回之前调用fsync(...)或fdatasync(...)或msync(...,MS_SYNC)实现的。

  leveldb::WriteOptions write_options;
write_options.sync = true;
db->Put(write_options, ...);

异步写操作通常比同步写操作快一千倍。 异步写入的缺点是,机器的崩溃可能导致最后几个更新丢失。 请注意,只是写入进程(即不是机器重新启动)的崩溃不会导致任何损失 ,因为即使syncfalse,更新从进程内存推送到操作系统在之后即认为完成。

通常可以安全地使用异步写入。 例如,将大量数据加载到数据库中时,可以通过在崩溃后重新启动批量加载来处理丢失的更新。 混合方案也是可能的,其中每第N次写入是同步的,并且在崩溃的情况下,批量加载刚好在由前一次运行完成的最后同步写入之后重新启动。 (同步写入可以更新描述崩溃时重新启动位置的标记。)

WriteBatch提供了异步写入的替代方法。 多个更新可以放置在同一WriteBatch中,并使用同步写入(即,write_options.sync设置为true)一起应用。 同步写入的额外成本将在批次中的所有写入之间摊销。

并发 Concurrency

数据库只能由一个进程一次打开。leveldb实现从操作系统获取以防止误用。 在单个进程中,同一个leveldb::DB对象可以安全地由多个并发线程共享。 即,不同的线程可以写入或获取迭代器或在没有任何外部同步的情况下在同一数据库上调用Get(leveldb实现将自动执行所需的同步)。

但是其他对象(如IteratorWriteBatch)可能需要外部同步。如果两个线程共享这样的对象,它们必须使用自己的锁定协议保护对它的访问。 更多详细信息在公共头文件中可用。

迭代器 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的,表示read操作应该在某个特定版本的DB状态。 如果ReadOptions::snapshot为NULL,则读取操作将对当前状态的隐式快照进行操作

快照由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是比返回std::string的更廉价替代方法,因为我们不需要复制潜在的大keyvalue。此外,leveldb方法不返回以null结束的C风格字符串,因为leveldb键和值允许包含\0字节。

C++ std::string和以null结尾的C风格字符串可以很容易地转换为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使用时,Slice所指向的外部字节数组保持有效。 例如,以下是buggy:

   leveldb::Slice slice;
if (...) {
std::string str = ...;
slice = str; // str是一个临时对象,会被析构
}
Use(slice);

当出if语句范围时,str将被销毁,slice的内指针指向的内存将被释放。

比较器 Comparators

前面的例子使用了key的默认排序函数,它按字节顺序排列。 然而,您可以在打开数据库时提供自定义比较器。 例如,假设每个数据库键由两个数字组成,我们应该按第一个数字排序,用第二个数字断开关系(这里实际上就是按这两个数字组合比较大小)。 首先,定义一个表示这些规则的leveldb::Comparator的适当子类:

  class TwoPartComparator : public leveldb::Comparator {
public:
// 三种比较结果:
// if a < b: 结果为负
// if a > b: 结果为正
// else: 结果为0
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; // a1==b1
if (a2 > b2) return +1; // a1==b1
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 { }
};

现在创建一个数据库,使用自定义的比较器comparator:

  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);
...

后向兼容性 Backwards compatibility

比较器的Name方法的结果在创建时附加到数据库,并在每次后续数据库打开时检查。 如果Name更改,leveldb::DB::Open调用将失败。

因此,当且仅当新的key格式比较函数(comparison function)与现有数据库不兼容,并且可以丢弃所有现有数据库的内容时,可对Name修改。

然而,您仍然可以通过一些预先计划逐渐演变您的key格式。例如,您可以在每个键的末尾存储一个版本号(一个字节应该足矣满足大多数用途)。

当您希望切换到新的键格式(例如,在TwoPartComparator处理的键添加一个可选的第三部分),

(a)保持相同的比较器名称

(b)增加新键的版本号

(c)更改比较器函数,以便它根据在key中找到的版本号决定如何解析它们。

性能 Performance

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

块大小 Block size

leveldb相邻的键组合在一起成为同一个块,并且这样的块访问持久存储器的传送单元(一次读写大小)。

默认块大小约为4096个未压缩字节。 如果应用程序主要对数据库内容进行批量扫描,可能希望增加此大小。性能测量表明有改进,那么对小value进行大量定位读取的应用程序可能希望切换到较小的块大小。 使用小于1KB或大于几MB的块没有太大的好处。 还要注意,压缩对于更大的块大小将更有效。

压缩 Compression

每个块在被写入永久存储器之前被单独压缩。 压缩默认情况下处于打开状态,因为默认压缩方法非常快,并且会自动禁用不可压缩数据。 在极少数情况下,如果应用程序可能希望完全禁用压缩,应该这样做,基准显示性能改进:

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

缓存 Cache

数据库的内容存储在文件系统中的一组文件中,并且每个文件存储压缩块的序列。 如果options.cache为非NULL,它用于缓存常用的未压缩块内容。

  #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实现)。

当执行批量读取时,应用程序可能希望禁用高速缓存,使得由批量读取处理的数据不会最终取代大部分高速缓存的内容。 设置每个迭代器选项可以实现这一点:

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

键布局 Key Layout

注意,磁盘传输和高速缓存以一个块(Block)为单位。 相邻的键(根据数据库排序顺序)通常放置在同一个块中。 因此,应用程序可以通过放置彼此靠近在一起的键,并将不常使用的键放置在键空间的单独区域中来提高其性能。

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

   filename -> permission-bits, length, list of file_block_ids
file_block_id -> data

我们可能需要用一个字母前缀标识文件名keys(比如/),并用不同的字母标识file_block_idkeys(比如0),以便只扫描元数据而不强迫我们提取和缓存庞大的文件内容。

过滤器 Filters

由于leveldb数据在磁盘上的组织方式,单个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;

上述代码将基于BoolmFilter的过滤策略与数据库相关联。 基于Boolm过滤器的过滤依赖于在每个Key的存储器中保持一些数量的数据bits(在这种情况下每个密钥是10位,因为这是我们传递给NewBloomFilterPolicy的参数)。 此过滤器将Get()调用所需的 不必要磁盘读取次数 减少大约100倍。增加每个键的bits位将导致更大的减少,但是以更多的内存使用为代价。我们建议不适合将工作集放置在内存中的应用程序,多设置一些随机读取过滤策略。

如果您使用自定义比较器,则应确保您使用的过滤器策略与比较器兼容。 例如,考虑一个比较器,在比较键时忽略尾随空格。 NewBloomFilterPolicy不能与这样的比较器一起使用。 因此,应用程序也应提供一个也忽略尾部空格的自定义过滤器策略。 例如:

  class CustomFilterPolicy : public leveldb::FilterPolicy {
private:
FilterPolicy* builtin_policy_;
public:
CustomFilterPolicy() : builtin_policy_(NewBloomFilterPolicy(10)) { }
~CustomFilterPolicy() { delete builtin_policy_; } const char* Name() const { return "IgnoreTrailingSpacesFilter"; } void CreateFilter(const Slice* keys, int n, std::string* dst) const {
// 删除尾随空格后使用内置bloom filter代码
std::vector<Slice> trimmed(n);
for (int i = 0; i < n; i++) {
trimmed[i] = RemoveTrailingSpaces(keys[i]);
}
return builtin_policy_->CreateFilter(&trimmed[i], n, dst);
} bool KeyMayMatch(const Slice& key, const Slice& filter) const {
// 删除尾随空格后使用内置bloom filter代码
return builtin_policy_->KeyMayMatch(RemoveTrailingSpaces(key), filter);
}
};

Advanced应用程序可以提供不使用Bloom过滤器,但使用一些其他的机制来总结一组键的过滤策略。 有关详细信息,请参阅leveldb/filter_policy.h

检验和 Checksums

leveldb将校验和与其存储在文件系统中的所有数据相关联。 提供了两个单独的控件来对这些校验进行主动验证:

  • ReadOptions::verify_checksums 可以设置为true,以强制代表特定读取从文件系统读取的所有数据的校验和验证。 默认情况下,不进行此类验证。
  • Options::paranoid_checks 可以在打开数据库之前设置为true,以使数据库实现在检测到内部损坏时立即引发错误。 根据数据库的哪个部分已损坏,可能在打开数据库时或稍后由另一个数据库操作引发错误。 默认情况下,paranoid检查关闭,以便即使数据库的持久存储器的部分已损坏也可以使用数据库。
  • 如果数据库损坏(或者当打开paranoid checking时失败),leveldb::RepairDB函数可以用于尽可能多地恢复数据。

大致大小 Approximate Sizes

GetApproximateSizes方法可用于获取一个或多个key范围使用的文件系统空间的大致字节数。

   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);

前面的调用会将size[0]和size [1]设置为key范围[a..c)和[x..z)使用的文件系统空间的大致字节数。

环境 Environment

leveldb实现发出的所有文件操作(和其他操作系统调用)都通过一个leveldb::Env对象进行转调。老练的客户可能希望提供自己的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可以通过提供由leveldb/port/port.h导出特定平台的types/methods/functions实现而被移植到新的平台。相关更多详细信息,请参阅leveldb/port/port_example.h

此外,新平台可能需要一个新的默认leveldb::Env实现。 有关示例,请参见leveldb/util/env_posix.h

其它信息 Other Information

有关leveldb实现的细节可以在以下文档中找到:

Leveldb 使用说明文档的更多相关文章

  1. 【腾讯GAD暑期训练营游戏程序班】游戏场景管理作业说明文档

    场景管理作业说明文档                              用了八叉树的算法,测出三层时最快,区域范围内物体数量为21块,控制台打印出的结果如图所示: 场景物体:游戏中,所有具有空 ...

  2. 浏览器内核控制Meta标签说明文档

    浏览器内核控制Meta标签说明文档 原文链接 背景介绍 由于众所周知的情况,国内的主流浏览器都是双核浏览器:基于Webkit内核用于常用网站的高速浏览.基于IE的内核用于兼容网银.旧版网站.以360的 ...

  3. OAuth2.0说明文档

    OAuth2.0说明文档 1.OAuth 2.0 简介 OAuth为应用提供了一种访问受保护资源的方法.在应用访问受保护资源之前,它必须先从资源拥有者处获取授权(访问许可),然后用访问许可交换访问令牌 ...

  4. Net 通用权限管理系统源码 带数据库设计文档,部署说明文档

    Net 通用权限管理系统源码 带数据库设计文档,部署说明文档 包括数据库设计文档部署安装文档源码数据库文件 下载地址:http://www.mallhd.com/archives/1389

  5. SWFUpload 2.5.0版 官方说明文档 中文翻译版

    原文地址:http://www.cnblogs.com/youring2/archive/2012/07/13/2590010.html#setFileUploadLimit SWFUpload v2 ...

  6. 在Sharepoint 2010中启用Session功能的说明文档

    在Sharepoint 2010中启用Session功能的说明文档 开发环境:Windows 7系统,SharePoint Server 2010,Visual Studio 2010 按以下步骤进行 ...

  7. Highcharts选项配置详细说明文档(zz)

    http://www.helloweba.com/view-blog-156.html Highcharts提供大量的选项配置参数,您可以轻松定制符合用户要求的图表,目前官网只提供英文版的开发配置说明 ...

  8. Java基础(60):Java打包生成Jar和Javadoc说明文档,以及在另外的工程中导入和使用自己的Jar

    一.Jar包的导出 1.在Package Explorer中选中项目,右键,点击“Export”   2.在弹出框一次选择Java-->JAR file,点击Next   3.在新弹出的窗口选择 ...

  9. JAVA 文档注释,类的说明,HTML说明文档的生成

    有的时候,我们会写一些类,编译成.class文件,给别人使用,那么,别人不知道这个类有哪些方法,如何调用. 所以我们需要做一个类的说明文档. 可以采用在.java类里面进行注释,通过注释来生成类的说明 ...

随机推荐

  1. IP组播

    1  IP组播基础 IP组播技术有效地解决了单点发送.多点接收的问题.组播源只发送一份数据,被传递的信息在距组播源尽可能远的网络节点才开始被复制和分发,并且只发送给需要该信息的接收者.  说明: 本章 ...

  2. protobuf 嵌套示例

    1.嵌套 Message message Person { required string name = 1; required int32 id = 2;        // Unique ID n ...

  3. img设置默认图片最简单的解决方法

    <img src='图片的路径' onerror='this.src="如果图片不存在,则使用该图片"' 这个解决方法除了简单外,还有一个优点. 就是当你不知道图片是否存在, ...

  4. iOS:viewController 和 view 的生命周期、不错的代码设计风格

    一.介绍: viwe和viewController的生命周期是最基本的知识,如果很好地理解它们的方法调用的执行顺序,就能很好地设计代码的风格.这篇博客转载自:http://www.cnblogs.co ...

  5. 深度学习Github排名,很不错的介绍

    今天看到这篇文章,把深度学习github排名靠前的项目,介绍了一下,很不错: https://blog.csdn.net/yH0VLDe8VG8ep9VGe/article/details/81611 ...

  6. iOS开发-16进制颜色转换

    项目中经常会用到颜色转换,有的是通过十六进制转成数字转颜色,想简单的点直接通过字符串转一下,简单扩展了一下分类UIColor,代码如下: +(UIColor *)colorWithHex:(NSStr ...

  7. SAP ABAP编程 取得用户中文名称

    有时候我们知道SAP当前用户登录的ID,也就是SY-UNAME.能够取得用户中文名称.例如以下: ***取得用户中文名称 DATA: g_sheet_jsr TYPE string.  "用 ...

  8. 使用TensorFlow动手实现一个Char-RNN

    https://blog.csdn.net/thriving_fcl/article/details/72565455 前言 学习RNN的时候很多人应该都有看过Andrej Karpathy写的The ...

  9. 测试数据——有效范围(2)

    测试数据库搞好,学习了一下逾期率的官方定义: • 对于某支标,如果某一期没有正常还款,则悲观逾期率=所有未还本金/借款本金: • 对于一批标,悲观逾期率=当前逾期标的所有未还本金/借款本金: • 以3 ...

  10. OpenGL ES Shader语言中的函数不支持递归

    An example function definition is given here for a simple function that computes basic diffuse light ...