建议先阅读我的博客 RocksDB 架构
RocksDB 应用于分布式场景,底层通过 LSM - Tree 实现,分布式场景 LSM - Tree 这种结构的原因在于:
列族,CF column famliy
。DB 的每个键值对都与唯一一个列族结合。若不指定列族,则与default
结合
可以这样理解,DB 对应着数据库中的库,CF 对应着数据库中的表。实际上,一个列族可以是一个表,也可以是表的一个索引。一个列族里面可以包括多个表的数据。每个列族有一棵 LSM-Tree 和多个 Memtable。
列族提供了一种数据库逻辑分片的方法
如图所示:一个DB 的多个列族共享一个 WAL 日志,但是不共享 Memtable 和 SST 文件。通过共享 WAL 日志实现了原子写。通过隔离 Memtable 和 SST 文件,可以独立配置每个列族并且快速删除它们。
当一个列族刷盘时,创建一个新的 WAL 文件。此后,DB 中所有列族都会写入这个 WAL 文件。由于 WAL 共享,所以只有当所有的列族都把这个 WAL 里的数据刷盘了,才能删除该 WAL 文件。
快照,snapshot
。一个快照会捕获在创建的时间点的 DB 的一致性视图。快照在 DB 重启之后将消失。
一个快照相当于一个 SnapshotImpl
类的对象。其中用于实现 MVCC 的字段是:
number_
:序列号,DB 下全局自增。unix_time_
:DML 操作的时间快照的本质是双向链表,其应用主要体现在迭代器和事务上。
迭代器读取一个指定快照中的数据。
RocksDB 支持事务。
TransactionDB
:预写时加锁,适用于高并发写冲突大的场景。例:mysqlOptimisticTransactionDB
:预写时不加锁,只在提交事务时检查冲突,有冲突就丢弃修改。适用于写冲突小的场景,例:redis首先查找本事务对应的 Writebatch 中是否存在请求的数据。接着跳表查找内存的 Memtable(active + immutable );若不存在,则基于 SST 文件元数据查找是否缓存在 Block Cache 中;若没有被缓存,则读磁盘的 SST 文件,找到后并加载到 Block Cache 中。为了提高查找效率,会借助布隆过滤器,避免无效的数据 IO 和遍历操作。
每个事务都有一个 Writebatch 对象,用于缓存该事务在提交前修改的所有数据。当通过 WriteBatch 写入多个 key 的时候,RocksDB 提供原子化操作。在事务提交时,先写入 WAL 日志,再写入内存可写的 Memtable。当 Memtable 达到阈值后会变为只读的 immutable,此时再新生成一个 Memtable。
数据写入 Memtable 后就意味着事务已经提交。数据的持久化和 compaction 都是异步进行的。当 Immutable Memtable 的数量达到阈值后,会被刷成 L0 SST 文件。在 L0 文件个数达到阈值后,合并到 L1 上并依次往下刷。RocksDB 中可以配置多个线程用于对每层数据文件进行 compaction。
事务的实现方式,见本文第三部分 MyRocks。
pika 是 360 开源的可持久化的大容量类 redis 数据库。
pika 特点
内存数据库的缺陷:当数据量足够大时,导致内存不够用
若减少使用 List 命令,pika 可以达到 redis 80% 的性能。
# 安装 lib
sudo apt-get install libgflags-dev libsnappy-dev
sudo apt-get install libprotobuf-dev protobuf-compiler
sudo apt install libgoogle-glog-devgit clone https://github.com/OpenAtomFoundation/pika.git
cd pika
git submodule update --init
git checkout -b v3.4.0
make
使用
./output/bin/pika -c ./conf/pika.conf
Blackwidow 本质是基于 RocksDB 的封装,使只支持 KV 存储的 RocksDB 能够支持多种数据结构。
String 由 1 个列族组成,RocksDB 落盘方式:
解释:value 增加了 4 B用于存储 timestamp 用于实现 expire 功能。每当获取一个 string 对象,首先解析 value 后 4 B,获取到 timestamp 后作出判断返回结果(0 - 未设置超时时间)。
哈希表由两个列族组成
(meta_key, meta_value)
:存储哈希表的信息。(data_key, data_value)
:存储对应的 field 和 value。RocksDB 落盘方式
解释:hash_size 用于实现 hlen 命令。
版本号version
指的是快照的序列号,用于标记删除,真正的删除操作是在 compaction 过程中。元数据中 key 对应的 value 中的 version 记录了最新的有效版本信息
由于 String 结构只有一个列族,可以直接删除;对于其他的数据结构,存在多个列族,需要版本号来确定是删除整个 key,还是删除其中的一个 field-value。而对于时间戳timestamp
,只有 key 存在过期时间。
list 由两个列族组成:元数据和普通数据。
RocksDB 落盘方式
解释:list_size 实现 llen 命令,left_index 和 right_index 用于实现 lpush 和 rpush 命令。index 实现有序。
list 由两个列族组成:元数据和普通数据。
RocksDB 落盘方式
解释:set_size 实现 scard 命令
list 由三个列族组成:元数据和普通数据。
RocksDB 落盘方式
解释:第 3 列族用于 zset 排序,按 score 排序,若 score 相等,对 member 来排序。
version 如何用于删除
MyRocks 将 RocksDB 替换 InnoDB 作为 Mysql 存储引擎 。
优点:
适用场景
sequence number
:RocksDB 中的每一条记录都有一个序列号 sn,存储在记录的 key 中snapshot
:快照,与序列号一一对应,对于一个序列号为 sn 的快照来说,只能看到小于或等于 sn 的快照记录,大于 sn 的快照记录不可见InternalKey:| User key (string) | sequence number (7 bytes) | value type (1 byte) |
序列号 sn 提供了记录的多版本信息。当查询记录时,不需要加锁,而是根据当前序列号 sn 创建一个快照,查询过程中只能读取小于或等于 sn 的快照记录,查询结束后释放 snapshot。