RocksDB使用小结

RocksDB是Facebook的高效数据库系统,基于LSM树实现,支持内存和磁盘存储,适用于高速存储硬件。文章介绍了RocksDB的原理,如LSM树、memtable、sstfile和logfile,以及其特性,如压缩、WAL、事务日志和多线程compaction。此外,还详细阐述了RocksDB的使用方法,包括打开、关闭数据库、读写操作、批处理和同步异步写入。最后,提到了编译RocksDB的相关信息。
摘要由CSDN通过智能技术生成

一、介绍
        RocksDB是Facebook的一个实验项目,目的是希望能开发一套能能在服务器压力下,真正发挥高速存储硬件(特别是Flash存储)性能的高效数据库系统。这是一个C++库,允许存储任意长度二进制kv数据。支持原子读写操作。RocksDB依靠大量灵活的配置,使之能针对不同的生产环境进行调优,包括直接使用内存,使用Flash,使用硬盘或者HDFS。支持使用不同的压缩算法,并且有一套完整的工具供生产和调试使用。RocksDB大量复用了levedb的代码,并且还借鉴了许多HBase的设计理念。原始代码从leveldb 1.5 上fork出来。同时Rocksdb也借用了一些Facebook之前就有的理念和代码。
官网:RocksDB中文网 | 一个持久型的key-value存储
源码下载:GitHub - facebook/rocksdb: A library that provides an embeddable, persistent key-value store for fast storage.
文档:欢迎使用RocksDB RocksDB中文网 | 一个持久型的key-value存储

二、原理

LSM树

RocksDB 是一个快速存储系统,它会充分挖掘 Flash or RAM 硬件的读写特性,支持单个 KV 的读写以及批量读写。RocksDB 自身采用的一些数据结构如 LSM/SKIPLIST 等结构使得其有读放大、写放大和空间使用放大的问题。

LSM-Tree(Log-Structured-Merge-Tree)

LSM树而且通过批量存储技术规避磁盘随机写入问题。 LSM树的设计思想非常朴素, 它的原理是把一颗大树拆分成N棵小树, 它首先写入到内存中(内存没有寻道速度的问题,随机写的性能得到大幅提升),在内存中构建一颗有序小树,随着小树越来越大,内存的小树会flush到磁盘上。磁盘中的树定期可以做 merge 操作,合并成一棵大树,以优化读性能。

memtable, sstfile 和 logfile

RocksDB 的三个基本结构是 memtable, sstfile 和 logfile。

memtable 是一个内存数据结构,新写入的数据被插入到 memtable 中,并可选地写入日志文件。

日志文件是存储上顺序写入的文件。当 memtable 填满时,它被 flush 到存储上的 sstfile ,然后可以被安全地删除。sstfile 中的数据顺序存放,以方便按 key 进行查找。

compaction style

RocksDB支持两种compaction style(level style和universal style)。这两种style可做读放大、写放大、空间放大之间做tradeoff。compaction也支持多线程,所以打的DB可以支持高性能的compaction。

基于 RocksDB 设计存储系统,要考虑到应用场景进行各种 tradeoff 设置相关参数。譬如,如果 RocksDB 进行 compaction 比较频繁,虽然有利于空间和读,但是会造成读放大;compaction 过低则会造成读放大和空间放大;增大每个 level 的 comparession 难度可以减小空间放大,但是会增加 cpu 负担,是运算时间增加换取使用空间减小;增大 SSTfile 的 data block size,则是增大内存使用量来加快读取数据的速度,减小读放大。

WAL

WAL (write ahead log):WAL主要作用是用来恢复发生 rocksdb非优雅退出(节点断电,死机) 时 memtable中的未commited中的数据。所以WAL 的写入需要优先于memtable,且每一次写入都需要flush。

避免停顿

后台压缩线程也负责将 memtable 内容刷新到存储上的文件。如果所有后台压缩线程都忙于执行长时间运行的压缩,那么突然的写入操作可以快速填满memtable ,从而新的写入操作将会卡顿。这种情况可以通过配置 RocksDB 保留一小段线程来避免,这些线程显式保留用于将 memtable 刷新到存储器的唯一目的。

压缩过滤器

一些应用程序可能希望在压缩时对数据做一些处理。例如,具有对生存时间(TTL)的固有支持的数据库,可以移除过期的 key。这可以通过应用程序定义的压缩过滤器来完成。如果应用程序想要连续删除超过特定时间的数据,它可以使用压缩过滤器删除已过期的记录。RocksDB 压缩过滤器让应用程序修改 key 的值或完全删除 key 作为压缩过程的一部分。例如,应用程序可以作为压缩的一部分连续运行数据清理程序。

ReadOnly 模式

数据库可以以只读模式打开,其中数据库保证应用程序不会修改数据库中的任何内容。这导致高得多的读取性能,因为被横穿的代码路径完全避免了锁的开销。

数据库调试日志

RocksDB 将详细日志写入名为 LOG* 的文件。这些主要用于调试和分析正在运行的系统。该日志可以被配置为以指定的周期滚动。

数据压缩

RocksDB 支持 snappy,zlib,bzip2,lz4 和 lz4_hc 压缩。RocksDB 可以配置为在不同级别的数据上支持不同的压缩算法。通常 90% 的数据在 Lmax 级别。

典型的安装可能配置无压缩级别 L0-L2,snappy 压缩中级和 zlib 压缩 Lmax。

事务日志

RocksDB 将事务存储到日志文件中以防止系统崩溃。在重新启动时,它会重新处理日志文件中记录的所有事务。日志文件可以配置为存储在与 _sstfile_s 不同的目录中,比如某些场景,你可能会将所有数据文件存储在非持久性快速存储器中,同时,您可以通过将所有事务日志放在较慢但持久的存储上确保不会有数据丢失。

完全备份,增量备份和复制

RocksDB 支持完全备份和增量备份。RocksDB 是一个 LSM 数据库引擎,因此,一旦创建,数据文件就不会被覆盖,这使得很容易提取与数据库内容的时间点快照相对应的文件名列表。API DisableFileDeletions 指示 RocksDB 不要删除数据文件。压缩将继续发生,但数据库不需要的文件将不会被删除。然后,备份应用程序可以调用 API GetLiveFiles / GetSortedWalFiles 以检索数据库中的活动文件列表,并将它们复制到备份位置。备份完成后,应用程序可以调用 EnableFileDeletions ; 数据库现在可以自由回收所有不再需要的文件。

增量备份和复制需要能够找到并 tail 数据库的所有最近更改。API GetUpdatesSince 允许应用程序在 RocksDB 事务日志上执行 tail 操作。它可以从RocksDB 事务日志中连续获取事务,并将它们应用到远程复制副本或远程备份。

复制系统通常希望用一些元数据注释每个 Put。该元数据可以用于检测复制管道中的循环。它也可以用于时间戳和顺序事务。为此,RocksDB 支持一个称为 PutLogData 的 API,应用程序可以使用该 API 来为每个 Put 添加元数据。此元数据仅存储在事务日志中,不存储在数据文件中。通过 PutLogData 插入的元数据可以通过 GetUpdatesSince API 来获取。

RocksDB 事务日志在数据库目录中创建。当不再需要日志文件时,将其移动到归档目录。留在归档目录的原因是落后的复制流可能需要从日志文件中检索过去的事务。API GetSortedWalFiles 返回所有事务日志文件的列表。

在同一个进程中支持多个嵌入式数据库

RocksDB 的一个常见用例是应用程序固有地将其数据集分区为逻辑分区或分片。这种技术有利于应用程序负载平衡和从故障快速恢复。这意味着单个服务器进程需要能够同时操作多个 RocksDB 数据库。这通过名为 Env 对象完成。除此之外,线程池也与 Env 关联。如果应用程序想要在多个数据库实例之间共享公共线程池(用于后台压缩),那么它应该使用相同的 Env 对象来打开这些数据库。

类似地,多个数据库实例可以共享相同的块高速缓存。

块缓存 - 压缩和未压缩数据

RocksDB 使用 LRU 缓存来提供读取。块高速缓存被分割成两个单独的高速缓存:第一高速缓存是未压缩块,第二高速缓存是压缩块,它们都存在 RAM 中。如果配置了压缩块高速缓存,则数据库智能地避免在 OS buffer 中缓存数据。

表缓存

表缓存是一种用于缓存打开的文件描述符的结构。这些文件描述符用于 sstfile。应用程序可以指定表缓存的最大大小。

外部压缩算法

LSM 数据库的性能在很大程度上取决于压缩算法及其实现。RocksDB 有两个支持的压缩算法:LevelStyle 和 UniversalStyle。我们还希望使大型开发人员能够开发和实验其他压缩策略。因此,RocksDB 有适当的钩子关闭内置的压缩算法,并提供 API 允许应用程序操作自己的压缩算法。

Options.disable_auto_compaction(如果设置)禁用本机原生的压缩算法。GetLiveFilesMetaData API 允许外部组件访问数据库中的每个数据文件,并决定哪个文件可以合并和压缩。DeleteFile API 允许应用程序删除被视为已过期的数据文件。

非阻塞数据库访问

一些应用程序希望他们仅在数据调用是非阻塞的时候,才从数据库获取数据,即数据获取调用不需要从存储器中读取数据。RocksDB 将数据库的一部分缓存在块缓存中,因此这些应用程序希望仅在该块缓存中找到数据时才访问数据。如果这个调用没有在块缓存中找到数据,那么 RocksDB 会向应用程序返回一个适当的错误代码。应用程序然后可以调度正常的 Get / Next 操作,并且理解该数据访问调用可能潜在地被访问存储器(可能在不同的线程上下文中)的 IO阻塞。

可堆叠 DB

RocksDB 有一个内置的包装机制,可以在数据库内核之上添加功能。此功能由 StackableDB API 提供。例如,TTL 功能由 StackableDB 实现,而不是核心 RocksDB API 的一部分。这种方法保持代码模块化和干净。

可备份数据库

使用 StackableDB 接口实现的一个功能是 BackupableDB,这使得 RocksDB 的备份变得简单。参看附录链接更多了解如何备份 RocksDB [3]。

Memtable

可插拔 memtable

RocksDB 的 memtable 的默认实现是一个 skiplist。skiplist 是一个有序集,当工作负载使用 range-scans 并且交织写入时,这是一个必要的结构。

然而,一些应用程序不交织写入和扫描,而一些应用程序根本不执行范围扫描。对于这些应用程序,排序集可能无法提供最佳性能。因此,RocksDB 支持可插拔的 API,允许应用程序提供自己的 memtable 实现。

开发库提供了三个 memtable:skiplist memtable,vector memtable 和前缀散列(prefix-hash) memtable。

  • Vector memtable 适用于将数据批量加载到数据库中。每个写入在向量的末尾插入一个新元素; 当它是刷新 memtable 到存储的时候,向量中的元素被排序并写出到 L0 中的文件。

  • 前缀散列 memtable 允许对 gets,puts 和 scans-within-a-key-prefix 进行有效的处理。

Memtable 管道

RocksDB 支持为数据库配置任意数量的 memtable。当 memtable 已满时,它变成不可变的 memtable,后台线程开始将其内容刷新到存储。同时,新的写入继续累积到新分配的 memtable。如果新分配的 memtable 被填充到其限制,它也被转换为不可变的 memtable 并被插入到 flush 管道中。后台线程继续将所有流水线不可变的 memtables 刷新到存储。这种流水线提高了 RocksDB 的写吞吐量,尤其是在慢速存储设备上运行时。

Memtable 压缩

当 memtable 被 flush 到存储时,内联压缩过程从输出流中删除重复记录。类似地,如果较早的 put 被稍后的删除隐藏,那么 put 根本不会写入输出文件。此功能大大减少了存储和写入放大数据的大小。这是 RocksDB 用作生产者 - 消费者队列时的一个基本特征,特别是当队列中的元素的生命周期非常短的时候。

合并 Merge 操作

RocksDB 本地支持三种类型的记录:Put 记录,Delete 记录和 Merge 记录。当压缩过程遇到 Merge 记录时,它调用应用程序指定的称为 Merge 的方法。合并可以将多个 Put 和 Merge 记录合并成一个。这个强大的功能允许通常执行读 - 修改 - 写的应用程序完全避免读。它允许应用程序将操作意图记录为合并记录,RocksDB 压缩过程将该意图延迟应用于原始值。此功能在合并运算符中详细描述。

三、使用方法

打开一个数据库

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

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

原子化更新

主意,如果进程在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系统,可以在写操作返回前,调用fsyn

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

byxdaz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值