1、是什么
- 是一个使用C++编写的嵌入式 KV 存储引擎。
- 是Facebook基于levelDB开发的。
2、特点
高性能
RocksDB使用一套日志结构的数据库引擎,为了更好的性能,这套引擎是用C++编写的。 Key和value是任意大小的字节流。
为快速存储而优化
RocksDB为快速而又低延迟的存储设备(例如闪存或者高速硬盘)而特殊优化处理。 RocksDB将最大限度的发挥闪存和RAM的高度率读写性能。
可适配性
RocksDB适合于多种不同工作量类型。 从像MyRocks这样的数据存储引擎, 到应用数据缓存, 甚至是一些嵌入式工作量,RocksDB都可以从容面对这些不同的数据工作量需求。
基础和高级的数据库操作
RocksDB提供了一些基础的操作,例如打开和关闭数据库。 对于合并和压缩过滤等高级操作,也提供了读写支持。
3、发展状况
目前,业界使用或支持RocksDB的项目:
- JIMKV
京东自研的分布式数据库,支持RocksDB存储引擎。
- CockroachDB
网易开源的分布式New-SQL数据库,底层使用Rocksdb。
- ByteKV
字节跳动自研的 KV 存储系统。底层使用了Rocksdb 存储引擎。
- Pika
360自研的类Redis存储系统。存储引擎是基于 Rocksdb 修改的。
- Kvrocks
美图开源的一个KV数据库,基于RocksDB并与Redis协议兼容。
- Parker
OPPO 自研的一个基于 RocksDB 的分布式 KV 存储系统。
- WTable
58自研的分布式KV存储系统。
- TiKV
PingCAP 开源的一个支持分布式事务和水平扩展的 KV 数据库。底层本地存储是依赖了 RocksDB。
- LogDevice
Facebook 开源的一个用于日志的分布式数据存储系统。它的本地日志存储被称为LogsDB,是构建于RocksDB之上的。
4、示例
4.1、引入依赖
<dependency>
<groupId>org.rocksdb</groupId>
<artifactId>rocksdbjni</artifactId>
<version>6.5.3</version>
</dependency>
4.2、连接数据库
@Before
public void init() throws RocksDBException {
Options options = new Options().setCreateIfMissing(true);
db = RocksDB.open(options, dbPath);
}
4.3、插入数据
@Test
public void testPut() throws RocksDBException {
db.put("hello".getBytes(), "world".getBytes());
db.put("你好".getBytes(), "世界".getBytes());
}
4.4、查询数据
@Test
public void testGet() throws RocksDBException {
System.out.println(new String(db.get("hello".getBytes())));
System.out.println(new String(db.get("你好".getBytes())));
}
4.5、删除数据
@Test
public void testRemove() throws RocksDBException {
System.out.println(db.get("hello".getBytes()) != null);
db.remove("hello".getBytes());
System.out.println(db.get("hello".getBytes()) != null);
}
4.6、批量插入
@Test
public void testMPut() throws RocksDBException {
WriteOptions writeOpt = new WriteOptions();
WriteBatch batch = new WriteBatch();
for (int i = 0; i < 5; i++) {
batch.put(("testMPut_" + i).getBytes(), String.valueOf(i).getBytes());
}
db.write(writeOpt, batch);
}
4.7、批量查询
@Test
public void testMGet() throws RocksDBException {
List<byte[]> keys = new ArrayList<>();
for (int i = 0; i < 5; i++) {
keys.add(("testMPut_" + i).getBytes());
}
List<byte[]> values = db.multiGetAsList(keys);
for (int i = 0; i < keys.size(); i++) {
System.out.println(new String(keys.get(i)) + "--" + (values.get(i) == null ? null : new String(values.get(i))));
}
}
4.8、迭代器
正序:
@Test
public void testIterator1() {
RocksIterator iterator = db.newIterator();
// 从第一个元素开始,正序遍历
for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) {
System.out.println(new String(iterator.key()) + "--" + new String(iterator.value()));
}
}
倒序:
@Test
public void testIterator2() {
RocksIterator iterator = db.newIterator();
// 从最后一个元素开始,倒序遍历
for (iterator.seekToLast(); iterator.isValid(); iterator.prev()) {
System.out.println(new String(iterator.key()) + "--" + new String(iterator.value()));
}
}
从指定元素开始:
@Test
public void testIterator3() {
RocksIterator iterator = db.newIterator();
for (iterator.seek("testMPut_1".getBytes()); iterator.isValid(); iterator.next()) {
System.out.println(new String(iterator.key()) + "--" + new String(iterator.value()));
}
}
5、架构
6、模块
6.1、log文件
描述:
写Memtable前会先写Log文件,Log通过append的方式顺序写入。
作用:
Log的存在使得机器宕机导致的内存数据丢失得以恢复。
格式:
说明:
- log 文件由多个 32 KB的 block 组成
- 每个 block 由多个 record 组成(若末尾不足7字节,用0x00填充;若末尾刚好 7 字节,用一个 length 为 0 的 record填充)。
- 每个 record 由 header 和 data 组成
- chechSum:校验码,判断数据是否被破坏
- length:数据的大小
- type:当数据很大时,可能会存储在多个block中;用来表示record 的类型
record类型:
type | 说明 |
---|---|
FULL | 表示当前 record 完整地存储在一个 block 里 |
FIRST | 表示这是一个 record 的起始部分 |
MIDDLE | 表示这是一个record 的中间部分 |
LAST | 表示这是一个 record 的末尾部分 |
6.2、memtable
6.3、immutable memtable
- 当memtable的size达到阈值,会变成immutable memtable
- immutable memtable与memtable结构是一样的,只提供读不允许写入
- 由后台线程 flush 到磁盘上
6.4、SSTable
全称:
Sorted String Table
描述:
- 分为 level-0 到 level-n 多层(最大支持7层)
- 每一层包含多个 SSTable
- 文件内数据是有序
- 除了 level-0 之外,每一层内部的 SSTable 的 key 范围都不相交
- 默认大小2MB
结构:
说明:
- Footer:固定 48 字节,位于文件尾部,索引的索引
- Index Block:data block的索引
- Meta Index Block:meta block的索引,目前 LevelDB 中只有一个 meta block
- meta block:filter,默认是bloom filter
- Data Block:存储实际的 key-value 数据,默认大小4KB。
6.4.1、Footer
结构:
说明:
- meta index handle :指向 meta index block
- index handle: 指向 index block
- padding:填充补齐
- magic number:魔数, 8 个字节
- handle 由offset 和 size 组成
- offset 和 size 最少占用1个字节长度、最多占用9个字节长度
6.4.2、Index Block
结构:
说明:
索引分为三部分:
- 记录大于等于block i 中最大的Key且小于block i +1中最小的Key的 key
- block i 在sst文件中的起始位置
- block i 的大小
6.4.3、Data Block
结构:
说明:
- Record:实际的KV记录,按顺序排列
- Restart:重启点
6.4.4、重启点
解决了什么问题?
Block里的记录key是有序的,所以,相邻的两条记录很可能Key部分存在重叠,从节省空间的角度考虑,后面的key可以只存储跟前面不同的部分,相同的部分可以从前面的数据获取。
描述:
重启点表示以当前记录为新的基础,后面的数据只存不同的部分。
6.4.5、Record
结构:
6.5、Manifest文件
保存了整个 LevelDB 实例的元数据:
- 每一层有哪些 SSTable
- 单个SSTable文件的文件大小、最大 key、最小 key 等信息
- 其他一些LevelDB需要的元信息
6.6、Current文件
Current文件简单的记录了当前Manifest的文件名
7、写流程
当写入一条Key:Value时,流程如下:
- 先往log文件里写入
- 成功后将记录插进Memtable中
这样就完成了一次写入操作,只涉及一次磁盘顺序写和一次内存插入,这也是写入速度很快的原因。
8、读流程
流程:
- 先去查询 Memtable,找到则返回
- 再去查询 Immutable Memtable,找到则返回
- 查询 level 0 的文件level 0 比较特殊, level 0 下的不同文件可能key的范围有重叠,查询流程如下:3.1. 根据manifest的记录,找到相关的所有文件3.2. 对文件排序,从最新的文件依次查询,直到找到为止,找到返回,找不到去下一层找3.2.1. 对于每个sst文件,查询文件的索引(index block)3.2.1.1. 若缓存(Table Cache )中有,用缓存中的索引3.2.1.2. 若缓存中没有,则打开sst文件,同时将这个文件的索引部分加载进缓存中****3.2.2. 根据索引找对应的数据(data block)3.2.2.1. 若缓存(Block Cache )中有,用缓存中的数据3.2.2.2. 若缓存中没有,则从文件中查询,并放入缓存
- 查询 level i(i>0) 的文件****不同文件key不重复,所以最多只对应一个文件,后面的流程与 3.2.1 ~3.2.2相同
其中3.2的流程图如下:
9、删除
LevelDB的删除操作是标记删除,并没有真正删除原来的数据,真正的删除操作通过compaction删除
10、缓存
有两个cache是为了优化读性能的:TableCache和BlockCache。这两种Cache都采用了LRUCache机制,只是内部存储的数据类型不同。
10.1、TableCache
用于缓存 SSTable 的文件描述符、索引和 filter
10.2、BlockCache
SSTable 的数据是被组织成一个个 block。BlockCache 用于缓存这些 block(解压后)的数据。
11、压缩
levelDb采取了compaction的方式来对已有的记录进行整理压缩,通过这种方式,来删除掉一些不再有效的KV数据,减小数据规模,减少文件数量等。LevelDB Compaction分两种:
- minor compaction
- major compaction
11.1、minor compaction
当内存中的memtable大小到了一定值时,将内容保存到磁盘文件中:
主要做两件事情:
- 构造sstable
- 新的sstable文件写入哪一层
流程如下:
新产生出来的sstable 并不一定总是处于level 0, 尽管大多数情况下,处于level 0。新创建的出来的sstable文件应该位于那一层呢? 由PickLevelForMemTableOutput 函数来计算。
11.1、major compaction
当某个level下的SSTable文件数目超过一定设置值后,levelDb会从这个level的SSTable中选择一个文件(level>0),将其和高一层级的level+1的SSTable文件合并,这就是major compaction。
12、扩展
12.1、LSM-Tree
全称:
Log-Structured-Merge-Tree
起源:
1996 年的一篇论文《The Log-Structured Merge-Tree (LSM-Tree)》。
思想:
- 新增数据先记录 log
- 在保存进内存中,超过阈值后,批量flush到磁盘
- 磁盘数据达到一定条件后merge,优化读性能
特点:
写入速度很快,主要利用了磁盘的顺序写。
12.2、对比LevelDB
- 多MemTable,防止可能出现的写阻塞
- 多线程压缩合并
- 将flush 和 compaction分开用不同的线程池来进行调度
- 增加了对WAL的特殊管理机制
- 增加了merge operator,即原地更新
- 增加了ColumnFamily(列族)
- 支持分层自定义压缩算法
- 性能更好
- 适应更多的应用场景