分类

安卓应用安卓游戏

编程软件 数据库类

RocksDB(可嵌入数据库)

RocksDB(可嵌入数据库) v6.16.4官方版 附教程

大小:7.4 MB

语言:简体中文系统:WinXP, Win2003, Vista, Win7, Win8, Win10

类别:数据库类时间:2021-04-01 08:17

RocksDB是一款可嵌入持久型的key-value数据库,C++编写,具有完整的数据库引擎,包括数据库的编辑、删除、创建、打开、合并和读写等功能,快速低延迟,能够最大限度的发挥闪存和RAM的高度率读写性能,适用于多种不同工作量类型。

软件功能

1、高性能

RocksDB使用一套日志结构的数据库引擎,为了更好的性能,这套引擎是用C++编写的。 Key和value是任意大小的字节流。

2、为快速存储而优化

RocksDB为快速而又低延迟的存储设备(例如闪存或者高速硬盘)而特殊优化处理。 RocksDB将最大限度的发挥闪存和RAM的高度率读写性能。

3、可适配性

RocksDB适合于多种不同工作量类型。 从像MyRocks这样的数据存储引擎, 到应用数据缓存, 甚至是一些嵌入式工作量,RocksDB都可以从容面对这些不同的数据工作量需求。

4、基础和高级的数据库操作

RocksDB提供了一些基础的操作,例如打开和关闭数据库。 对于合并和压缩过滤等高级操作,也提供了读写支持。

RocksDB使用教程

基础操作

Rocksdb库提供一个持久化的键值存储。健和值都可以是任意二进制数组。所有的键按爪一个用户定义的比较函数排列。

打开一个数据库

一个rocksdb数据库会有一个与文件系统目录关联的名字。所有与该数据库有关的内容都会存储在那个目录里。下面的例子将展现如何打开一个数据库,有必要的时候创建他:

#include

#include "rocksdb/db.h"

rocksdb::DB* db;

rocksdb::Options options;

options.create_if_missing = true;

rocksdb::Status status = rocksdb::DB::Open(options, "/tmp/testdb", &db);

assert(status.ok());

...

如果你希望在数据库存在的时候返回错误,在调用rocksdb::DB::Open调用前,增加下面这行。

options.error_if_exists = true;

如果你正在从leveldb迁移到rocksdb,你可以使用rocksdb::LevelDBOptions把你的leveldb::Options对象转成rocksdb::Options对象,他有与leveldb::Options一样的功能。

#include "rocksdb/utilities/leveldb_options.h"

rocksdb::LevelDBOptions leveldb_options;

leveldb_options.option1 = value1;

leveldb_options.option2 = value2;

...

rocksdb::Options options = rocksdb::ConvertOptions(leveldb_options);

RocksDB选项

如上所示,用户可以选择总是在代码里明确设置选项的内容。或者,你可以通过一个字符串到字符串的map,或者一个选项字符串来设置。参考选项字符串和选项Map

有一些选项可以在DB运行过程中动态修改。例如:

rocksdb::Status s;

s = db->SetOptions({{"write_buffer_size", "131072"}});

assert(s.ok());

s = db->SetDBOptions({{"max_background_flushes", "2"}});

assert(s.ok());

RocksDB自动将当前数据库使用的配置保存到数据库目录下的OPTIONS-xxx文件。用户可以选择在数据库重启之后从配置文件导出选项,以此来保存配置。参考 RocksDB配置文件

状态(Status)

你可能已经注意到上面的rocksdb::Status类型。这个类型的值是大部分rocksdb的返回值,他有时会返回一个错误。你可以检测这个结果是不是ok,然后打印相关的错误信息:

rocksdb::Status s = ...;

if (!s.ok()) cerr << s.ToString() << end

关闭一个数据库

当你使用完这个数据库,只需要删除这个数据库对象即可:

... 按上面的描述打开数据库 ...

... 对这个数据库做一些操作 ...

delete db;

读与写

数据库提供Put,Delete以及Get方法用于修改/查询数据库。例如,下面的代码将key1的值,移到key2。

std::string value;

rocksdb::Status s = db->Get(rocksdb::ReadOptions(), key1, &value);

if (s.ok()) s = db->Put(rocksdb::WriteOptions(), key2, value);

if (s.ok()) s = db->Delete(rocksdb::WriteOptions(), key1);

目前,值的大小必须小于4GB。RocksDB还允许使用 单删除,这个在特定场景很有用。

每个Get请求至少需要使用一次从源到目的字符串的值memcpy拷贝。如果源在块缓存,你可以使用一个PinnableSlice来避免额外的拷贝。

PinnableSlice pinnable_val;

rocksdb::Status s = db->Get(rocksdb::ReadOptions(), key1, &pinnable_val);

源数据会在pinnable_val被销毁或者::Reset被调用的时候释放。参考这里

原子化更新

主意,如果进程在key2的Put调用之后,在删除key1之前,崩溃了,那么相同的值会被保存到多个键下面。这种问题可以通过WriteBatch类来原子地写入一批更新:

#include "rocksdb/write_batch.h"

...

std::string value;

rocksdb::Status s = db->Get(rocksdb::ReadOptions(), key1, &value);

if (s.ok()) {

rocksdb::WriteBatch batch;

batch.Delete(key1);

batch.Put(key2, value);

s = db->Write(rocksdb::WriteOptions(), &batch);

}

WriteBatch保存一个数据库编辑序列,这些批处理的修改会被按顺序应用于数据库。主意我们在Put之前使用Delete,所以如果key1和key2相同,我们不会错误地将这个值删掉。

除了他原子化的有点,WriteBatch还可以通过将大量的单独修改合并到一个批处理,以此加速批量更新。

批量写

rocksdb的写请求默认都是同步的:他在进程把写请求压入操作系统后返回。从操作系统的内存写入之下的持续存储介质的过程是异步的。可以对某个特定的写请求使用sync标识位,使之在数据完全被写入持久化存储介质之前都不会反回。(在Posix系统,可以在写操作返回前,调用fsync或者fdatasync或者msync。)

rocksdb::WriteOptions write_options;

write_options.sync = true;

db->Put(write_options, ...);

异步写

通常异步写会比同步写快上千倍。异步写的缺点是机器崩溃的时候可能会造成最后一个更新操作丢失。主意,如果只是写操作的进程崩溃,即使sync标示没有设置为真,也不会造成数据丢失,在他的写操作返回成功前,更新操作会从进程的内存压入操作系统。

异步写通常可以被安全地使用。例如,在加载一大批数据的时候,你可以通过重启批量加载操作来处理由于崩溃导致的数据丢失。一个混合方案是,你可以每隔几个写操作,就使用一次同步写,当崩溃发生的时候,批量导入可以从上一次运行的最后一次同步写那里继续。(同步写可以更新一个标记,用于纪录下次重启的时候应该从哪里开始)。

WriteBatch提供另一个异步写的方案。可以把许多更新打包在一个WriteBatch里面,然后一次性用一个同步写导入数据库。(write_options.sync被设置为真)。同步写的开销会被该批量写的所有写请求均匀分摊。

我们还提供一个办法,在有必要的时候,可以彻底关闭WAL。如果你把write_option.disableWAL设置为true,写操作完全不会写日志,并且在进程崩溃的时候出现数据丢失。

同步

一个数据库可能同时只能被一个进程打开。RocksDB的实现方式是,从操作系统那里申请一个锁,以此来阻止错误的写操作。在单进程里面,同一个rocksdb::DB对象可以被多个同步线程共享。举个例子,不同的线程可以同时对同一个数据库调用写操作,迭代遍历操作或者Get操作,而且需要不用使用额外的同步锁(rocksdb的实现会自动进行同步)。然而其他对象(比如迭代器,WriteBatch)需要额外的同步机制保证线程同步。如果两个线程共同使用这些对象,他们必须使用自己的锁协议保证访问的同步。更多的细节会在公共头文件给出。

合并操作符

合并操作符为 读-修改-写 操作提供高效的支持。更多的接口和实现参考:

合并操作符

合并操作符开发

迭代器

下面的例子展示如何打印一个数据库里的所有键值对(key,value)。

rocksdb::Iterator* it = db->NewIterator(rocksdb::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;

The following variation shows how to process just the keys in the range [start, limit):

for (it->Seek(start);

it->Valid() && it->key().ToString() < limit;

it->Next()) {

...

}

assert(it->status().ok()); // Check for any errors found during the scan

你还可以通过反向的顺序处理这些项目。(警告:反响迭代器会比正向迭代器慢一些)

for (it->SeekToLast(); it->Valid(); it->Prev()) {

...

}

assert(it->status().ok()); // Check for any errors found during the scan

这里有一个例子展示如何以逆序处理一个范围(limit,start]的键

for (it->SeekForPrev(start);

it->Valid() && it->key().ToString() > limit;

it->Prev()) {

...

}

assert(it->status().ok()); // Check for any errors found during the scan

参考 SeekForPrev

对于错误处理的解释,不同的迭代选项和最佳实践,参考 迭代器

了解更多的实现细节,参考: 迭代器的实现

快照

快照在整个kv存储之上提供一个一致的,制度的视图。 ReadOptions::snapshot不是NULL的时候意味着这个读操作应该在某个特定的数据库状态版本进行。

如果 ReadOptions::snapshot是NULL,读操作隐式地认为使用当前数据库状态进行读操作。

快照通过DB::GetSnapshot方法获得:

rocksdb::ReadOptions options;

options.snapshot = db->GetSnapshot();

... apply some updates to db ...

rocksdb::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()的返回值类型是rocksdb::Slice类型。Slice是一个简单的结构体,他有一个长度字段和一个指针指向一个外部的字节数组。相比返回一个std::string类向,返回一个Slice是一个开销更低的选项,因为我们不必另外去拷贝那些可能会很大的键值对。另外,rocsdb的方法不会返回以null结束的c风格字符串,因为rocksdb的键值都是允许使用'\0'字符的。

c++字符串和null结束的c风格字符串,都可以简单地转换为slice:

rocksdb::Slice s1 = "hello";

std::string str("world");

rocksdb::Slice s2 = str;

一个Slice可以简单地转换会c++字符串:

std::string str = s1.ToString();

assert(str == std::string("hello"));

使用slice的时候要小心,因为需要由调用者来保证外部的字节数组在Slice使用期间存活。比如,下面这个代码就是有bug的:

rocksdb::Slice slice;

if (...) {

std::string str = ...;

slice = str;

}

Use(slice);

当if声明结束的时候,str会被析构,然后slice存储的数据就消失了。

事务

RocksDB现在支持多操作事务。参考 事务

比较器

前面的例子使用默认的排序函数对键值进行排序,也就是使用字典顺序排列。你也可以在打开数据库的时候使用自定义的比较器。例如,假如每个数据库的键值都是两个数字,然后我们应该按第一个数字排序,如果第一个数字相同,按照第二个数字排序。首先,定义一个合适的rocksdb::Comparator子类,实现下面的规则:

class TwoPartComparator : public rocksdb::Comparator {

public:

// Three-way comparison function:

// if a < b: negative result

// if a > b: positive result

// else: zero result

int Compare(const rocksdb::Slice& a, const rocksdb::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 rocksdb::Slice&) const { }

void FindShortSuccessor(std::string*) const { }

};

现在,使用自定义的比较器打开数据库

TwoPartComparator cmp;

rocksdb::DB* db;

rocksdb::Options options;

options.create_if_missing = true;

options.comparator = &cmp;

rocksdb::Status status = rocksdb::DB::Open(options, "/tmp/testdb", &db);

...

列族

列族提供一种逻辑上给数据库分区的方法。用户可以提供一个原子的,跨列族的多键写操作以及通过一个一致的视图读他们。

批量加载

你可以通过创建并导入SST文件来将导入大量的数据直接,批量导入数据库,并且将线上流量做到最小的影响。

备份以及检查点

备份允许用户创建周期性的增量备份到远程文件系统(想想HDFS和S3),然后从他们中的任意一个恢复。

检查点提供一种能力,为线上的RocksDB生成一个快照到一个独立的目录。文件通过硬链接(如果可以的话),而不是拷贝生成,所以这个是相对轻量的一个操作

I/O

默认RocksDB的IO会使用操作系统的页缓存。通过设置 速度限制器可以限制rocksdb的写文件操作速度,给读IO留下空间。

用户也可以选择直接跳过页缓存,使用 直接IO

参考 IO

向后兼容

数据库创建的时候,比较器的 Name方法返回的结果会被追加到数据库,然后每次重新打开的时候都会被检查。如果名称修改了,rocksdb::DB::Open调用会失败。所以,当且仅当新的键组织和比较器跟当前已经存在的数据库有冲突,并且可以丢弃旧的所有数据库的数据的时候,才修改这个名称。

当然,你也可以有计划的一点点地修改你的键格式。例如,你可以在每个键的末尾存一个版本号(对于多数用户,一个byte应该足够了)。当你希望切换到新的键组织的时候,(例如,增加一个可选的第三部分键处理给以前TwoPartComparator处理过的键),(a)保留比较器的名字(b)新的键增加版本号(c)修改比较器,按照版本号决定如何解析他们。

Memtable和table工厂

默认,我们用skiplist memtable保存内存的数据,然后用 RocsDB表格式 描述的表格式保存硬盘的数据。

由于RocksDB的其中一个目标是让系统里面的每个部分都可以是简单的插件,我们支持对memtable和表格式使用不同的实现。你也可以使用自己的memtable工厂,只要设置Options::memtable_factory 和Options::table_factory即可。对于可用的memtable工厂类,请参考rocksdb/memtablerep.h,表格式参考 rocksdb/table.h。这些功能都在开发中,请注意某些API的变化,可能会影响到你的应用走向。

更多关于memtable的内容参考 这里 和 这里

性能

从 设置配置 开始。更多关于RocksDB的性能信息,参考旁边的Performance章节

块大小

rocksdb将一组连续的键打包到一个块,这个块就是跟持久存储的交换单位。默认的块大小是接近4096byte(压缩前)。一些经常需要做区间扫描的程序,可能希望增加这个大小。对于一些大量点查询的应用,如果确实能看到性能提升,会希望使用一个更小的值。使用一个小于1kB的块大小一般不会有特别多的好处,使用大于几个MB同理。主意,压缩对于越大的块效率越高。使用Options::block_size修改块大小

写缓冲区

Options::write_buffer_size选项指定那些还没排序并存入磁盘文件的数据在内存里面可以占用的空间大小。更大的值一般带来更好的性能,特别是在大批量导入数据的时候。同时最多有max_write_buffer_number个写缓冲区在内存,你可以通过这个参数控制内存消耗。同时,更大的写缓冲区意味着数据库重启的时候需要更长的恢复时间。

相关选项是Options::max_write_buffer_number,用于控制内存中写缓冲区的数量。默认为2,这样,当一个写缓冲区正在被落盘的时候,一个新的可以继续服务新的写请求。落盘操作会在一个线程池中进行。

选项Options::min_write_buffer_number_to_merge声明在落盘前,需要合并的写缓冲区的最小数量。如果设置为1,那么所有的写缓冲区都与L0的一个文件对应,这会增加写放大,因为每次读都要检查所有的这些文件。同时,如果每个写缓冲区都有一些重复的键,内存中的合并操作可以减少落盘数据的量。默认值为1。

压缩

每个块都会在写入持久化存储前进行压缩。压缩是默认开启的,因为默认的压缩算法非常快,对于不可压缩的数据,则被自动关闭。在非常罕见的情况下,应用会希望彻底关闭压缩,除非你的压测显示这确实带来了好处,否则不要这么做:

rocksdb::Options options;

options.compression = rocksdb::kNoCompression;

... rocksdb::DB::Open(options, name, ...) ....

同时,我们还提供 字典压缩

缓存

数据库的数据会被存储到文件系统的一系列文件里,每个文件存储一部分压缩好的块。如果 options.block_cache为非NULL,他会被用于缓存最常用的解压后的块的内容。我们使用操作系统来缓存原始的,压缩的数据。文件系统缓存扮演着压缩数据缓存的角色。

#include "rocksdb/cache.h"

rocksdb::BlockBasedTableOptions table_options;

table_options.block_cache = rocksdb::NewLRUCache(100 * 1048576); // 100MB uncompressed cache

rocksdb::Options options;

options.table_factory.reset(rocksdb::NewBlockBasedTableFactory(table_options));

rocksdb::DB* db;

rocksdb::DB::Open(options, name, &db);

... use the db ...

delete db

执行批量读取的时候,应用会希望关闭缓存,这样批量读区就不会污染已经缓存的数据。一个针对迭代器的选项可以做到这个:

rocksdb::ReadOptions options;

options.fill_cache = false;

rocksdb::Iterator* it = db->NewIterator(options);

for (it->SeekToFirst(); it->Valid(); it->Next()) {

...

}

你也可以通过把options.no_block_cache设置为true来关闭块缓存。

参考 块缓存

键分布

注意,缓存与磁盘交换数据的单位是块。连续的键(根据数据库的排序)通常被放在同一个块。所以应用也可以通过把常常一起使用的键放在一起,然后把另一些不常用的放在另一个命名空间,以此提高性能。

例如,加入我们机遇rocksdb开发一个简单的文件系统。每个节点的类型可能这样存储:

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

file_block_id -> data

我们可能希望给filename这个键使用一个前缀(例如'/'),然后给file_block_id使用另一个前缀(例如'0'),这样,扫描元数据的时候就不用关心大量的文件内容信息了。

过滤器

由于Rocksdb在硬盘的数据组织方式,一个Get请求可能会导致多个磁盘读请求。这个时候,FilterPolicy机制就可以用来非常可观地减少磁盘读。

rocksdb::Options options;

rocksdb::BlockBasedTableOptions bbto;

bbto.filter_policy.reset(rocksdb::NewBloomFilterPolicy(10));

options.table_factory.reset(rocksdb::NewBlockBasedTableFactory(bbto));

rocksdb::DB* db;

rocksdb::DB::Open(options, "/tmp/testdb", &db);

... use the database ...

delete db;

delete options.filter_policy;

这段代码需要对数据库使用一个基于 BloomFilter 的过滤策略。基于Bloom Filter的过滤器会在内存保留一部分键的内容(这里是10bit,因为我们传递给NewBloomFilter的参数就是这个)。这个过滤器可以在Get请求的时候减少大概100倍不必要的磁盘读。增大每个键的bit数会导致更多的削减,但是回来带更多的内存使用。我们推荐那些数据没法全部存储在内存,但是有大量随机读的应用使用过滤策略。

如果你使用自定义的比较器,你需要保证你的过滤策略跟你的比较器是兼容的。例如,如果有一个比较器,比较键的时候,不关心末尾的空格。那么,NewBloomFilter就不能给这种比较器使用了。作为替代,应用应该提供一个自定义的过滤策略,忽略掉这些末尾的空格。

比如说:

class CustomFilterPolicy : public rocksdb::FilterPolicy {

private:

FilterPolicy* builtin_policy_;

public:

CustomFilterPolicy() : builtin_policy_(NewBloomFilter(10, false)) { }

~CustomFilterPolicy() { delete builtin_policy_; }

const char* Name() const { return "IgnoreTrailingSpacesFilter"; }

void CreateFilter(const Slice* keys, int n, std::string* dst) const {

// Use builtin bloom filter code after removing trailing spaces

std::vector trimmed(n);

for (int i = 0; i < n; i++) {

trimmed = RemoveTrailingSpaces(keys);

}

return builtin_policy_->CreateFilter(&trimmed, n, dst);

}

bool KeyMayMatch(const Slice& key, const Slice& filter) const {

// Use builtin bloom filter code after removing trailing spaces

return builtin_policy_->KeyMayMatch(RemoveTrailingSpaces(key), filter);

}

};

上面的应用提供一个不是使用bloom filter的过滤策略,而是使用其他的策略来提取一个键值集合的值。参考rocksdb/filter_policy.h

校验和

rocksdb会给所有存储在文件系统的数据添加校验和。有两个独立的方法控制校验和的校验强度。

ReadOptions::verify_checksums 强制要求某个读请求对所有的从硬盘读的数据都要检查校验和。这个是默认开的。

Options::paranoid_checks 如果在打开数据库的时候被设置为了true,那么数据库的实现就会在他检测到校验和错误的时候返回一个错误。根据数据库具体出错的部位,这个错误可能在数据库打开的时候就反回,也可能在后续的其它操作反回。默认paranoid_checks为false,这样即使部分持久化数据出错,DB也还可以继续运作。

如果db崩溃了(比如paranoid_checks为true时打开失败了),rocksdb::RepairDB方法可能可以用来尽可能地恢复数据。

压缩

RocksDB会不停地重写已经存在的数据文件。这是为了丢掉已经过期的数据,并且保证数据结构对读友好。

与压缩有关的内容已经移动到 压缩章节,用户操作RocksDB前不需要知道哪部压缩过程。

估算大小

GetApproximateSizes方法可以用于获得一个或多个键占用的文件系统的空间的大概值。

rocksdb::Range ranges[2];

ranges[0] = rocksdb::Range("a", "c");

ranges[1] = rocksdb::Range("x", "z");

uint64_t sizes[2];

rocksdb::Status s = db->GetApproximateSizes(ranges, 2, sizes);

上面的代码会把sizes[0]填写成键空间[a...c)占用的文件系统大概的大小,然后sizes[1]则会填写成键空间[x..z)的。

环境

所有rocksdb实现的,发起的文件操作(以及其他系统调用),都是通过一个rocksdb::Env对象来实现的。一些熟悉原理的客户可能希望使用自己的Env实现来获得更好的控制。例如,一个应用可能会人为制造一些文件IO的延迟,来限制rocksdb对该系统上的其它活动的影响。

class SlowEnv : public rocksdb::Env {

.. implementation of the Env interface ...

};

SlowEnv env;

rocksdb::Options options;

options.env = &env;

Status s = rocksdb::DB::Open(options, ...);

移植

rocksdb可以通过实现rocksdb/port/port.h导出的类型/方法/函数,来移植到一个新的平台。参考rocksdb/port/port_example.h

另外,移植到一个新的平台可能需要实现一个新的rocksdb::Env。参考 rocksdb/util/env_posix.h

可管理性

为了更有效地调优你的应用, 如果能获得一些统计数据,总是很有用的。你可以通过设置Options::table_properties_collectors或者Options::statistics来收集统计信息。其他信息,可以参考 rocksdb/table_properties.h和 rocksdb/statistics.h。这个不会给你的系统带来太多的负担,我们推荐你把他们导出到其它监控系统。参考 统计, 你也可以使用上下文及IO状态剖析对单一请求进行剖析。用户还可以对一些内部事件注册 事件监听器 。

清理WAL文件

默认,旧的WAL文件会在他们已经掉出范围并且没人需要的时候被自动删除。我们也提供选项允许用户归档日志并且做懒删除,既可以是TTL风格,也可以根据空间限制。

这些选项就是Options::WAL_ttl_seconds和Options::WAL_size_limit_MB。这里展示怎么使用它们

如果都是0,logs会尽可能快的被删除,而且不会被归档。

如果WAL_ttl_seconds是0,但是WAL_size_limit_MB不是0,WAL文件没10分钟会被检查一次,如果它们的总大小大于WAL_size_limit_MB,他会从最早的文件开始删除,直到总大小小于WAL_size_limit_MB。所有空白文件都会被删除

如果WAL_ttl_seconds不是0,而WAL_size_limit_MB是0,那么WAL日志会每WAL_ttl_seconds/2秒就被检查一次,事件晚于WAL_ttl_seconds秒的文件会被删除。

如果都不是零,WAL日志会每10分钟被检查一次,上面的两种检查都会被进行,而且ttl先进行。

常见问题

问:如果我的进程crash了,我的数据库数据会受影响吗?

答:不会,但是如果你没有开启WAL没有刷入到存储介质的memtable数据可能会丢失。

问:如果我的机器crash了,RocksDB能保证数据的完整吗?

答:数据在你调用一个带sync的写请求的时候会被写入磁盘(使用WriteOptions.sync=true的写请求),或者你可以等待memtable被刷入存储的时候。

问:RocksDB会抛异常嘛?

答:不,出错的时候RocksDB会返回一个rocksdb::Status,用于指明错误。然后RocksDB本身也不捕捉来自STL库和其他依赖的异常,所以如果内存不够,你可能会遇到std::bad_malloc异常,或者类似的场景下的类似的异常。

问:如何获取RocksDB里面存储的键值的数量?

答:使用GetIntProperty(cf_handle, “rocksdb.estimate-num-keys") 可以获得存储在一个列族里的键的大概数量。GetAggregatedIntProperty(“rocksdb.estimate-num-keys", &num_keys) 可以获得整个RocksDB数据库的键数量。

问:为什么GetIntProperty只能返回RocksDB总键数的估算值?

答:在像RocksDB这种LSM数据库里获得key的总数量是一个非常有挑战性的问题,因为他们总是存储有一些重复的key和一些被删除的项目,所以如果你需要精确数量,你需要进行一次全压缩。另外,如果RocksDB的数据库使用了合并操作符,这个估算的值将更加不准确。

问:哪些基本操作,Put(),Write(),NewIterator(),是线程安全的吗

答:是的

问:我可以从多个进程同时写RocksDB吗?

答:不可以。然而,你可以在多个进程中用只读模式打开RocksDB。

问:RocksDB支持多进程读吗?

答:RocksDB支持多个进程以只读模式打开rocksDB,可以通过使用DB::OpenForReadOnly()打开数据库。

问:最大支持的key和value的大小是多少

答:RocksDB不是针对大key设计的。推荐的最大key value大小分别为8MB和3GB

问:我可以在HDFS上跑RocksDB吗?

答:可以,使用NewHdfsEnv()接口返回的Env,RocksDB就可以把数据存储到HDFS上了。然而HDFS上目前无法支持文件锁。

问:我可以保存一个snapshot,然后回滚rocksDB到这个状态吗?

答:可以,请使用BackupEngine或者Checkpoints接口

问:如何快速将数据载入rocksdb

答:其中一种方法是直接往RocksDB里面插入数据:

使用单一写线程,然后按顺序插入

将数百个键值在一个写请求插入

使用vector memtable

确保options.max_background_flushes至少为4

开始插入数据前,关闭自动压缩,将options.level0_file_num_compaction_trigger, options.level0_slowdown_writes_trigger and options.level0_stop_writes_trigger 设置到非常大的值。插入完成后,开始一次手动压缩。

3-5条可以通过调用Options::PrepareForBulkLoad()一次完成。

如果你可以离线处理数据,有一个更加快的方法:你可以对数据进行排序,并行生成没有交集的SST文件,然后批量加载这些SST文件接口。参考 创建以及导入SST文件

问: RocksJava已经支持全部功能了嘛?

答:我们还在开发RocksJava。当然,如果你发现某些功能缺失,你也可以主动提交PR。

问:谁在用RocksDB

答:参考 Users

问:如何正确删除一个DB?我可以直接对活动的DB调用DestoryDB吗?

答:先调用close然后调用destory才是正确的方法。对一个活动的DB调用DestoryDB会导致未定义行为。

问:调用DestoryDB和直接删除DB所在的文件夹有什么差别?

答:如果你的数据存放在多个文件夹,DestoryDB会处理好这些文件夹。一个DB可以通过配置DBOptions::db_paths, DBOptions::db_log_dir, 和 DBOptions::wal_dir以将数据存储在不同的目录。

问:BackupableDB会创建一个指定时间的snapshot吗?

答:调用CreateNewBackup的时候,如果BackupOptions::backup_log_files = true或者flush_before_backup为true,就会创建。

问:备份进程会影响到其他数据库访问吗?

答:不会,你可以在备份的同时读写数据库。

问:有什么更好的办法把map-reduce生成的数据导入到rocksDB吗?

答:使用SstFileWriter,它可以让你直接创建RocksDB的SST文件,然后直接把他们加入到数据库。然而,如果你把SST文件加入到已有的RocksDB数据库,那么这些键不能跟已有的键值有交集。参看 创建以及导入SST文件

问:对不同的列族配置不同的前缀提取器安全吗?

答:安全。

问:RocksDB编译需要的最小gcc版本是多少?

答:可以使用4.7。但是推荐4.8及以上的。

问:如何配置RocksDB以使用多个磁盘?

答:你可以在多个磁盘上创建一个文件系统(ext3,xfs,等)。然后你就可以在这个单一文件系统上跑RocksDB了。使用多个磁盘的一些tips:

如果使用RAID,请使用大的stripe size(64kb太小,推荐使用1MB)

考虑把ColumnFamilyOptions::compaction_readahead_size设置到大于2MB以打开压缩预读

如果主要的工作压力是写,可以增加压缩线程以保持磁盘满负载工作。

考虑为压缩打开一步写

问:直接拷贝一个打开的RocksDB实例安全吗?

答:不安全,除非这个实例是以只读模式打开。

问:如果我用一种新的压缩方式打开RocksDB,我还能读到旧的(用其他压缩方式保存的)数据吗?

答:可以,RocksDB会在SST文件里面保存压缩方式,所以就算换了压缩方式,仍旧能读到现有的文件。你甚至可以通过ColumnFamilyOptions::bottommost_compression给最底层换一个压缩算法。

问:我想在HDFS上备份RocksDB,怎么配置呢?

答:使用BackupableDB然后把backup_env设置为NewHdfsEnv()的返回值即可。

问:在压缩过滤器的回调里面读,写rocksDB是不是安全的?

答:读是安全的,但是写不一定总是安全的,因为在要锁过滤器的回调里写数据可能会在触发停写条件的时候造成死锁。

问:生成snapshot 的时候,RocksDB会保留SST文件以及memtable吗?

答:不会,参考 snapshot工作原理

问:如果设置了DBWithTTL,过期的key被删除的时间有保证吗?

答:没有,DBWithTTL不保证一个时间上限。过期的key只在他们被压缩的时候被删除。然而,没人保证这个压缩一定会发生。例如,你有一部分key永远都不会更新,那么压缩基本不会影响这部分key所以他们也不会因过期而被删除。

问:如果我删除了一个列族,但是不删除他的handle,我还能访问这个数据库吗?

答:可以的,DropColumnFamily()只是把特定的列族标记为丢弃,在他的引用减少到0之前都不会被删除。

问:为什么RocksDB在我只做写请求的时候还会发起读请求?

答:这些读请求来自压缩操作。RocksDB的压缩操作会读取不定数量的SST文件,然后进行合并排序之类的操作,生成新的SST文件,然后删除旧的读取的SST文件。

问:RocksDB支持拷贝吗?

答:不支持,RocksDB不直接支持拷贝。然而,他提供一些API可以用来帮忙支持拷贝。例如,GetUpdateSince()允许开发者遍历从某个时间点之后的所有更新。参考。

问:RocksDB的最新版本是哪个?

答:所有在releases的都是稳定版本。对于RocksJava,稳定版本发布在 (https://oss.sonatype.org/#nexus-search;quick~rocksdb

问:block_size是压缩前的大小,还是压缩后的?

答:压缩前的。

问:使用了options.prefix_extrator之后,我有时会看到错误的结果。哪里出错了呢?

答:options.extrator有一些限制。如果使用前缀迭代器,那么他将不支持Prev()或者SeekToLast(),很多操作还不支持SeekToFirst()。一个常见的错误是通过Seek和Prev来找一个前缀的最后一个键。这是不支持的。目前没法通过前缀迭代器来拿到某个前缀的最后一个键。同事,如果所有prefix都查完了,你不能继续迭代这些键。如果你确实需要这些操作,你可以试着将ReadOptions.total_order_seek设置为true来关闭前缀迭代。

问:一个迭代器需要持有多少资源,这些资源会在什么时候被释放?

答:迭代器需要持有数据块以及内存的memtable。每个迭代器需要持有以下资源:

当前的迭代器所指向的所有数据块。参考 迭代器固定的数据块

迭代器创建的时候存在的memtable,即使这些memtable被刷到磁盘,也需要持有

所有在迭代器创建的时候,硬盘上的SST文件。即使这些SST文件被压缩了,也需要持有 这些资源会在迭代器删除的时候被释放

问:我可以把日志文件和sst文件放在不同目录吗?info日志呢?

答:可以,WAL文件可以通过DBOptions::wal_dir指定存放目录,info日志可以通过DBOptions::db_log_dir指定存放目录

问:bloom filter的SST文件总是被加载到内存吗?还是他们会从硬盘加载

答:这是可配置的。 BlockBaseTableOptions::cache_index_and_filter_blocks为true的时候,bloom filter以及索引块会在Get调用的时候被载入一个LRU缓存。否则,RocksDB会尝试加载DBOptions::max_open_files个SST文件的bloom filter文件及索引进入内存。

问:发起一个Put和Write请求的时候,如果设置WriteOptions.sync=true,是不是之前写的数据也会落盘呢?

答:如果之前写的所有请求都是WriteOptions.disableWAL=false的话,是的。

问:我关闭了WAL,然后依赖DB::Flush来落盘数据。在单一列族的时候这很完美。如果我有多个列族,我也能这么做吗?

答:不可以,目前 DB::Flush不是跨列族的原子操作。我们有计划支持这个功能。

问:如果使用非默认的压缩器和合并操作符,我还能用ldb工具吗?

答:这种情况下,常规的ldb工具是不能使用的。然而,你可以把这些东西编译进你的定制ldb工具,然后通过rocksdb::LDBTool::Run(argc, argv, options) 把参数传过去。

问:RocksDB如何处理读写IO错误?

答:如果IO错误发生在前端请求,例如Get和Write的时候,RocksDB会返回一个rocksdb::IOError状态。如果错误发生在后台线程并且options.paranoid_checks为true,我们会切换到只读模式。所有写操作都会被拒绝,并且返回具体的后台的错误。

问:我可以取消某次特定的压缩吗?

答:不可以。你没法指定。

问:如果压缩正在进行,我可以手动关闭这个db吗?

答:不可以,这样不安全。你可以在另一个线程调用CancelAllBackgroundWork来放弃压缩工作,这样你可以更快地关闭DB。

问:如果我用一个不同的压缩方式打开RocksDB,会发生什么?

答:如果用不同的压缩方式打开rocksdb的数据库,一下场景可能会发生:

如果当前LSM布局的压缩方式与配置不兼容,数据库会拒绝打开。

如果新的配置跟当前的LSM布局兼容,rocksdb会继续大开数据库。然而,为了保证新的配置完全生效,rocksdb会做一次全量压缩

问:如何正确删除一个范围的key?

答:参考 这里

问:列族是用来干嘛的?

答:用列族的原因包括:

对不同的数据使用不同的压缩方式,压缩算法, 压缩风格,合并操作符,压缩过滤器。

通过删除一个列族来删除其携带的所有数据。

一个列族用于携带另一个列族的元数据。

问:把数据存在不同的列族和存在不同的rocksdb数据库有什么不同?

答:主要差异在于备份,原子写以及写性能。 使用多个数据库的优点:数据库是备份(backup)和检查点(checkpoint)的单位。拷贝到另一个主机的时候,数据库比列族更方便。是用列族的优点:在一个数据库里,批量写是跨列族原子的。用多个数据库的时候没法做到这个。如果你对WAL调用sync,大量的数据库会损耗性能。

问:RocsDB有列吗?如果没有,那为什么有列族呢?

答:没有,RocksDB没有列。参考 Column Families了解什么是列族

问:RocksDB真的在读的时候无锁吗?

答:下列情况读请求会需要锁:

读区共享的缓存块。

读取options.max_open_files != -1的表缓存

如果一个读请求发生在一次落盘或者压缩之后,他可能会短暂地持有一俄国全局锁,用于加载最新的LSM树的元数据。

RocksDB使用的内存分配器(如jmelloc),有时候会加锁。这些锁通常都是很少出现的,并且都有性能保证。

问:如果我需要更新多个key,我应该用多个Put操作,还是把他们放到一个批量写操作,然后使用Write调用?

答:通常使用一次Write会比多次调用Put获得更好的性能。

问:迭代所有的key的最好方式是什么?

答:如果是一个小的或者只读的数据库,创建一个迭代器然后遍历所有key就行了。否则,考虑隔一段时间就重新创建一个迭代器,毕竟迭代器会持有所有资源,不让他们释放。如果你需要一个一致的视图,创建一个snapshot,然后遍历这个snapshot即可。

问:如果我有不同的键值空间,我应该用不同的前缀区分它们,还是放在不同的列族?

答:如果每个键值空间都很大,放在不同的列族是好的。如果它们可能很小,你应该考虑把几个不同的键值空间打包到一个列族,这样就不用维护好几个不同的列族了。

问:如何估算一次强制全压缩会归还的空间?

答:目前没有一个简单的方法估算到精确值,特别是如果你用了压缩过滤器的时候。如果数据库的大小比较稳定,读取DB属性rocksdb.estimate-live-data-size应该是最好的估算。

问:snapshot,checkpoint和backup有什么区别

答:snapshot是一个逻辑概念,用户可以通过API查询数据,但是底层的压缩还是会覆盖存在的文件。 checkpoint会用同一个Env为所有数据库文件创建一个物理镜像。如果操作系统允许使用硬链接创建镜像文件,那么这个操作就比较轻量。 backup可以把物理的数据库文件移动到其他环境(如HDFS)。backup引擎还支持不同备份之间的增量复制。

问:我应该用哪种压缩风格呢?

答:从把所有层设置为LZ4(或者Snappy)开始,已得到一个好性能。如果你希望减小数据大小,尝试在最后一层实用Zlib。

问:如果没有覆盖或者删除,压缩还有必要吗?

答:即使没有过时数据,压缩仍旧是有必要的,这可以提高读性能。

问:如果我用option.disableWAL=true发起了一个写请求,然后用options.sync=true发起另一个写请求,前面那个请求会被落盘吗?

答:不会。程序崩溃的话,如果option.disableWAL=true的数据没有被刷入sst文件,这些数据就丢了。

问:options.target_file_size_multiplier是用来干嘛的?

答:这是一个非常少用的功能。你可以用这个来减小SST文件的数量。

问:如何区分RocksJava抛出的异常?

答:RocksJava会用RocksDBException封装所有的异常。

问:我观察到写IO有尖峰。我应该如何消除它们?

答:尝试使用限速器:https://github.com/facebook/rocksdb/blob/v4.9/include/rocksdb/options.h#L875-L879

问:不重新打开rocksdb,我能修改压缩过滤器吗?

答:不支持这种操作。然而,你可以通过编写自己的,返回不同压缩过滤器的CompactionFilterFactory来实现这个操作。

问:对于迭代器,Next和Prev的性能是一样的吗?

答:反向迭代器的性能通常比正向迭代差。有几个原因:1,数据块的编码方式对Next更加友好。memtable的跳表是单向的,所以每次Prev调用都是一个新的二分搜索。3,内部的key排序是为Next优化过的。

问:一个数据库最多可以有多少个列族?

答:用户在至少上千个列族的时候,都是不应该看到错误的。然而,列族太多会影响性能。我们不推荐用户使用超过数百个列族。

问:RocksDB可以提供数据库里的key总数吗?或者某个范围内的key总数?

答:rocksDB可以通过DB属性"rocksdb.estimate-num-keys"估算总key数量。注意,如果有合并操作符,写覆盖,删除不存在的键值,这个估算会偏差很大。

估算一个区间的key数量的最好办法是,先调用DB::GetApproximateSizes,然后通过该返回估算一个值。

问:我什么时候应该使用RocksDB repair工具?有什么最佳实践吗?

答:参考 RocksDB Repairer

问:如果我想取10个key出来,是调用MultiGet好还是调用10次Get好?

答:性能很接近。MultiGet从同一个一致视图读取,但是不会更快。

问:我应该怎么处理数据分片?

答:你可以从对每个数据分片用一个rocksdb数据库开始。

问:块缓存的默认大小是多大?

答:8MB。

问:如果我有好几个列族然后在调用DB函数的时候不传列族指针,会发生什么?

答:只会操作默认列族。

问:由于磁盘空间不足,DB操作失败了,我要如何把自己解锁呢?

答:先清理出一些空间。然后你需要重启DB,使之恢复正常。目前没有不重启DB就恢复的方法。

问:ReadOptions,WriteOptions可以被跨线程使用吗?

答:只要他们是不变的,你就可以复用它们。

问:我可以复用DBOptions或者ColumnFamilyOptions来打开多个DB或者列族吗?

答:可以。内部实现上,RocksDB总是会拷贝一次这些选项,所以你可以修改,复用它们。

问:RocksDB支持群提交吗?

答:是的。多个线程提交的多个写请求会被聚集起来。你可以配置其中一个线程为这些请求通过一个写调用写WAL日志并且落盘。

问:有办法只对key做遍历吗?如果可以,这样会比加载key和value更高效吗?

答:不,这通常不会更高效。RocksDB的key和value一般存在一起。当用户遍历key的时候,value已经被加载到内存了,所以不管value不会有太大的提升。在BlobDB,key和value被分开存储,所以他可以在只遍历key的时候有更好的性能,不过这个还没有支持。我们以后可能会支持这个特性。

问:事务对象是线程安全的吗?

答:不是的。你不可以并发地对同一个事务对象进行操作。(当然,你可以平行地对多个事务对象进行操作,毕竟这个是该功能的意义)

问:当迭代器从一个key/value上移开之后,该key/value占用的内存还会被保留吗?

答:不,它们会被释放,除非你设置了ReadOptions.pin_data = true,并且你的设置支持这个功能。

问:如何估算DB索引和过滤块的大小

答:对一个离线的DB,"sst_dump --show_properties --command=none"会给出一个sst文件的索引和过滤器的大小。你可以把它们加起来,得到数据库的总大小。对于运行中的DB,你可以读取DB属性kAggregatedTableProperties。或者对每个独立的文件调用DB::GetPropertiesOfAllTables(),然后把它们加起来。

问:我可以从程序里读取SST文件的数据吗?

答:我们目前不支持这么做。你可以通过sst_dump来导出数据。

∨ 展开

同类推荐

相关下载

热门游戏

下载排行

热门关键字

  • 编程软件
  • 编程控件
  • 编译调试
  • 开发环境
  • 网页制作
  • 安装制作
  • 数据库类
  • Java相关
  • 加壳脱壳
  • 控件下载
  • 源码相关
  • 编程其他