RocksDB

简介

RocksDB项目是起源于Facebook,是一个高性能的嵌入式键值存储库,特别适合用于需要低延迟和高吞吐量的场景。RocksDB 借鉴了开源leveldb项目的重要代码以及Apache HBase的想法。最初的代码是从开源 leveldb 1.5 fork出来的。
库组件形式嵌入程序中,为大规模分布式应用在ssd上运行提供优化。RocksDB不提供高层级的操作,例如备份、负载均衡、快照等,而是选择提供工具支持将实现交给上层应用。正是这种高度可定制化能力,允许RocksDB对广泛的需求和工作负载场景进行定制。
很多项目都接纳了RocksDB作为其后端存储的一种解决方案,如Mysql, Ceph, Flink, MongoDB, TiDB等。

RocksDB 架构

RocksDB使用Log-Structured Merge(LSM)trees做为基本的数据存储结构。
在这里插入图片描述

Block Cache

纯内存存储结构,存储SST文件被经常访问的热点数据

一个Cache对象可以被同一个进程中的多个RocksDB实例共享,用户可以控制整体的缓存容量。
块缓存存储未压缩的块。用户可以选择设置存储压缩块的二级块缓存。读取将首先从未压缩的块缓存中获取数据块,然后是压缩的块缓存。如果使用 Direct-IO,压缩块缓存可以替代 OS 页面缓存。

Block Cache有两种缓存实现,分别是 LRUCache 和 ClockCache。两种类型的缓存都使用分片以减轻锁争用。容量平均分配给每个分片,分片不共享容量。默认情况下,每个缓存最多会被分成 64 个分片,每个分片的容量不小于 512k 字节。

  • LRUCache: 默认的缓存实现。使用容量为8MB的基于LRU的缓存。缓存的每个分片都维护自己的LRU列表和自己的哈希表以供查找。通过每个分片的互斥锁实现同步,查找与插入都需要对分片加锁。
    极少数情况下,在块上进行读或迭代的,并且固定的块总大小超过限制,缓存的大小可能会大于容量。如果主机没有足够的内存,这可能会导致意外的 OOM 错误,从而导致数据库崩溃
  • ClockCache: ClockCache 实现了 CLOCK 算法。时钟缓存的每个分片都维护一个缓存条目的循环列表。时钟句柄在循环列表上运行,寻找要驱逐的未固定条目,但如果自上次扫描以来已使用过,也给每个条目第二次机会留在缓存中。
    ClockCache 还不稳定,不建议使用

Write Buffer Manager

Write Buffer Manager 用于控制多个列族或者多个数据库实例的内存表总使用量。
使用方式:用户创建一个write buffer manager对象,并将对象传递到需要控制内存的列族或数据库实例中。

有两种限制方式:

  1. 限制 memtables 的总内存用量

触发其中一个条件将会在实例的列族上触发flush操作:

1. 如果活跃的 memtables 使用超过阈值的90%
2. 总内存超过限制,活跃的 mamtables 使用也超过阈值的 50% 时。
  1. memtable 的内存占用转移到 block cache

大多数情况下,block cache中实际使用的block远小于block cache中缓存的,所以当用户启用该功能时,block cache容量将覆盖block cache和memtable两者的内存使用量。
如果用户同时开启 cache_index_and_filter_blocks,那么RocksDB的三大内存区域(index and filter cache, memtables, block cache)内存占用都在block cache中。

SST文件格式

BlockBasedTable 是 SSTable 的默认表格式。

<beginning_of_file>
[data block 1]
[data block 2]
...
[data block N]
[meta block 1: filter block]                  (see section: "filter" Meta Block)
[meta block 2: index block]
[meta block 3: compression dictionary block]  (see section: "compression dictionary" Meta Block)
[meta block 4: range deletion block]          (see section: "range deletion" Meta Block)
[meta block 5: stats block]                   (see section: "properties" Meta Block)
...
[meta block K: future extended block]  (we may add more meta blocks in the future)
[metaindex block]
[Footer]                               (fixed size; starts at file_size - sizeof(Footer))
<end_of_file>

数据块 DataBlock:键值对序列按照根据排序规则顺序排列,划分为一系列数据块(data block)。这些块在文件开头一个接一个排列,每个数据块可选择性压缩。

元数据块 MetaBlock:紧接着数据块的是一堆元数据块(meta block),元数据块包括:过滤块(filter block)、索引块(index block)、压缩字典块(compression dictionary block)、范围删除块(range deletion block)、属性块(properties block)。

MetaIndexBlock: 元索引块包含一个映射表指向每个meta block,key是meta block的名称,value是指向该meta block的指针,指针通过offset、size指向数据块。

页脚 Footer:文件末尾是固定长度的页脚。包括指向metaindex block的指针,指向index block的指针,以及一个magic number。

Meta Block的具体种类

索引块 Index Block

索引块用于查找包含指定key的数据块。是一种基于二分搜索的数据结构。一个文件可能包含一个索引块,也可能包含一组分区索引块,这取决于使用配置。即存在全局索引与分区索引两种索引方式。

过滤器块 Filter Block

全局过滤器、分区过滤器,都是通过用布隆过滤器实现
全局过滤器 Full Filter: 在此过滤器中,整个 SST 文件只有一个过滤器块。
分区过滤器 Partitioned Filter: Full Filter 被分成多个子过滤器块,在这些块的顶层有一个索引块用于将key映射到相应的子过滤器块。
压缩字典块 Compression Dictionary Block

包含用于在压缩/解压缩每个块之前准备压缩库的字典。

范围删除块 Range Deletion Block

范围删除块包含文件中key与序列号中的删除范围。在读请求下发到sst的时候能够从sst中的指定区域判断key是否在deleterange 的范围内部,存在则直接返回NotFound。memtable中也有一块区域实现同样的功能。
compaction或者flush的时候会清除掉过时的tombstone数据。

属性块 Properties Block

包含属性信息。
统计块格式:

 [prop1] 每个property都是一个键值对    
 [prop2]
 ...
 [propN]

属性信息保证顺序且没有重复,默认情况下包含了以下信息:

 data size               // data block总大小
 index size              // index block总大小
 filter size             // filter block总大小
 raw key size            // 所有key的原始大小
 raw value size          // 所有value的原始大小
 number of entries
 number of data blocks

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,用于记录那些未被成功提交的数据操作,然后在重启时进行数据的恢复。
  • SSTFile,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操作的时候等等。

使用

java使用rocksdb

添加依赖

首先,你需要在项目中添加 RocksDB 的依赖。如果你使用 Maven,可以在 pom.xml 中添加以下依赖:

<dependency>
    <groupId>org.rocksdb</groupId>
    <artifactId>rocksdbjni</artifactId>
    <version>6.29.3</version> <!-- 请使用最新版本 -->
</dependency>

基本操作

以下是一些基本的 RocksDB 操作示例:

import org.rocksdb.Options;
import org.rocksdb.RocksDB;
import org.rocksdb.RocksDBException;

public class RocksDBExample {
    static {
        RocksDB.loadLibrary();
    }

    public static void main(String[] args) {
        try (final Options options = new Options().setCreateIfMissing(true)) {
        //打开数据库
            try (final RocksDB db = RocksDB.open(options, "path/to/db")) {
            	//插入数据
                db.put("key1".getBytes(), "value1".getBytes());
                //读取数据
				byte[] value = db.get("key1".getBytes());
				if (value != null) {
				    System.out.println(new String(value));
				}
				//删除
				db.delete("key1".getBytes());
				//迭代数据
				try (final RocksIterator iterator = db.newIterator()) {
				    for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) {
				        System.out.println(new String(iterator.key()) + ": " + new String(iterator.value()));
				    }
				}
            }
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
    }
}

关闭

如果没有使用try-with-resource,需要关闭数据库

db.close();
options.close();

配置

RocksDB 提供了许多配置选项来优化性能和行为。以下是一些常见的配置:

setCreateIfMissing(true):如果数据库不存在则创建。
setMaxOpenFiles(5000):设置最大打开文件数。
setWriteBufferSize(64 * 1024 * 1024):设置写缓冲区大小。
setMaxWriteBufferNumber(4):设置最大写缓冲区数量。

列族

在 RocksDB 中,列族(Column Families) 是一种将键值对分组管理的机制。每个列族都是一个独立的命名空间,允许在同一数据库中存储多个逻辑上独立的数据集。db可以认为是数据库,列族对应数据库中的表。列族的使用可以提高数据管理的灵活性,并支持更高效的查询和操作。

如果没有指定 Column Family,键值对将会结合到“default” 列族。

列族的特点

  • 独立命名空间:每个列族有自己独立的键值对集合。
  • 共享 WAL(Write-Ahead Log):所有列族共享同一个 WAL,确保原子性。
  • 独立配置:每个列族可以有自己的配置(如压缩策略、内存大小等)。
  • 高效查询:可以单独查询某个列族的数据,减少扫描范围。

列族的基本操作

import org.rocksdb.*;

import java.util.ArrayList;
import java.util.List;

public class RocksDBColumnFamilyExample {
    static {
        RocksDB.loadLibrary();
    }

    public static void main(String[] args) {
        try (final Options options = new Options().setCreateIfMissing(true);
             final ColumnFamilyOptions cfOptions = new ColumnFamilyOptions().optimizeUniversalStyleCompaction()) {

            // 定义列族名称
            final List<ColumnFamilyDescriptor> cfDescriptors = new ArrayList<>();
            cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOptions));
            cfDescriptors.add(new ColumnFamilyDescriptor("cf1".getBytes(), cfOptions));
            cfDescriptors.add(new ColumnFamilyDescriptor("cf2".getBytes(), cfOptions));

            // 打开数据库并获取列族句柄
            final List<ColumnFamilyHandle> cfHandles = new ArrayList<>();
            try (final RocksDB db = RocksDB.open(options, "path/to/db", cfDescriptors, cfHandles)) {

                // 获取默认列族和自定义列族的句柄
                ColumnFamilyHandle defaultCfHandle = cfHandles.get(0);
                ColumnFamilyHandle cf1Handle = cfHandles.get(1);
                ColumnFamilyHandle cf2Handle = cfHandles.get(2);

                // 插入数据到不同列族
                db.put(cf1Handle, "key1".getBytes(), "value1".getBytes());
                db.put(cf2Handle, "key2".getBytes(), "value2".getBytes());

                // 从列族中读取数据
                byte[] value1 = db.get(cf1Handle, "key1".getBytes());
                byte[] value2 = db.get(cf2Handle, "key2".getBytes());

                System.out.println("cf1:key1 -> " + new String(value1));
                System.out.println("cf2:key2 -> " + new String(value2));

                // 关闭列族句柄
                for (ColumnFamilyHandle handle : cfHandles) {
                    handle.close();
                }
            }
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
    }
}

对列族的put/get/delete操作时,要指定列族

列族的管理

创建新列族

ColumnFamilyHandle newCfHandle = db.createColumnFamily(new ColumnFamilyDescriptor("new_cf".getBytes(), cfOptions));

删除

db.dropColumnFamily(cfHandle);
cfHandle.close(); // 关闭句柄

列出所有列族

List<byte[]> columnFamilies = RocksDB.listColumnFamilies(new Options(), "path/to/db");
for (byte[] cfName : columnFamilies) {
    System.out.println(new String(cfName));
}
  1. 列族的配置

每个列族可以有自己的配置选项,例如:

  • 压缩策略:setCompressionType(CompressionType.SNAPPY_COMPRESSION)
  • 内存大小:setWriteBufferSize(64 * 1024 * 1024)
  • 合并操作:optimizeUniversalStyleCompaction()

示例:

ColumnFamilyOptions cfOptions = new ColumnFamilyOptions()
    .setCompressionType(CompressionType.SNAPPY_COMPRESSION)
    .setWriteBufferSize(64 * 1024 * 1024);
RocksDB 是一个高效的嵌入式键值存储引擎,广泛用于需要高性能读写操作的场景。它最初由 Facebook 开发,基于 Google 的 LevelDB 项目,并进行了大量优化以支持现代硬件环境,特别是 SSD 存储介质。 ### 功能特性 RocksDB 提供了丰富的功能,包括但不限于: - **多线程压缩**:通过并行处理 SST 文件的压缩任务来提高吞吐量。 - **内存管理**:提供了多种机制来控制内存使用,比如 block cache 和 memtable 的大小调整。 - **快照与事务**:支持快照隔离级别的一致性视图和 ACID 事务[^3]。 - **列族(Column Families)**:允许将数据逻辑上划分为不同的列族,每个列族可以独立配置选项,这为不同类型的数据显示了灵活性[^3]。 ### 使用教程 要开始使用 RocksDB,首先需要安装库文件。对于 C++ 开发者来说,可以通过源码编译安装或者利用包管理器进行安装。Python 用户则可以借助 `python-rocksdb` 这样的第三方模块。 以下是一个简单的 RocksDB 数据库创建和基本操作的例子: ```cpp #include <rocksdb/db.h> #include <rocksdb/options.h> int main() { rocksdb::DB* db; rocksdb::Options options; // Optimize RocksDB. This is important for performance. options.IncreaseParallelism(); options.OptimizeLevelStyleCompaction(); // create the DB if it's not already present options.create_if_missing = true; // open DB rocksdb::Status status = rocksdb::DB::Open(options, "/tmp/testdb", &db); assert(status.ok()); // put key-value status = db->Put(rocksdb::WriteOptions(), "key1", "value"); assert(status.ok()); std::string value; // get value status = db->Get(rocksdb::ReadOptions(), "key1", &value); assert(status.ok()); assert(value == "value"); // delete database object before exit delete db; return 0; } ``` ### 性能优化 为了获得最佳性能,对 RocksDB 的参数进行调优是必要的。例如,调整内存相关的设置如 `write_buffer_size` 和 `max_write_buffer_number` 可以影响写入性能;而 `block_cache_size` 则会影响读取速度。此外,还可以根据工作负载的特点选择合适的压缩算法和策略。 在 Flink 中使用 RocksDB 作为状态后端时,可以通过修改配置文件中的参数来进行调优,比如增加 `state.backend.rocksdb.memory.managed` 设置为 `true` 来启用托管内存模式,从而让 Flink 更好地管理和限制 RocksDB 的内存占用[^2]。 ### 应用场景 RocksDB 被设计成可以在各种环境中高效运行,尤其是在那些要求低延迟、高吞吐量以及大规模数据集的情况下。典型的应用场景包括: - **分布式数据库系统**:如 TiKV 和其他分布式数据库系统采用 RocksDB 作为底层存储引擎,因为其出色的性能表现和对 LSM-Tree 结构的支持。 - **实时数据分析平台**:Apache Flink 等流处理框架中,RocksDB 常被用作状态后端,因为它能够很好地处理连续不断的数据流带来的状态更新需求[^1]。 - **缓存服务**:由于 RocksDB 支持快速查找且具有良好的压缩特性,因此也非常适合用来构建本地缓存解决方案。 以上只是关于 RocksDB 的简要介绍及其一些核心方面的概述。实际应用中可能还需要深入理解具体的业务需求和技术细节才能充分发挥 RocksDB 的潜力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值