高性能分布式KV存储引擎RocksDB入门与C/C++编码实战

一、RocksDB项目介绍

RocksDB是由Facebook团队开发的一个嵌入式、持久化的键值(Key-Value)存储数据库,其核心设计基于Google的LevelDB项目。
当时为了应对大规模数据存储与高并发写入场景时遇到的性能瓶颈,而传统的基于B-Tree结构的数据库在随机写入场景下性能表现不佳,因此,Facebook团队基于基于LevelDB的LSM-Tree(Log-Structured Merge-Tree)架构进行深度优化,开发出了RocksDB项目。
RocksDB于2013年正式开源,其开发目标包括:提供高性能的键值存储(尤其适用于闪存存储)、支持快照与事务、支持自定义合并操作、兼容多种编程语言和操作系统等。RocksDB存储引擎开源后迅速在TiKV/TiDB、Kafka Streams、Flink等大型项目中被广泛应用。
RocksDB数据库的诞生是Facebook为解决其业务中的写入性能瓶颈,并充分利用现代存储硬件的特性而启动的工程实践。它不仅继承了LevelDB的优雅设计,又在并发控制、内存管理、压缩算法等方面进行了大幅改进,成为当前最流行的LSM-Tree存储引擎之一。

RocksDB常见的应用场景:

分布式数据库的本地存储,如TiKV等,每个分布式节点采用RocksDB存储分片数据。

写密集业务的日志存储。

区块链开发,构建区块索引。

本地持久化缓存和快照保存。

二、RocksDB架构拆解

RocksDB项目的架构核心是LSM-Tree(Log-Structured Merge-Tree),它是一种面向写密集负载的多层有序结构,可以将随机写转换为顺序写,从而获得极高的写入吞吐量。

RocksDB项目的设计要点:

按顺序写内存:所有数据的写入先写到WAL和Memtable上,避免随机写,适应SSD的硬件特性。

延迟合并:通过Compaction异步整理数据,平衡三种放大因子(写放大、读放大、空间放大)。

负载参数可配置:早期配置继承自LevelDB,后逐步解耦并加入参数如write_buffer_size等。

1.先写日志WAL(Write-Ahead Log)

保证写入的持久性,程序崩溃后可通过WAL恢复Memtable中的数据。

2.Memtable与Immutable Memtable

Memtable:内存中的写缓存,采用跳表(SkipList)实现,支持高效的写入和范围查询。所有写入操作首先写入Memtable,同时写入WAL以保证持久性。

Immutable Memtable:当Memtable写满后,它变为只读的Immutable Memtable,后台线程负责将其刷入磁盘,形成SST文件。

3.Flush流程

后台线程把Immutable MemTable按顺序写出,生成一个Level 0层的SST文件,然后丢掉该MemTable和对应的WAL,并开启新的WAL。

4.SST(Sorted String Table)文件

每个SST文件内部按Key排序存储,并包含索引、布隆过滤器等元数据,以加速查找。

SST默认的块文件大小约4KB,支持Zlib、BZ2、Snappy、LZ4、ZSTD等压缩方式。

SST块带有checksum校验值,可以防止损坏和截断。

5.压缩合并策略(Compaction)

Compaction是LSM-Tree的核心,用于解决三个放大因子之间的trade-off:

写放大:每次写入实际写入磁盘的数据量 / 用户写入数据量。

读放大:读取一个 Key 需要读取的磁盘块数。

空间放大:已删除或过期数据占用的冗余空间。

Compaction用k-way merge策略合并有序SST,输出新的SST,并在合适时机丢掉过期版本。

常见的三种Compaction策略:

Leveled Compaction:默认策略,将数据逐层合并,每层大小按固定倍数增长,如10倍,写放大适中,读放大较小。

Universal Compaction:适用于写入密集场景,合并所有层级为一个大的SST,写放大低,但读放大较高。

FIFO Compaction:类似队列,过期数据直接丢弃,适合TTL场景。

RocksDB写数据流程:

step.01:写入WAL,按顺序写。

step.02:写入Memtable,Memtable被写满后转为Immutable Memtable。

step.03:后台Flush为L0层SST文件。

step.04:通过Compaction策略将数据下沉。

RocksDB读数据流程:

step.01:查询Memtable。

step.02:查询Immutable Memtable。

step.03:查询L0层所有SST文件,检查布隆过滤器。

step.04:逐层向下查询,若命中则返回结果,否则返回Not Found。

RocksDB读写流程图

三、RocksDB核心接口


1.打开与关闭数据库

cpp 复制代码
// RocksDB 11+ 已移除 DB** 重载,统一使用 unique_ptr 接管所有权
static Status DB::Open(const Options& options,
                       const std::string& name,
                       std::unique_ptr<DB>* dbptr);

static Status DB::OpenForReadOnly(const Options& options,
                                  const std::string& name,
                                  std::unique_ptr<DB>* dbptr,
                                  bool error_if_wal_file_exists = false);

static Status DB::OpenAsSecondary(const Options& options,
                                  const std::string& name,
                                  const std::string& secondary_path,
                                  std::unique_ptr<DB>* dbptr);

Status DB::Close();   // 可选,析构也会关闭

Open():

常规读写打开;目录不存在时可通过"create_if_missing=true"创建。

OpenForReadOnly():

只读打开,适合备份校验、只读副本工具, 不写 WAL/不触发compaction。

OpenAsSecondary():

二级实例, 从库跟随主库SST/MANIFEST,用于在线只读扩展。

Close():

显式关闭,便于捕获关闭阶段错误,能获取到错误对应的Status。

代码样例:

cpp 复制代码
rocksdb::Options options;
options.create_if_missing = true;
options.error_if_exists = false;

// 常用性能相关配置
options.IncreaseParallelism(std::thread::hardware_concurrency());
options.OptimizeLevelStyleCompaction();

std::unique_ptr<rocksdb::DB> db;
auto s = rocksdb::DB::Open(options, "/var/lib/myapp/rocksdb", &db);

2.更新与删除

cpp 复制代码
Status Put(const WriteOptions& options,
           ColumnFamilyHandle* column_family,
           const Slice& key,
           const Slice& value);

Status Delete(const WriteOptions& options,
              ColumnFamilyHandle* column_family,
              const Slice& key);

Status SingleDelete(const WriteOptions& options,
                    ColumnFamilyHandle* column_family,
                    const Slice& key);

Status Merge(const WriteOptions& options,
             ColumnFamilyHandle* column_family,
             const Slice& key,
             const Slice& value);

Put():

写入或覆盖一个Key-Value对。

Delete():

删除对应的Key,范围内可能触发多次删除。

SingleDelete():

仅当该key只被Put过一次时使用,可减少部分Compaction开销。

Merge():

合并多个Value,适合计数器、列表追加等场景。

代码样例:

cpp 复制代码
WriteOptions wo;
wo.sync = false; // 默认异步落盘

db->Put(wo, "k1", "v1");

// key不存在也返回OK
db->Delete(wo, "k1"); 

// 保证只写过一次的场景,可更快回收
db->SingleDelete(wo, "k1"); 

// 需配置merge_operator
db->Merge(wo, "counter", "1"); 

3.读操作

cpp 复制代码
Status Get(const ReadOptions& options,
           ColumnFamilyHandle* column_family,
           const Slice& key,
           std::string* value);

std::vector<Status> MultiGet(const ReadOptions& options,
                             ColumnFamilyHandle* column_family,
                             const std::vector<Slice>& keys,
                             std::vector<std::string>* values);

bool KeyMayExist(const ReadOptions& options,
                 ColumnFamilyHandle* column_family,
                 const Slice& key,
                 std::string* value,
                 bool* value_found = nullptr);

Get():

查询单个key。

MultiGet():

批量点查,一次调用获取多个键的值,显著快于循环Get。

KeyMayExist():

结合Bloom,快速判断Key可能存在,适合过滤明显不存在的key。

代码样例:

cpp 复制代码
rocksdb::ReadOptions ropt;
ropt.fill_cache = true;
ropt.verify_checksums = true;

std::string value;
auto s = db->Get(ropt, "session:abc", &value);

std::vector<rocksdb::Slice> keys = {"a", "b", "c"};
std::vector<std::string> values;
auto statuses = db->MultiGet(ropt, keys, &values);
for (size_t i = 0; i < keys.size(); ++i) {
  if (statuses[i].ok()) {
    std::cout << keys[i].ToString() << " => " << values[i] << "\n";
  }
}

4.原子批量写操作

cpp 复制代码
class WriteBatch {
public:
  Status Put(const Slice& key, const Slice& value);
  Status Delete(const Slice& key);
  Status Merge(const Slice& key, const Slice& value);
  void Clear();
};

Status Write(const WriteOptions& options, WriteBatch* updates);

将多个Put/Delete组合为一个原子操作,减少IO次数和WAL次数,提升吞吐量,适合一次业务变更涉及多个key的场景。

代码样例:

cpp 复制代码
rocksdb::WriteBatch batch;

batch.Put("key2", "value2");
batch.Delete("key1");
db->Write(rocksdb::WriteOptions(), &batch);

5.扫描与前缀遍历

cpp 复制代码
Iterator* NewIterator(const ReadOptions& options,
                      ColumnFamilyHandle* column_family);

// Iterator 常用方法:
void SeekToFirst();
void SeekToLast();
void Seek(const Slice& target);
void SeekForPrev(const Slice& target);
void Next();
void Prev();
bool Valid() const;

常用操作:Seek/SeekForPrev/SeekToFirst/SeekToLast/Next/Prev。

支持有序扫描、范围查询、前缀遍历、反向遍历等操作。

代码样例:

cpp 复制代码
rocksdb::ReadOptions ropt;
// 前缀扫描优化:需开启prefix_extractor
ropt.prefix_same_as_start = true;
ropt.total_order_seek = false;

std::unique_ptr<rocksdb::Iterator> it(db->NewIterator(ropt));
for (it->Seek("user:"); it->Valid() && it->key().starts_with("user:"); it->Next()) {
  std::cout << it->key().ToString() << " => " << it->value().ToString() << "\n";
}
if (!it->status().ok()) {
  std::cerr << it->status().ToString() << "\n";
}

6.SnapShot快照

cpp 复制代码
const Snapshot* GetSnapshot();
void ReleaseSnapshot(const Snapshot* snapshot);

// 与ReadOptions配合
ReadOptions options;
options.snapshot = snapshot;

获得某一时刻的一致性只读视图,适合备份扫描、报表导出、长事务只读阶段。

代码样例:

cpp 复制代码
const Snapshot* snap = db->GetSnapshot();
ReadOptions ro;
ro.snapshot = snap;

//看到创建快照时的版本
db->Get(ro, "k", &v); 
db->ReleaseSnapshot(snap);

7.事务处理

cpp 复制代码
Status Transaction::Get(const ReadOptions&, 
                        const Slice& key, std::string* value);
Status Transaction::Put(const Slice& key, const Slice& value);
Status Transaction::Delete(const Slice& key);
Status Transaction::Commit();
Status Transaction::Rollback();

RocksDB提供了TransactionDB类,用于多行操作的原子性和隔离性。

代码样例:

cpp 复制代码
rocksdb::TransactionDB* txn_db;
rocksdb::Transaction* txn = txn_db->BeginTransaction(rocksdb::WriteOptions());
txn->Put("key1", "value_new");
txn->Delete("key2");
txn->Commit();  // 原子提交
delete txn;

8.完整代码示例

cpp 复制代码
#include "rocksdb/db.h"
#include "rocksdb/write_batch.h"

using namespace ROCKSDB_NAMESPACE;

int main() {
  Options options;
  options.create_if_missing = true;
  options.IncreaseParallelism();
  options.OptimizeLevelStyleCompaction();

  std::unique_ptr<DB> db;
  Status s = DB::Open(options, "/tmp/rocksdb_demo", &db);
  assert(s.ok());

  s = db->Put(WriteOptions(), "key1", "value1");
  assert(s.ok());

  PinnableSlice val;
  s = db->Get(ReadOptions(), db->DefaultColumnFamily(), "key1", &val);
  assert(s.ok() && val == "value1");
  val.Reset();

  WriteBatch batch;
  batch.Delete("key1");
  batch.Put("key2", "value1");
  s = db->Write(WriteOptions(), &batch);
  assert(s.ok());

  {
    std::unique_ptr<Iterator> it(db->NewIterator(ReadOptions()));
    for (it->SeekToFirst(); it->Valid(); it->Next()) {
      // ...
    }
  }

  db->Close();
}

补充:Options中最常用的调参项

cpp 复制代码
rocksdb::Options options;
options.create_if_missing = true;

// 写路径
options.write_buffer_size = 64 << 20; //MemTable 大小
options.max_write_buffer_number = 3;
options.min_write_buffer_number_to_merge = 1;

// 压缩与层级
options.level0_file_num_compaction_trigger = 4;
options.max_bytes_for_level_base = 256 << 20;
options.target_file_size_base = 64 << 20;
options.compression = rocksdb::kLZ4Compression;
options.bottommost_compression = rocksdb::kZSTD;

// 后台线程
options.max_background_jobs = 8;
options.max_subcompactions = 2;

四、RocksDB开发环境安装

方式1:包管理器安装

bash 复制代码
# Ubuntu 22.04+ / Debian 12+
sudo apt update
sudo apt install -y librocksdb-dev

# Fedora
sudo dnf install rocksdb-devel

# Arch
sudo pacman -S rocksdb

方式2:源码编译安装(更推荐这种)

cpp 复制代码
# 源码下载
git clone https://github.com/facebook/rocksdb.git
cd rocksdb

# 编译与安装
mkdir build && cd build
cmake ..
make -j$(nproc)
make static_lib && sudo make install-static 
make shared_lib && sudo make install-shared

常用的编译命令:

bash 复制代码
g++ -std=c++20 demo.cpp -o demo -lrocksdb -lpthread -ldl -lz -lbz2 -lsnappy -llz4 -lzstd

五、RocksDB编码实战

Demo1.Rall风格的C++代码封装

cpp 复制代码
#include <rocksdb/db.h>
#include <rocksdb/options.h>
#include <rocksdb/slice.h>

class RocksStore {
public:
  struct Config {
    std::string path = "/var/lib/myapp/rocks";
    size_t block_cache_mb = 256;
    bool sync_wal = false;
  };

  explicit RocksStore(Config cfg) : cfg_(std::move(cfg)) {
    rocksdb::DBOptions db_opt;
    db_opt.create_if_missing = true;
    db_opt.create_missing_column_families = true;
    db_opt.max_background_jobs = 4;
    db_opt.IncreaseParallelism();

    rocksdb::ColumnFamilyOptions cf_opt;
    cf_opt.compression = rocksdb::kLZ4Compression;
    cf_opt.bottommost_compression = rocksdb::kZSTD;

    rocksdb::BlockBasedTableOptions table_opt;
    table_opt.block_cache = rocksdb::NewLRUCache(cfg_.block_cache_mb << 20);
    table_opt.filter_policy.reset(rocksdb::NewBloomFilterPolicy(10, false));
    table_opt.cache_index_and_filter_blocks = true;
    cf_opt.table_factory.reset(NewBlockBasedTableFactory(table_opt));

    std::vector<rocksdb::ColumnFamilyDescriptor> descs = {
        {rocksdb::kDefaultColumnFamilyName, cf_opt},
        {"meta", cf_opt},
        {"data", cf_opt},
    };

    auto s = rocksdb::DB::Open(db_opt, cfg_.path, descs, &handles_, &db_);
    if (!s.ok()) throw std::runtime_error("open rocksdb: " + s.ToString());
  }

  ~RocksStore() {
    for (auto* h : handles_) delete h;
  }

  rocksdb::Status PutData(const std::string& key, const std::string& value) {
    rocksdb::WriteOptions w;
    w.sync = cfg_.sync_wal;
    return db_->Put(w, handles_[2], key, value);
  }

  rocksdb::Status GetData(const std::string& key, std::string* value) {
    return db_->Get(rocksdb::ReadOptions(), handles_[2], key, value);
  }

  rocksdb::Status PutMeta(const std::string& key, const std::string& value) {
    return db_->Put(rocksdb::WriteOptions(), handles_[1], key, value);
  }

  rocksdb::DB* raw() { return db_.get(); }

private:
  Config cfg_;
  std::unique_ptr<rocksdb::DB> db_;
  std::vector<rocksdb::ColumnFamilyHandle*> handles_;
};

Demo2.基于RocksDB的用户订单前缀扫描

cpp 复制代码
#include <iostream>
#include <memory>
#include <string>
#include <vector>

#include <rocksdb/db.h>
#include <rocksdb/options.h>
#include <rocksdb/slice_transform.h>

namespace {
constexpr size_t kOrderNamespacePrefixLen = 6;
}

struct Order {
  std::string order_id;
  std::string payload;
};

std::string MakeOrderKey(const std::string& user_id, const std::string& order_id) {
  return "order:" + user_id + ":" + order_id;
}

std::vector<Order> ListUserOrders(rocksdb::DB* db, const std::string& user_id,
                                  size_t limit) {
  const std::string prefix = "order:" + user_id + ":";
  rocksdb::ReadOptions ropt;
  ropt.total_order_seek = true;

  std::vector<Order> result;
  std::unique_ptr<rocksdb::Iterator> it(db->NewIterator(ropt));
  for (it->Seek(prefix); it->Valid() && result.size() < limit; it->Next()) {
    const std::string key = it->key().ToString();
    if (key.compare(0, prefix.size(), prefix) != 0) {
      break;
    }
    Order order;
    order.order_id = key.substr(key.find_last_of(':') + 1);
    order.payload = it->value().ToString();
    result.push_back(std::move(order));
  }
  return result;
}

void PrintOrders(const std::string& user_id, const std::vector<Order>& orders) {
  std::cout << "\n=== 用户 " << user_id << " 的订单列表(共 " << orders.size()
            << " 条)===\n";
  if (orders.empty()) {
    std::cout << "(空)\n";
    return;
  }
  for (size_t i = 0; i < orders.size(); ++i) {
    std::cout << "  [" << (i + 1) << "] order_id=" << orders[i].order_id << "\n"
              << "      payload=" << orders[i].payload << "\n";
  }
}

int main() {
  const std::string db_path = "/tmp/rocks_orders_cpp";
  rocksdb::Options options;
  options.create_if_missing = true;
  options.prefix_extractor.reset(
      rocksdb::NewFixedPrefixTransform(kOrderNamespacePrefixLen));

  // RocksDB 11+:Open 输出参数为 std::unique_ptr<DB>*,已移除 DB** 重载
  std::unique_ptr<rocksdb::DB> db;
  rocksdb::Status s = rocksdb::DB::Open(options, db_path, &db);
  if (!s.ok()) {
    std::cerr << "Open failed: " << s.ToString() << "\n";
    return 1;
  }

  rocksdb::WriteOptions wopt;
  db->Put(wopt, MakeOrderKey("u42", "20260723001"),
          R"({"amount":99.0,"item":"键盘","status":"paid"})");
  db->Put(wopt, MakeOrderKey("u42", "20260723002"),
          R"({"amount":20.0,"item":"鼠标垫","status":"paid"})");
  db->Put(wopt, MakeOrderKey("u42", "20260724003"),
          R"({"amount":199.0,"item":"耳机","status":"shipped"})");
  db->Put(wopt, MakeOrderKey("u99", "20260723001"),
          R"({"amount":9.9,"item":"数据线","status":"paid"})");
  db->Put(wopt, MakeOrderKey("u99", "20260725002"),
          R"({"amount":59.0,"item":"保护壳","status":"cancelled"})");
  db->Put(wopt, MakeOrderKey("u07", "20260721001"),
          R"({"amount":1299.0,"item":"显示器","status":"paid"})");

  std::cout << "打开 RocksDB: " << db_path << "\n";
  std::cout << "写入演示订单数据完成。\n";

  for (const auto& uid : {"u42", "u99", "u07", "u_not_exist"}) {
    PrintOrders(uid, ListUserOrders(db.get(), uid, 100));
  }
  auto limited = ListUserOrders(db.get(), "u42", 2);
  std::cout << "\n=== 用户 u42 前缀扫描 limit=2 ===\n";
  for (const auto& order : limited) {
    std::cout << "  " << order.order_id << " -> " << order.payload << "\n";
  }
  std::cout << "\n订单查询完成。\n";
  return 0;
}

代码运行结果:

Demo3.最简单的写入 & 查询用例

cpp 复制代码
#include <iostream>
#include <memory>
#include <string>

#include <rocksdb/db.h>

int main() {
  std::unique_ptr<rocksdb::DB> db;
  rocksdb::Options options;
  options.create_if_missing = true;
  rocksdb::Status s = rocksdb::DB::Open(options, "/tmp/rocksdb_basic", &db);
  if (!s.ok()) {
    std::cerr << "Open failed: " << s.ToString() << "\n";
    return 1;
  }

  s = db->Put(rocksdb::WriteOptions(), "user:1001", R"({"name":"alice","age":32})");
  if (!s.ok()) {
    std::cerr << "Put failed: " << s.ToString() << "\n";
  }

  std::string value;
  s = db->Get(rocksdb::ReadOptions(), "user:1001", &value);
  if (s.ok()) {
    std::cout << "value = " << value << "\n";
  } else if (s.IsNotFound()) {
    std::cout << "key not found\n";
  } else {
    std::cerr << "Get failed: " << s.ToString() << "\n";
  }

  // unique_ptr 析构时自动 Close,无需手动 delete
  return 0;
}

代码运行结果:

cpp 复制代码
value = {"name":"alice","age":32}

六、常见问题排查

Q1:Open失败 & Open时进程崩溃
可能"WAL/MANIFEST/SST"不一致。先尝试备份目录,再用ldb工具诊断,必要时从Backup/Checkpoint检查点恢复数据,请勿直接删MANIFEST。
Q2:写入变慢或出现Stall
可能是Compaction/Flush跟不上了。检查磁盘利用率、max_background_jobs、L0文件数等参数。观察rocksdb.stall相关统计。
Q3:Get查询速度很慢
Block Cache过小、未打开Bloom布隆过滤器、查询的value过大、层级过多导致读放大。用PerfContext看主要时间花在了block read还是mutex步骤上。
Q4:Iterator查看到旧数据或漏数据
确认是否设置了Snapshot快照。
Q5:多进程同时写同一目录
RocksDB默认不支持多进程写。如果需要多进程写,请用二级实例、或外置服务化、文件锁等策略自行串行化处理。
Q6:编译时,链接报缺少snappy/lz4/zstd
按编译RocksDB时启用的压缩库补全-lsnappy -llz4 -lzstd -lbz2 -lz等依赖库。
Q7:编译报"error: no matching function for call to 'rocksdb::DB::Open(rocksdb::Options&, const std::string&, rocksdb::DB**)'"
RocksDB 11.0以后的版本已移除"DB**"形式的Open/OpenForReadOnly/OpenAsSecondary等重载,改为输出智能指针"std::unique_ptr<DB>*"。

七、RocksDB与其他数据库选型方案


参考阅读:

官方仓库:GitHub - facebook/rocksdb: A library that provides an embeddable, persistent key-value store for fast storage. · GitHub

官方Wiki:https://github.com/facebook/rocksdb/wiki

Basic Operations:https://github.com/facebook/rocksdb/wiki/Basic-Operations

Column Families:https://github.com/facebook/rocksdb/wiki/Column-Families

Transactions:https://github.com/facebook/rocksdb/wiki/Transactions

Tuning Guide:https://github.com/facebook/rocksdb/wiki/RocksDB-Tuning-Guide

Checkpoints:https://github.com/facebook/rocksdb/wiki/Checkpoints

相关推荐
进击的_鹏1 小时前
从零开始的 Redis 学习
服务器·数据库·c++·redis·缓存
钱栈up1 小时前
Mac 开发机一键发版不用切环境:我这样改造了团队的后端部署脚本Maven编译卡住20分钟?我靠两步定位到2处隐蔽编译错误
开发语言·python·macos
william_yangshun1 小时前
【大模型部署实战】kimi-k3-in-c 中文版:用 176KB 纯 C99 引擎在 CPU 上跑 Kimi K3 上手指南
c语言·开发语言
隔窗听雨眠1 小时前
当KES遇到多租户:金仓数据库多租户架构的隔离实践与部署指南
数据库·架构
Sagittarius_A*1 小时前
Burst Lab | PHP 审计 14|反序列化(四):字符串逃逸与绕过技巧
开发语言·安全·php·web·反序列化
名字还没想好☜1 小时前
Go 用 slices/maps 标准库泛型函数:告别手写 Contains、Sort、去重(Go 1.21)
开发语言·后端·算法·golang·go
JOker_Chu_1 小时前
知识图谱详解:从图结构、关系建模到查询与应用
数据库·知识图谱·database·neo4j
淼澄研学2 小时前
Python实战:基于LangChain与Chroma构建企业级RAG知识库问答系统
开发语言·python·langchain
tryCbest2 小时前
搭建企业知识库之Milvus 向量数据库
数据库·milvus