RocksDB 基本原理以及应用
1. RocksDB 是什么?高效发挥存储硬件性能的嵌入式KV存储引擎
我们都使用过数据库,如mysql、redis等。提供了网路服务,主要是存储数据。
那么嵌入式KV存储引擎是把存储做的非常好。
然后我们可以基于我们自己的需求构建一个数据库(提供一些网络服务,在提供一些针对rocksdb的封装)。
那么高效发挥存储硬件性能是什么意思呢?
主要是由于rocksdb针对内存、闪存、hdd/SSD都会有一些优化,并可以通过配置进行调参。
rocksdb的历史
rocksdb是基于leveldb的一个上层封装或者说版本迁移,主要由facebook维护。
而leveldb是google的,由GFS\BigTable\Mapreduce中bigtable的作者写的。
而我们前面说的数据库都有对应的rocksdb版本,比如redis对应的是pika,mysql对应myrocks。
TiBD也是基于RocksDB做的分布式存储。
因此学好rocksdb对存储的认识,以及对分布式技术发展都有帮助。
2. RocksDB 解决了什么问题?写多读少场景需求
主要用于写多读少场景,如大数据存储。
这里的写多读少只是相对而言,只是说对于读的需求没有那么严格,并不是说rocksdb要慢很多,只是相对于写来说差一点点。
对比innodb来看,rocksdb和它解决的问题恰好是两个方向。
- mysql innodb B+树 是一个数据结构
- rocksdb LSM-tree 不是数据结构,而是磁盘组织数据的一种方式
写日志为什么快?
因为利用了磁盘顺序i/o
(内存顺序io 100ns >> 内存随机io ≈ 磁盘顺序io >> 磁盘随机io 10ms)
所以如何加快写速度?
答案是追加写的方式,即磁盘顺序io。
就地写和追加写
- 就地写:我们通过B+树的数据结构访问到原本的数据页(叶子节点,大小16k是磁盘数据页4k的倍数),这种就属于就地写。
- 追加写:无论这个数据是否曾经写过磁盘,都
会追加写,因此会产生冗余。
追加写的两个问题如何解决?
- 查询:有序、数据块方式存储
追加写的情况,查询就会比较麻烦,可能扫描很多无效数据。因此需要一种排序方式。
我们最好是通过数据块的方式来组织数据。 - 解决冗余:
压缩合并的方式把老的冗余数据清除掉。
文件拆分,使阻塞范围较少。
而内存中也设置了一个区域,内存的随机写是可以接受的。达到一定阈值后直接顺序写(dump)到磁盘新文件,因此就不会阻塞前面文件的合并压缩了。
注意内存中是一个有序结构,它是没有重复数据的,是就地写的。查询优先查内存热数据。
宕机的情况
当内存数据还没转储到磁盘时宕机了怎么办?
这时候就使用wal。思路同关系型数据库的wal。
3. RocksDB 是怎么解决的?LSM-Tree
LSM-Tree(log struture merge tree)
以打日志的结构进行写,并合并冗余数据的方式。
3.1内存结构
memtable:提供并发读写。
immutable memtable:只读内存,负责落盘,落盘的时候由memtable提供读写。
3.2磁盘结构
level 0 文件间数据有重复(L0单个文件内没有重复,因为是从内存来的)
level 1~n 每一层中没有数据重复,是上一层数据合并的结果。但层与层之间可能有冗余数据。在所有层中,单个文件都是有序不重复的。
3.3整体结构
1.按冷热数据进行分层,时间轮的思想。通过一层一层的合并压缩,90%的数据会落在最后一层。
2.所有层中单个文件是有序不重复的,level1~n文件间是有序的,多路归并,由小的有序文件组合成大的有序文件。
总结:写多读少是通过磁盘顺序io、以及分层和拆分实现的。
3.4 内存中采用什么数据结构?
Rocksdb提供并发读写,那么内存中采用什么数据结构?
根据时间复杂度,红黑树和跳表是O(logn),而B+树和B树都是O(m*logn),多了个层数,因此B+树和B树只适合磁盘,不适合内存。
再来看红黑树,每次增删改由于都要维持数据结构的平衡(重新着色或旋转),都可能改变节点位置,因此需要对整个结构加锁。所以也不合适。
跳表是个很好的选择,过程如图示:
插入:先随即层数,再建立节点间关系。
高度只影响读,节点构建节点之间的相互关系
3.5 磁盘中数据如何存储?
- SSTable(Sorted String Table):数据持久化文件,内部按 Key 有序排列,包含数据块(Data Block)、索引块(Index Block)和元数据块(如 Bloom Filter)。
- 分层存储:SSTable 分布在多个层级(Level 0 到 Level N),Level 0 允许 Key 重叠,下层通过 Compaction 逐步全局有序
4. RocksDB 解决了哪些具体问题?Pika、MyRocks、TiDB
写多读少的场景我们就会想到能否用rocksdb去替换。
5.总结
5.1 核心架构设计
RocksDB 的核心架构围绕 LSM-Tree 展开,分为内存组件和磁盘组件,通过分层存储与合并机制优化写入性能,同时兼顾查询效率:
-
内存组件
- MemTable:写入数据首先存入内存中的 MemTable(默认使用跳表 SkipList 实现),支持高效的顺序插入与范围查询(O(log n) 复杂度)。
- Immutable MemTable:当 MemTable 填满后转为不可变状态,后台线程将其刷盘为 SSTable 文件,同时生成新的 MemTable 接收写入。
- Write-Ahead Log (WAL):每次写入同时记录 WAL,用于故障恢复,保证数据持久性。
-
磁盘组件
- SSTable(Sorted String Table):数据持久化文件,内部按 Key 有序排列,包含数据块(Data Block)、索引块(Index Block)和元数据块(如 Bloom Filter)。
- 分层存储:SSTable 分布在多个层级(Level 0 到 Level N),Level 0 允许 Key 重叠,下层通过 Compaction 逐步全局有序。
5.2 写入流程
-
数据写入
- 客户端写入 Key-Value 对时,先记录到 WAL 和 MemTable,随后返回成功。
- 当 MemTable 达到阈值(如 64MB)后转为 Immutable,后台线程将其刷入 Level 0 的 SSTable。
-
Compaction(压缩合并)
- 目的:合并冗余数据、清理删除标记(Tombstone)、优化存储空间与查询性能。
-
策略:
- Leveled Compaction:逐层合并,减少空间放大,适合读密集型场景。
- Tiered Compaction:延迟合并,降低写放大,适合写密集型场景(如日志系统)。
- FIFO Compaction:淘汰旧数据,适用于缓存场景。
5.3 读取流程
-
查询路径
- 优先从 MemTable 和 Immutable MemTable 查找最新数据。
- 若未找到,逐层扫描 SSTable(Level 0 → Level N),利用 Bloom Filter 快速过滤无效文件,通过索引块定位具体数据块。
-
性能优化
- Block Cache:缓存热点数据块,减少磁盘 I/O。
- 前缀压缩与布隆过滤器:减少 Key 比较次数和无效磁盘访问。
5.4 关键特性与优化
-
高吞吐写入
- 利用 LSM-Tree 的顺序写入特性,将随机写转换为批量顺序写,适合 SSD 优化。
-
可配置性
- 支持动态调整 MemTable 大小、压缩算法(如 LZ4、ZStandard)、缓存策略等,适应不同负载需求。
-
资源平衡
- 写放大控制:通过 Compaction 策略减少重复写入。
- CPU 与 I/O 优化:使用异步线程处理 Compaction,结合 Rate Limiter 限制资源竞争。
5.5 应用场景
- 分布式数据库存储引擎:如 TiDB、CockroachDB,利用其高吞吐和持久化能力同步多副本数据。
- 流处理系统状态存储:如 Flink,通过 RocksDB 实现低延迟的状态存取。
- 消息队列与日志系统:如 Kafka(KRaft 模式),支持高并发写入与高效压缩。
| 维度 | RocksDB 设计 | 优势与挑战 |
|---|---|---|
| 写入性能 | LSM-Tree 顺序写 + 异步 Compaction | 高吞吐,但需平衡写放大与空间占用 |
| 读取性能 | 内存优先 + 多级索引 | 范围查询高效,点查依赖优化策略 |
| 适用场景 | 写多读少、需持久化的系统 | 需根据负载调整参数以发挥最佳性能 |
通过灵活的配置和分层设计,RocksDB 在分布式系统中表现出色,但其性能调优需结合具体场景(如调整 Compaction 策略、缓存大小等)