Last updated
Last updated
redis
的根对象是一个 redisDb
结构体。里面有个 dict
字段,存储了所有的 kv
,还有个 expire
字段,存储了所有设置了过期时间的 key
。
当我们使用 EXPIRE key duration
命令对一个 key
设置过期时间时,会将该 key
保存到 expires
这个字典中,就是说一个 key
存了两次,额外占用内存。不过这个 key
的 value
不是实际数据的 value
,而是过期时间。
dictEntry
里的 key
和 value
,由一种叫做 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
属性所指定的编码,选择合适的操作函数来处理底层的数据结构。返回数据结构的操作结果作为命令的返回值
参考
dict
的结构详见