数据结构
redis
的根对象是一个 redisDb
结构体。里面有个 dict
字段,存储了所有的 kv
,还有个 expire
字段,存储了所有设置了过期时间的 key
。
当我们使用 EXPIRE key duration
命令对一个 key
设置过期时间时,会将该 key
保存到 expires
这个字典中,就是说一个 key
存了两次,额外占用内存。不过这个 key
的 value
不是实际数据的 value
,而是过期时间。
dict
的结构详见 哈希表结构
dictEntry
里的 key
和 value
,由一种叫做 redisObject
的结构来存储。
RedisObject
在 redis
命令中,对 key
进行处理的命令占了很大一部分。对于不同的 value
类型,key
能执行的命令又大不相同。比如说,LPUSH
和 LLEN
只能用于 list
类型的 key
。另外一些命令,比如 DEL
、TTL
和 TYPE
,可以用于任何类型的 key
,但底层的执行逻辑又是不一样的。说明 redis
必须让每个 key
都带有类型信息,使得程序可以检查 key
的类型,并为它选择合适的处理方式。
为了解决以上问题,redis
构建了自己的类型系统,用来储存 value
。RedisObject
是其中的核心。其结构如下:
type
, encoding
, 和 ptr
是最重要的3个属性。
type
可能的值如下:
encoding
可能的值如下:
String
结构为 SDS
,编码有如下三种:
int
:这种编码支持incr
、decr
命令embstr
:长度小于44
的字符串raw
:长度超过44
的字符串
List
quicklist
:linkedlist
与ziplist
的结合。
Hash
ziplist
: 数据量小时,会直接用ziplist
编码hashtable
: 数据量大时,改为hashtable
编码
Set
intset
: 元素全为int
,且数量小于配置时,使用该特殊编码hashtable
: 其它情况使用hashtable
编码。其实就是个value
为NULL
的Hash
的结构
Sorted Set
ziplist
: 数据量小时,使用该编码skiplist
+hashtable
: 其余情况使用该编码,跳表用于排序,hashtable
用于O(1)
查找元素
高级数据结构
Bitmap
底层为 String
,只不过操作的粒度变成了位。因为 String
类型最大长度为 512MB
,所以 bitmap
最多可以存储 2^32
个 bit
GEO
本质上还是借助于
ZSET
,并且使用GeoHash
技术进行填充。Redis
中将经纬度使用52
位整数进行编码,放进zset
中,score
就是这52
位整数值通过对
score
进行排序就可以得到坐标附近的其它元素,将score
还原成坐标值就可以得到元素的原始坐标
HyperLogLog
可以用来统计网站PV
基本原理是伯努利实验,比如进行
n
轮抛硬币实验,每轮直到出现1
为止,如果最多出现连续2
个0
然后才是1
的情况 (001
),那么n
大概率为8
001
的概率为1/2^3
,也就是说如果 出现了001
这个序列,说明起码抛了8
次硬币。
但是这样误差太大,因此
HyperLogLog
做了分桶。使用PFAdd key offset
添加元素时,先计算元素的64
位hash
, 前14
位用于分桶,后50
位即伯努利过程,计算最长的连续0
的个数。基于每个桶里
0
的最大长度计算平均值,并乘以修正因子,得到最后的计数。感觉桶里
0
的最大长度,其实类似布谷鸟过滤器里的指纹。
Stream
用作消息队列。底层的数据结构是
radix tree
(基数树)
命令的类型检查和多态
有了 redisObject
结构的存在,在执行处理数据类型的命令时,进行类型检查和对编码进行多态操作就简单得多了。
当执行一个处理数据类型的命令时,Redis
执行以下步骤:
根据给定
key
, 在数据库字典中查找和它相对应的redisObject
,如果没找到, 就返回NULL
检查
redisObject
的type
属性和执行命令所需的类型是否相符,如果不相符,返回类型错误根据
redisObject
的encoding
属性所指定的编码,选择合适的操作函数来处理底层的数据结构。返回数据结构的操作结果作为命令的返回值
参考
Last updated