RocksDB 是由 Meta (原 Facebook) 开发的一款高性能、嵌入式键值(Key-Value)存储引擎。它基于 Google 的 LevelDB 开发,专为多核 CPU 和快速存储设备(如 SSD/NVMe)进行了深度优化。
介绍
RocksDB 不是像 MySQL 或 Redis 那样的独立服务器,而是一个C++ 库,直接链接到应用程序中运行。许多知名数据库和流计算系统(如 CockroachDB, TiKV, Flink, Kafka Streams, MyRocks)底层都使用 RocksDB 作为存储引擎。
核心特性
- 架构:基于 LSM-Tree (Log-Structured Merge Tree),擅长高写入吞吐量。
- 可调优:提供极丰富的配置参数(BlockSize, Memtable 大小, 压缩算法等)以适应不同负载。
- 特性:支持事务、快照(Snapshot)、列族(Column Families)、布隆过滤器(Bloom Filter)。
架构
RocksDB 的架构基于 LSM-Tree (Log-Structured Merge Tree),其术语体系主要围绕数据写入流程、存储文件结构以及逻辑隔离展开。
以下是 RocksDB 中最核心的术语介绍,分为 逻辑结构、内存组件、磁盘组件 和 后台操作 四个部分。
逻辑结构
Column Family (列族)
- 概念:类似于关系型数据库(如 MySQL)中的
表 (Table)。
- 作用:
- 逻辑隔离:可以把不同类型的数据放在不同的 Column Family (CF) 中(例如:一个存用户元数据,一个存用户日志)。
- 独立配置:每个 CF 可以有独立的配置参数(如压缩算法、缓存大小、MemTable 大小)。这允许根据数据的读写特性进行精细化调优。
- 底层机制:
- 所有的 CF 共享同一个 WAL(Write Ahead Log)文件(保证原子写入)。
- 每个 CF 拥有独立的 MemTable 和 SST 文件集合。
- 默认情况下,RocksDB 只有一个名为
"default" 的列族。
内存组件 (In-Memory)
MemTable (内存表)
- 概念:数据的写入缓冲区。
- 流程:所有写入请求(Put/Delete)首先都会写入 MemTable。它通常是一个跳表(SkipList),数据在内存中是有序的。
- 读写:提供极快的读写速度。读取时会先查 MemTable,查不到再查磁盘。
Immutable MemTable (不可变内存表)
- 概念:已经写满的 MemTable。
- 流程:当活跃的 MemTable 写满(达到
write_buffer_size)后,它会变成不可变状态(只读),并等待后台线程将其 Flush(刷盘)到磁盘生成 SST 文件。
磁盘组件 (On-Disk)
WAL (Write Ahead Log, 预写日志)
- 概念:用于故障恢复的日志文件。
- 作用:数据写入 MemTable 之前,会先顺序追加写入 WAL。如果系统崩溃(Crash),RocksDB 重启时可以通过重放 WAL 来恢复内存中尚未刷盘的数据。
- 生命周期:当 MemTable 成功刷入磁盘生成 SST 后,对应的 WAL 会被删除。
SST / SSTable (Static Sorted Table)
- 概念:RocksDB 在磁盘上存储数据的核心文件格式。
- 特点:
- 静态 (Static):文件一旦生成,就不会再被修改(Immutable)。
- 有序 (Sorted):文件内部的 Key 是有序排列的,便于二分查找。
- 分层 (Leveled):SST 文件分布在不同的层级(Level 0 到 Level N)中。
- 内部结构:通常包含 Data Block(数据块)、Index Block(索引块)、Bloom Filter(布隆过滤器)等。
MANIFEST (清单文件)
- 概念:数据库的元数据文件。
- 作用:记录了数据库当前的状态。比如:当前有哪些 SST 文件、每个 SST 文件属于哪一层 (Level)、每个 SST 的 Key 范围(最小 Key 和最大 Key)等。
- 重要性:如果 MANIFEST 丢失,数据库将无法打开。
关键流程与机制
LSM-Tree 层级 (Level 0, Level 1…)
RocksDB 的磁盘文件是分层管理的:
- Level 0 (L0):MemTable 刷盘后直接生成的 SST 都在 L0。这是唯一允许 Key 范围重叠的一层。读取时如果需要在 L0 查找,必须遍历 L0 的所有文件(性能最差)。
- Level 1 ~ Level N:从 L1 开始,每一层内的所有 SST 文件,其 Key 范围互不重叠。Level 越高,数据越冷(旧),总容量越大。
Flush (刷盘)
- 动作:将 Immutable MemTable 的内容写到磁盘,生成一个 L0 SST 文件。
Compaction (压缩/合并)
- 概念:RocksDB 最核心的后台维护操作。
- 为什么需要:
- L0 层文件允许重叠,读性能差,需要合并到 L1。
- 数据主要以追加方式写入,旧数据(被覆盖的 Key)和删除标记(Tombstone)占据空间。
- 动作:
- 读取高层(如 L0)的 SST 和低层(如 L1)有重叠的 SST。
- 进行多路归并排序。
丢弃旧版本数据和已删除数据。
- 生成新的 SST 文件放到下一层。
- 代价:Compaction 会消耗大量的 CPU 和磁盘 I/O(写放大),是调优的重点。
Bloom Filter (布隆过滤器)
- 概念:一种概率型数据结构,存储在 SST 文件中。
- 作用:快速判断 Key 是否不存在。
- 流程:在读取 SST 文件前,先查 Bloom Filter。如果它说
Key 不存在,那就绝对不存在,直接跳过该文件(节省磁盘 I/O)。如果它说可能存在,才去真正读取数据块。
Block Cache (块缓存)
- 概念:内存中的缓存区。
- 作用:缓存 SST 文件中的解压后的数据块(Data Block)。热点数据的读取通常直接命中 Block Cache,不需要读盘。
生命周期
一个 Key-Value 的生命周期通常是:
- 写入 -> 追加 WAL -> 写入 MemTable。
- MemTable 满了 -> 变 Immutable -> Flush 到磁盘 -> 生成 L0 SST (WAL 删除)。
- 后台 Compaction -> L0 SST 与 L1 SST 合并 -> 生成新的 L1 SST … 逐渐下沉到 Lmax。
- 读取 -> 查 MemTable -> 查 Block Cache -> 查 L0 SST (所有文件) -> 查 L1~Ln SST (二分查找 + Bloom Filter)。
常用工具与维护命令
RocksDB 官方提供了两个核心命令行工具用于运维和调试:
ldb: 用于读写数据、查询元数据、修复数据库。
sst_dump: 用于直接分析底层的 SST 物理文件。
注意:
- 以下命令假设你已经编译了 RocksDB,工具通常位于源码目录的
tools/ 下,或者已安装到系统路径。
- 停机维护:大部分
ldb 写操作命令建议在数据库停止服务(关闭应用)时运行,避免锁冲突。
- 请将
--db=/tmp/test_db 替换为你实际的数据库路径。
安装:
apt install rocksdb-tools
数据查询与操作
这是最常用的工具,类似于一个简单的命令行客户端。
-
写入/更新数据
# 写入 key="a", value="b" (如果 DB 不存在则创建)
ldb --db=/tmp/my_db --create_if_missing put a b
-
文件结构
$ ls /tmp/my_db/
000013.sst IDENTITY LOG lost MANIFEST-000014 OPTIONS-000012
CURRENT LOCK LOG.old.1766288971448365 MANIFEST-000001 OPTIONS-000007
数据库状态检查与维护
-
查看数据库元数据 (Manifest)
查看 LSM-Tree 的结构,包括每一层(Level)有哪些 SST 文件。
ldb --db=/tmp/my_db manifest_dump
-
强制进行 Compact (压缩整理)
如果磁盘空间占用过高,或删除大量数据后空间未释放,可以手动触发。
ldb --db=/tmp/my_db compact
-
修复数据库
如果数据库因异常崩溃导致无法打开(如 MANIFEST 文件损坏),可以尝试修复(警告:可能导致部分数据丢失)。
ldb --db=/tmp/my_db repair
底层文件分析
当需要排查具体的物理文件(.sst 文件)是否损坏或查看其属性时使用。
-
查看 SST 文件属性
检查文件包含的 Key 范围、压缩算法、数据量等。
# 替换具体的 .sst 文件路径
$ sst_dump --file=/tmp/my_db/000013.sst --command=show_properties
options.env is 0x59f0ccfeb050
Process /tmp/my_db/000013.sst
Sst file format: block-based
-
扫描 SST 文件内容
直接读取物理文件中的内容,不经过数据库逻辑。
$ sst_dump --file=/tmp/my_db/000013.sst --command=scan --read_num=10
options.env is 0x602309e21050
Process /tmp/my_db/000013.sst
Sst file format: block-based
from [] to []
'a' seq:0, type:1 => b
-
检测文件一致性
验证 SST 文件的 Checksum,检查文件是否损坏。
sst_dump --file=/tmp/my_db/000013.sst --command=check
-
导出数据为 Hex (Raw Dump)
将 SST 文件内容转储为可读的十六进制文本,用于深度 Debug。
sst_dump --file=/tmp/my_db/000013.sst --command=raw
使用
RocksDB 是 C++ 编写的库,因此在 Python 和 Golang 中使用时,都需要先在操作系统层面安装 RocksDB 的 C++ 动态链接库(Shared Library),否则无法编译或运行。
前置条件:安装系统库
在运行代码前,请确保操作系统已安装 RocksDB C++ 核心库。
- MacOS (Homebrew):
- Ubuntu / Debian:
sudo apt-get update
sudo apt-get install -y librocksdb-dev
- CentOS / RHEL:
sudo yum install rocksdb-devel
Python 示例
pip install rocksdict,这是一个基于 Rust 封装的 RocksDB 库,安装更简单
from rocksdict import Rdict
# 打开/创建数据库
db = Rdict("./test_rocksdict")
# 写
db["key1"] = "value1"
db[b"key2"] = b"value2"
# 读
print(db["key1"])
# 删
del db["key1"]
# 自动关闭
db.close()
# 需要先确保系统已经安装了 rocksdb C++ 动态库 (如 apt install librocksdb-dev)
pip install git+https://github.com/twmht/python-rocksdb.git
pip install python-rocksdb -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
Golang 示例
Go 语言需要使用 CGO 调用 C++ 库。目前社区较活跃的封装库是 linxGnu/grocksdb (它是 gorocksdb 的增强版)。
mkdir go-rocksdb-demo
cd go-rocksdb-demo
go mod init go-rocksdb-demo
# 下载依赖 (确保 CGO_ENABLED=1)
go get github.com/linxGnu/grocksdb
package main
import (
"fmt"
"log"
"github.com/linxGnu/grocksdb"
)
func main() {
dbPath := "./test_rocksdb_go"
// 1. 设置 Options
bbto := grocksdb.NewDefaultBlockBasedTableOptions()
bbto.SetBlockCache(grocksdb.NewLRUCache(32 << 20)) // 32MB Block Cache
opts := grocksdb.NewDefaultOptions()
opts.SetBlockBasedTableFactory(bbto)
opts.SetCreateIfMissing(true)
// 2. 打开数据库
db, err := grocksdb.OpenDb(opts, dbPath)
if err != nil {
log.Fatal("无法打开数据库: ", err)
}
// 确保程序结束时关闭数据库
defer db.Close()
fmt.Println("数据库已打开:", dbPath)
// 准备读写选项 (默认即可)
wo := grocksdb.NewDefaultWriteOptions()
ro := grocksdb.NewDefaultReadOptions()
// 3. 写入数据 (Put)
key := []byte("user_2002")
value := []byte("Bob")
err = db.Put(wo, key, value)
if err != nil {
log.Fatal(err)
}
fmt.Printf("写入数据: key=%s, value=%s\n", key, value)
// 4. 读取数据 (Get)
slice, err := db.Get(ro, key)
if err != nil {
log.Fatal(err)
}
// slice 需要手动释放内存
defer slice.Free()
if slice.Exists() {
fmt.Printf("读取数据: key=%s, value=%s\n", key, slice.Data())
} else {
fmt.Println("数据不存在")
}
// 5. 批量操作 (WriteBatch)
batch := grocksdb.NewWriteBatch()
defer batch.Destroy() // 释放 batch 内存
batch.Put([]byte("conf_a"), []byte("1"))
batch.Put([]byte("conf_b"), []byte("2"))
batch.Delete([]byte("user_2002")) // 删除上面的 Bob
err = db.Write(wo, batch)
if err != nil {
log.Fatal("批量写入失败:", err)
}
fmt.Println("批量操作完成")
// 6. 迭代器 (Iterator)
iter := db.NewIterator(ro)
defer iter.Close()
fmt.Println("开始扫描数据:")
// SeekToFirst: 从头开始; Seek(key): 从指定 key 开始
for iter.SeekToFirst(); iter.Valid(); iter.Next() {
// 获取 Key 和 Value (注意: Data() 返回的 slice 是引用,如需长期持有需拷贝)
k := iter.Key()
v := iter.Value()
fmt.Printf("Scan -> Key: %s, Value: %s\n", k.Data(), v.Data())
k.Free()
v.Free()
}
if err := iter.Err(); err != nil {
log.Fatal(err)
}
}
运行 Go 代码
# 必须开启 CGO
CGO_ENABLED=1 go run main.go
常见问题排查
-
编译报错 fatal error: rocksdb/c.h: No such file
- 原因: 找不到 C++ 头文件。
- 解决: 确认已安装
librocksdb-dev (Linux) 或 rocksdb (Mac)。如果安装了但仍报错,可能需要设置 CGO_CFLAGS 和 CGO_LDFLAGS 环境变量指向头文件位置。
-
运行时报错 ImportError (Python) 或 library not found (Go)
- 原因: 找不到
.so 或 .dylib 动态库。
- 解决: 确认库在
/usr/local/lib 或 /usr/lib 下。Linux 下可能需要运行 sudo ldconfig 刷新库缓存。
-
锁文件错误 (LOCK: Resource temporarily unavailable)
- 原因: 一个 RocksDB 数据库目录同一时间只能被一个进程打开。
- 解决: 确保没有其他终端(或上次运行崩溃的进程)正在占用该目录。