天天看点

Redis与KV存储(RocksDB)融合之编码方式Redis与KV存储(RocksDB)融合之编码方式

redis 是目前 nosql 领域的当红炸子鸡,它象一把瑞士军刀,小巧、锋利、实用,特别适合解决一些使用传统关系数据库难以解决的问题。redis 作为内存数据库,所有的数据全部都存在内存中,特别适合处理少量的热数据。当有巨量数据超过内存大小需要落盘保存时,就需要使用 redis + kv存储的方案了。

本文涉及的ardb就是一个完全兼容redis协议的nosql的存储服务。其存储基于现有成熟的kv存储引擎实现,理论上任何类似b-tree/lsm tree实现的kv存储实现均可作为ardb的底层存储实现,目前ardb支持leveldb/rocksdb/lmdb.

本文以ardb为例,介绍redis与kv存储之间融合时编解码层的实现。

redis与kv存储的融合方案中, 编解码层是一个很重要的环节。通过编解码层,我们可以屏蔽了各种kv存储实现的不同,可以在任意一个简单的kv存储引擎上,封装实现redis中string,hash,list,set,sorted set等复杂类型的数据结构。

对于string类型,很显然可以与kv存储中的一个kv对一一对应;

对于其它的容器类型,我们需要

一个kv来存储其整个key的元信息(比如list的成员个数,过期时间等);

每一个成员需要一个kv来保存成员的名称和值;

对于sorted set,其每个成员有score和rank两个属性,所以需要:

一个kv保存整个key的元信息

每一个成员需要一个kv保存 score信息

每一个成员需要一个kv保存每个成员对应 rank 信息

对于所有的key, 包含同样的前缀,编码格式定义如下:

namespace用于支持类似redis中的库概念, 可以为任意字符串, 不限制必须为数字;

key则是一个变长二进制字符串

type用于定义一个简单key-value的类型,此类型隐含表明key的数据结构类型;一个字节

meta信息的key中type固定为key_meta;具体类型将在value中定义(参考下一节)

除以上三部分外,不同类型的key可能有附加字段;如hash的key可能需要附加field字段

内部value则比较复杂,编码均以type开始, type取值即上节定义的keytype

后续格式根据各种类型定义不同.

各类型数据的编码方式如下: ns代表namespace

这里以最复杂的sorted set来做实例。假设有个sorted set为 a: {member=frist, score=1}, {member=second, score=2}。其在ardb中的存储方式如下:

key a的存储编码为:

成员first的score信息存储编码为:

成员first的rank信息存储编码为:

成员second的score信息存储编码略。

当用户使用zcard a命令时,直接访问namespace_1_a即可得到元信息中该有序集合的数目;

当用户使用zscore a first时,直接访问namespace_a_first即可得到first成员的score;

当用户使用zrank a first时,先用zscore得到score,再查找namespace_10_a_1_first的序号;

具体的存储方式代码如下:

所有的数据结构都有保存meta的一个key-value,而meta信息的key编码格式是统一的,因此不可能出现不同数据结构有相同名字的情况。(这就是为什么保存key的kv对中,k固定为key_meta类型,而对应redis类型信息存在meta类型数据的value中的原因)。

del实现中会先查询meta的key-value,得到具体数据结构类型,然后执行对应的删除工作, 类似如下的步骤:

查询指定key的meta信息,得到数据结构类型

根据具体类型,执行删除工作

所以一次del至少需要 一次读 + 后续删除写操作

具体代码如下:

前缀搜索代码如下:

在一个key-value存储引擎上支持复杂数据结构的expire过期数据的实现比较困难,ardb中则用几个特殊技巧实现了对所有数据结构的过期(expire)的支持。

具体实现如下:

meta的value中保存expire信息, 用绝对unix时间(ms)保存;

基于以上设计,ttl/pttl等查询ttl的操作只需要一次读meta即可完成;

基于以上设计,任何对meta信息的读取,都会触发expire的判断,由于对meta信息的读操作是必须的步骤,这里无需额外的读操作(和redis一样访问时会触发)

创建一个namespace ttl_db专门存放ttl排序信息。

保存设置expire时间到meta时, 当expire时间非0时,额外保存一个key-value, type为key_ttl_sort; key的编码格式为 [ttl_db] "" key_ttl_sort , value为空;所以类似expire 等设置过期时间的操作,在ardb的实现中将会多一次写操作;

在自定义的comparator中,对key_ttl_sort类型的key比较规则为先比较,这样key_ttl_sort数据将会以过期时间远近保存在一起

ardb中独立启动一个线程,每隔一定时间(100ms)顺序扫描key_ttl_sort类型数据;当过期时间小于当前时间,即可触发删除操作;当过期时间大于当前时间,即可终止本次扫描。(相当于是redis中的定时任务servercron中处理过期key)。

通过编码层的转换,我们可以很好的对kv存储进行封装从而和redis进行融合。所有对redis数据的操作,经过编码层的转换,最终会转化为对kv存储的n次读写(n>=1)。在符合redis命令语义的情况下,编码曾设计应当尽量的减少n的次数。

最重要的一点是,redis与kv存储的融合并不是为了替换redis,而是寻求一种在性能可接受的情况下使得单机能支持远超内存限制的数据量。在特定的场景中,也可以作为冷数据的存储方案与redis热数据之间互联互通。

硬广来了,不要急:

阿里云数据库redis团队诚邀加入:

<a href="https://job.alibaba.com/zhaopin/position_detail.htm?spm=0.0.0.0.l7roxq&amp;positionid=26437">https://job.alibaba.com/zhaopin/position_detail.htm?spm=0.0.0.0.l7roxq&amp;positionid=26437</a>

继续阅读