共计 4919 个字符,预计需要花费 13 分钟才能阅读完成。
起源
RocksDB 我的项目是起源于 Facebook,是一款作为各种存储介质上的服务器工作负载的存储引擎,最后专一于疾速存储(尤其是闪存存储)。它是一个 C++ 库,用于存储任意大小的字节流的键和值。它反对点查找和范畴扫描,并提供不同类型的 ACID 保障。
在可定制性和自适应性之间获得均衡。RocksDB 具备高度灵便的配置设置,能够调整为在各种生产环境中运行,包含 SSD、硬盘、ramfs 或近程存储。它反对各种压缩算法和良好的生产反对和调试工具。另一方面,还致力限度旋钮的数量,提供足够好的开箱即用性能,并在实用的中央应用一些自适应算法。
RocksDB 借鉴了开源 leveldb 我的项目的重要代码以及 Apache HBase 的想法。最后的代码是从开源 leveldb 1.5 fork 进去的。
很多我的项目都接收了 RocksDB 作为其后端存储的一种解决方案,如 Mysql, Ceph, Flink, MongoDB, TiDB 等。
我的项目链接:
leveldb: https://github.com/google/lev…
rocksdb: https://github.com/facebook/r…
对于 LSM 树
LSM 树,即日志构造合并树(Log-Structured Merge-Tree)。其实它并不属于一个具体的数据结构,它更多是一种数据结构的设计思维。大多 NoSQL 数据库核心思想都是基于 LSM 来做的,只是具体的实现不同。
传统关系型数据库应用 btree 或一些变体作为存储构造,能高效进行查找。但保留在磁盘中时它也有一个显著的缺点,那就是逻辑上相离很近但物理却可能相隔很远,这就可能造成大量的磁盘随机读写。随机读写比程序读写慢很多,为了晋升 IO 性能,咱们须要一种能将随机操作变为程序操作的机制,于是便有了 LSM 树。
从概念上说,最根本的 LSM 是很简略的。将之前应用一个大的查找构造(造成随机读写,影响写性能),变换为将写操作程序的保留到一些类似的有序文件(也就是 sstable)中。所以每个文件包 含短时间内的一些改变。因为文件是有序的,所以之后查找也会很快。文件是不可批改的,他们永远不会被更新,新的更新操作只会写到新的文件中。读操作查看很 有的文件。通过周期性的合并这些文件来缩小文件个数。
架构
RocksDB 是一个基于键值对存储接口的存储引擎库,其中键和值是任意字节流。RocksDB 将所有数据按排序程序组织起来,罕用的操作有 Get(key), NewIterator(), Put(key, val), Delete(key), 和 SingleDelete(key)。
RocksDB 的三个根本构造是 memtable、sstfile 和 logfile。memtable 是一种内存数据结构 – 新的写入被插入到 memtable 中,并且能够抉择写入日志文件(又名。Write Ahead Log(WAL))。日志文件是存储上按程序写入的文件。当 memtable 填满时,它会被刷新到存储上的 sstfile,并且能够平安地删除相应的日志文件。对 sstfile 中的数据进行排序以不便查找键。
RocksDB 应用布隆过滤器来断定键在哪个 sst 文件中。为了防止随机写,它将数据积攒到内存中的 memtable 中,而后一次性刷写到硬盘中。RocksDB 的文件是不可变的,一旦生成就不会持续写该文件。记录不会被更新或者删除,会生成一个新文件。这会在硬盘生成一些多余的数据,会须要数据库 Compaction(压缩),Compaction 文件会移除冗余的键值对并腾出空间,如图所示
RocksDB 用不同的排列组织数据,也就是层 level,每层都有个指标大小,每层的指标大小增长倍数是雷同的,默认是 10 倍,因而,如果第一层指标大小 1g,那么 2,3,4 层大小就是 10g,100g,1000g,一个键可能呈现在不同的层,随着 compaction,然而越新的值层越高,越旧的值层越低。
RocskDB 和 LevelDB 的区别
构造和 levelDB 大同小异,只是多了一些改良
- 减少了 column family,有了列簇的概念,可把一些相干的 key 存储在一起
- 内存中有多个 immute memtalbe,可避免 Leveldb 中的 write stall(写进展)
- 可反对多线程同时 compaction,实践上多线程同时 compction 会比一个线程 compaction 要快
- 反对 TTL 过期淘汰机制
- flush 与 compation 离开不同的线程池来调度,并具备不同的优先级,flush 要优于 compation,这样能够放慢 flush,避免 stall
- 对 SSD 存储做了优化,能够以 in-memory 形式运行
- 减少了对 write ahead log(WAL)的管理机制,更方便管理 WAL,WAL 是 binlog 文件
- 反对多种不同的 compaction 策略
RocksDB 子模块
RocksDB 5 大子模块,别离为:
- Basic Operation,基本操作定义
- Terminology,外部术语定义
- Tool,外部工具
- Logging/Monitoring , 日志和监控
- System Behavior,外部零碎行为
Basic Operation
除了 RocksDB 外围的 KV 的操作接口 get,put 两类操作外,RocksDB 还在此模块中封装了如下几类能实用于非凡应用场景的操作:
Iteration,Rocks DB 可能反对区间范畴内的 key 迭代器的遍历查找。
Compaction Filter,用户可应用 Compaction Filter 对 key 值进行删除或其它更新操作的逻辑定义,当零碎进行 Compact 行为的时候。
Creating and Ingesting SST files,当用户想要疾速导入大批量数据到零碎内时,能够通过线下创立无效格局的 SST 文件并导入的形式,而非应用 API 进行 KV 值的独自 PUT 操作。
Delete Range,区间范畴的删除操作,比一个个 Key 的独自删除调用应用更不便。
Low Priority Write,当用户执行大批量数据 load 的操作时但放心可能会影响到零碎失常的操作解决时,能够开启此属性进行优先级的调整。
Read-Modify-Write,这个操作的理论含意是 Merge 操作的含意,读取现有键值,进行更新 (累加计数或依赖原有值的任何更新操作),将新的值写入到原 Key 下。如果应用原始 Get/Set API 的前提下,咱们要调用 2 次 Get 1 次,而后再 Set 1 次,在 Merge API 下,使用者调用 1 次就足够了。
Transaction,RocksDB 外部提供乐观式的 OptimisticTransactionDB 和乐观式(事务锁形式) 的 TransactionDB 来反对并发的键值更新操作。
Terminology
首先是 RocksDB 外部的相干术语定义阐明,如上图所示,次要有以下一些术语:
Write-Ahead-Log File,相似于 HDFS JournalNode 中的 editlog,用于记录那些未被胜利提交的数据操作,而后在重启时进行数据的复原。
SST File,SST 文件是一段排序好的表文件,它是理论长久化的数据文件。外面的数据依照 key 进行排序能不便对其进行二分查找。在 SST 文件内,还额定蕴含以下非凡信息:
Bloom Fileter,用于疾速判断指标查问 key 是否存在于以后 SST 文件内。
Index / Partition Index,SST 外部数据块索引文件疾速找到数据块的地位。
Memtable,内存数据结构,用以存储最近更新的 db 更新操作,memtable 空间写满后,会触发一次写出更新操作到 SST 文件的操作。
Block Cache,纯内存存储构造,存储 SST 文件被常常拜访的热点数据。
System Behavior
在 RocksDB 外部,有着许多零碎操作行为来保障系统的安稳运行。
Compression,SST 文件内的数据可能被压缩存储来减小占用空间。
Rate Limit 行为。用户可能对其写操作进行速度管制,以此防止写入速度过快造成零碎读提早的景象。
Delete Schedule,系统文件删除行为的速度管制。
Direct IO,RocksDB 反对绕过零碎 Page Cache,通过利用内存从存储设置中间接进行 IO 读写操作。
Compaction,数据的 Compact 行为,删除 SST 文件中反复的 key 以及过期的 key 数据。
Logging/Monitoring
RocksDB 外部有以下的日志监控工具:
Logger,可用的 Logger 应用类。
Statistic / Perf Context and IO Stats Context,RocksDB 外部各类型操作的工夫,操作数计数统计信息,此数据信息能被用户用来发现零碎的性能瓶颈操作。
EventListener,此监听接口提供了一些 event 事件产生后的接口回调,比方实现一次 flush 操作,开始 Compact 操作的时候等等。
联合官网 Example 剖析代码
int main() {
DB* db;
Options options;
// 优化 Rocksdb 的配置
options.IncreaseParallelism();
options.OptimizeLevelStyleCompaction();
options.create_if_missing = true;
// 关上数据库
Status s = DB::Open(options, kDBPath, &db);
assert(s.ok());
// 操作数据库
s = db->Put(WriteOptions(), "key1", "value");
assert(s.ok());
std::string value;
s = db->Get(ReadOptions(), "key1", &value);
assert(s.ok());
assert(value == "value");
// 原子化操作数据库
{
WriteBatch batch;
batch.Delete("key1");
batch.Put("key2", value);
s = db->Write(WriteOptions(), &batch);
}
s = db->Get(ReadOptions(), "key1", &value);
assert(s.IsNotFound());
db->Get(ReadOptions(), "key2", &value);
assert(value == "value");
// 应用 PinnableSlice 缩小拷贝
{
PinnableSlice pinnable_val;
db->Get(ReadOptions(), db->DefaultColumnFamily(), "key2", &pinnable_val);
assert(pinnable_val == "value");
}
{
std::string string_val;
PinnableSlice pinnable_val(&string_val);
db->Get(ReadOptions(), db->DefaultColumnFamily(), "key2", &pinnable_val);
assert(pinnable_val == "value");
assert(pinnable_val.IsPinned() || string_val == "value");
}
PinnableSlice pinnable_val;
s = db->Get(ReadOptions(), db->DefaultColumnFamily(), "key1", &pinnable_val);
assert(s.IsNotFound());
pinnable_val.Reset();
db->Get(ReadOptions(), db->DefaultColumnFamily(), "key2", &pinnable_val);
assert(pinnable_val == "value");
pinnable_val.Reset();
delete db;
return 0;
}
更多技术分享浏览我的博客:
https://thierryzhou.github.io
本文由 mdnice 多平台公布