背景
新版本下的 Pika 采用 floyd 作为新的存储引擎,接下来我们以源码的方式向大家展示一下新存储引擎做了哪些改造.
KV
blackwidow
blackwidow 下的 string_db_
是一个 std::unique_ptr<RedisStrings>
,RedisStrings 继承自 Redis 类,Redis 类中有一个 rocksdb::DB* db_
的成员变量,子类 RedisStrings
使用父类 Redis 类的这个成员变量 db_
用来对 RocksDB 做操作.
1 | void SetCmd::Do(std::shared_ptr<Slot> slot) { /* 0 */ |
floyd
floyd 下的 Pika 首先会对 key 做一层 Hash 计算 (slot 计算 + index 计算),得出一个 index,用指定索引下的 insts_
去处理,insts_
是一个 std::vector<std::shared_ptr<Instance>>
,Instance 里面封装了成员变量rocksdb::DB* db_
, 这样的计算方式可以使多个 RocksDB 实例平均分给所有的 key,不局限于一种数据类型使用一个 RocksDB 实例,同时对 key 和 value 的构造也做了调整,对 key 来说新加了 reserve1(8B),db_id(2B),slot_id(2B),reserve2(16B). 对 value 来说新增了 reserve(16B),cdate(8B). 同时 timestamp
和 version
字段的大小从原来的 4B 调整为 8B. 这里的 reserve
字段目前用于占位,cdate
字段用于记录写入时间,db_id
字段用来区别各个 DB 的数据,slot_id
字段用于记录 key Hash 得到的 slot 值.
1 | void SetCmd::Do(std::shared_ptr<Slot> slot) { /* 0 */ |
1 | /* 旧版 key 格式 */ /* 旧版 value 格式 */ |
Hash
blackwidow
对于 Hash 类型,我们以 Hset 命令了解,对了除 KV 之外的数据类型,我们需要操作 RocksDB 的列族
1 | void HSetCmd::Do(std::shared_ptr<Slot> slot) { /* 0 */ |
floyd
1 | void HSetCmd::Do(std::shared_ptr<Slot> slot) { /* 0 */ |
1 | /* 旧版 meta_key 格式 */ /* 旧版 meta_value 格式 */ |
List
blackwidow
对于 Lish 类型,我们以 Lpush 命令了解
1 | void LPushCmd::Do(std::shared_ptr<Slot> slot) { /* 0 */ |
floyd
1 | void LPushCmd::Do(std::shared_ptr<Slot> slot) { /* 0 */ |
1 | /* 旧版 meta_key 格式 */ |
Set
blackwidow
对于 Set 类型,我们以 SAdd 命令了解
1 | void SAddCmd::Do(std::shared_ptr<Slot> slot) { /* 0 */ |
floyd
1 | void SAddCmd::Do(std::shared_ptr<Slot> slot) { /* 0 */ |
1 | /* 旧版 meta_key 格式 */ /* 旧版 meta_value 格式 */ |
Zset
blackwidow
对于 Zset 类型,我们以 ZAdd 命令了解
1 | void ZAddCmd::Do(std::shared_ptr<Slot> slot) { /* 0 */ |
floyd
1 | void ZAddCmd::Do(std::shared_ptr<Slot> slot) { /* 0 */ |
1 | /* 旧版 meta_key 格式 */ /* 旧版 meta_value 格式 */ |
FAQ
Q1: 为什么要加 reserve 字段,没看懂?
A1: reserve 翻译过来就是保留预定的意思,目前该字段我们先用于占位,后续有需求我们再加上去
Q2:为什么要加 slot-id,db-id,cdate 字段,为什么之前的存储引擎方式不需要这三个字段?
A2:
Q3:version 和 timestamp 为什么要由 4B 改成 8B?
A3:
Q4:你这篇文章说的都是单个 key 的两者的区别,那么对于多个 key 来说?多个 key 可能会保存在多个 RocksDB 实例中,那你解决并发问题?
A4:看来你考虑挺仔细的,我会在下篇文章介绍~
Q5:只需要在 Pika 层面做存储引擎的修改,对于集群模式来说,Codis 层面也需要做修改吗?
A5:是个好问题,目前我也在考虑
Q6:我刚刚发现 blackwidow 下的 RocksDB 实例用的是 unique_ptr,而 floyd 下的 RocksDB 实例用的是 shard_ptr,这是为什么?
A6:
Q7:为什么要改存储引擎,floyd 有什么优势呢?
A7:线上使用过程中发现,同一个业务服务使用的数据类型一般集中在一两个数据类型中,无法发挥多RocksDB实例的优势,floyd 的实现方式能保证每个分片持有的 RocksDB 实例个数近似相同
总结
floyd 中不再按照数据类型区分RocksDB实例,而是通过 column-family 区分.单个 Pika 节点的 RocksDB 实例个数根据物理机硬件配置决定,每个 RocksDB 实例使用独立的 compaction 线程池和 flush 线程池,初次之外每个 RocksDB 实例使用一个后台线程,该后台线程用来发起 manual compaction 以及对 RocksDB 中存储的数据进行定期的统计和巡检. 每个节点在启动时获取到当前节点持有的分片,将分片排序并等分为 RocksDB 实例个数,保证每个分片持有的 RocksDB 实例个数近似相同. 对于 key 来讲,前缀增加 8 字节的 reserve 保留字段以及 2 字节的 slot_id 和 2 字节的 db_id,后缀增加16字节的 reserve 保留字段。对于 value 来讲,在 value 最后统一增加:16 字节的 reserve 保留字段,8 字节的数据的写入时间 cdate. 此外 timestamp 和 version 由之前的 4 字节调整为 8 字节. 最后关于新的存储引擎还有无效数据清理和RocksDB使用的优化,我会在后续的文章中更新~