Redis对象的设计与实现
Posted 编程人,在天涯
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis对象的设计与实现相关的知识,希望对你有一定的参考价值。
一、Redis对象结构
Redis中的每个对象都由一个redisObject结构表示:
typedef struct redisObject {
unsigned type;//类型
unsigned encoding;//编码
void *ptr;//指向底层实现数据结构的指针
int refcount;//引用计数
unsigned lru;//对象最后一次被程序访问的时间
}
1. type:Redis对象类型
redisObject的\'type\'属性记录了对象的类型:
type命令:返回键对应的值对象的类型。
2. encoding:Redis对象编码和底层实现
redisObject的\'encoding\'属性记录了对象所使用的编码(记录对象底层实现的数据结构):
每种类型的对象都至少使用了两种不同的编码:
object encoding命令:查看一个数据库键的值对象的编码。
通过\'encoding\'属性来设定Redis对象所使用的编码,而不是为特定类型的对象关联一种固定的编码,提升了Redis的灵活性和效率,因为Redis可以根据不同的使用场景来为一个对象设置不同的编码, 从而优化对象在某一场景下的效率。
Redis服务器执行一个命令时,首先通过\'type\'属性进行类型检查,判断对象类型是否支持当前命令,然后再根据\'encoding\'属性判断对象的编码方式,选择正确的命令实现代码来执行命令。
3. refcount:Redis对象引用计数
Redis对象通过其\'refcount\'属性实现对象内存回收与对象共享。当Redis需要创建一个键值对的时候,如果已经存在一个与需要创建的值对象完全相同的值对象,Redis不会创建一个新的值对象,而是将键对象的值指针指向这个现有的完全相同的值对象,同时将这个值对象的\'refcount\'属性值加一,从而实现对象共享,节约内存;当一个键对象不再指向这个值对象的时候,则值对象的\'refcount\'属性值减一,当\'refcount\'属性值为0的时候,则将对象释放,将内存回收。
目前,Redis会在初始化服务器时,创建一万个字符串对象,这些对象包含了从0到9999的所有整数值,当服务器需要用到值为0到9999的字符串对象时,服务器就会使用这些共享对象,而不是新创建对象。创建共享字符串对象的数量可以通过修改redis.h/REDIS_SHARED_INTEGERS常量来修改。
当服务器考虑将一个共享对象设置为键的值对象时,程序会先验证给定的共享对象和键想创建的目标对象是否完全相同,共享对象保存的值越复杂,验证所需的复杂度就越高,消耗的CPU时间也会越多,所以Redis只对包含整数值的字符串对象进行共享,其验证复杂度为O(1)。
引用计数查看命令:object refcount
4. lru:Redis对象最后一次被程序访问的时间
对象的空转时长 = 当前时间 - 对象的\'lru\'属性值
查看对象空转时长的命令:object idletime(这个命令不会改变对象的\'lru\'属性值)
如果服务器打开了maxmemory选项,并且服务器用于回收内存的算法为volatile-lru或者allkeys-lru,那么当服务器占用的内存数超过了maxmemory选项所设置的上限值时,空转时长较高的那部分键会优先被服务器释放,从而回收内存(可以参考配置文件的maxmemory选项和maxmemory-policy选项)。
二、字符串对象
1. 字符串对象根据其值类型的不同,使用不同的编码方式,对应关系如下:
(1)可以用long类型表示的整数值:int
(2)小于等于39字节的字符串值:embstr(底层结构为embstr编码的SDS)
(3)大于39字节的字符串值:raw(底层结构为SDS)
注:浮点数在Redis中也是作为字符串值来保存的,只是在有需要的时候会将其转化为浮点数值,执行某些操作之后,存入Redis时又转换为字符串值。
2. 关于embstr编码
embstr编码是专门用于保存短字符串的一种优化编码方式,这种编码和raw编码一样,都使用redisObject结构和sdshdr结构来表示字符串对象,但raw编码会调用两次内存分配函数来分别创建redisObject结构和sdshdr结构,而embstr编码则通过调用一次内存分配函数来分配一块连续的空间,空间中依次包含redisObject和sdshdr两个结构。
使用embstr编码的字符串对象来保存短字符串值的好处:
(1)embst 编码将创建字符串对象所需的内存分配次数从raw编码的两次降低为一次。
(2)释放embstr编码的字符串对象只需要调用一次内存释放函数,而释放raw编码的字符串对象需要调用两次内存释放函数。
(3)因为embstr编码的字符串对象的所有数据都保存在一块连续的内存里面,所以这种编码的字符串对象比起raw编码的字符串对象能够更好地利用缓存带来的优势。
3. 三种编码的字符串对象结构
(1)int编码的字符串对象结构示例:
(2)raw编码的字符串对象结构示例:
(3)embstr编码的字符串对象结构示例:
4. 编码的转换
int编码的字符串对象和embstr编码的字符串对象在条件满足的情况下,会被转换为raw编码的字符串对象。
另外,因为Redis没有为embstr编码的字符串对象编写任何相应的修改程序(只有int编码的字符串对象和raw编码的字符串对象有这些程序),所以embstr编码的字符串对象实际上是只读的:当我们对embstr编码的字符串对象执行任何修改命令时,程序会先将对象的编码从embstr转换成raw,然后再执行修改命令;因为这个原因,embstr编码的字符串对象在执行修改命令之后,总会变成一个raw编码的字符串对象。
三、列表对象
1. 列表对象的编码可以是ziplist(底层结构为压缩列表)或者linkedlist(底层结构为双端链表)。
2. 列表对象结构
(1)使用ziplist编码的列表对象结构示例:
(2)使用linkedlist编码的列表对象结构示例:
其中双端链表中又嵌套了多个字符串对象,这里的‘StringObject’只是简化表示。
3. 编码转换
当列表对象可以同时满足以下两个条件时, 列表对象使用ziplist编码:
(1)列表对象保存的所有字符串元素的长度都小于64字节(可由参数list-max-ziplist-value配置);
(2)列表对象保存的元素数量小于512个(可由参数list-max-ziplist-entries配置);
不能同时满足这两个条件的列表对象需要使用linkedlist编码。
四、哈希对象
1. 哈希对象的编码可以是ziplist(底层结构为压缩列表)或hashtable(底层结构为字典)。
2. 哈希对象结构
(1)使用ziplist编码的哈希对象结构示例:
其中压缩列表结构示例如下:
(2)使用hashtable编码的哈希对象结构示例:
3. 编码转换
当哈希对象可以同时满足以下两个条件时, 哈希对象使用ziplist编码:
(1)哈希对象保存的所有键值对的键和值的字符串长度都小于64字节(可由参数hash-max-ziplist-value配置);
(2)哈希对象保存的键值对数量小于512个(可由参数hash-max-ziplist-entries配置);
不能同时满足这两个条件的哈希对象需要使用hashtable编码。
五、集合对象
1. 集合对象的编码可以是intset(底层结构是整数集合)或hashtable(底层结构是字典)。
2. 集合对象结构
(1)使用intset编码的集合对象结构示例:
(2)使用hashtable编码的集合对象结构示例:
(使用字典键值对中的键来存储集合元素,而值都被设置为NULL)
3. 编码转换
当集合对象可以同时满足以下两个条件时,对象使用intset编码:
(1)集合对象保存的所有元素都是整数值;
(2)集合对象保存的元素数量不超过512个(可由参数set-max-intset-entries配置);
不能满足这两个条件的集合对象需要使用hashtable编码。
六、有序集合对象
1. 有序集合的编码可以是ziplist(底层结构为压缩列表)或skiplist(底层结构为跳跃表+字典)。
2. 有序集合对象结构
(1)使用ziplist编码的有序集合对象结构示例:
其中压缩列表结构示例如下:
(2)使用skiplist编码的有序集合对象
使用skiplist编码的有序集合对象同时使用一个字典和一个跳跃表来实现。
结构定义:
结构示例:
其中zset结构示例如下:
理论上有序集合完全可以单独使用字典或跳跃表其中一种数据结构来实现,但是性能上比同时使用这两种数据结构都会有所下降。使用跳跃表可以提高对有序集合进行范围型操作的效率,而使用字典则可以快速找到指定成员的分值,只使用其中一种数据结构会丢失另一种数据结构在对应操作上带来的效率。另外,有序集合中的字典和跳跃表会通过指针共享相同元素的成员和分值,并不会造成任何数据重复,也不会浪费额外内存。
3. 编码转换
当有序集合对象可以同时满足以下两个条件时, 对象使用ziplist编码:
(1)有序集合保存的元素数量小于128个(可由参数zset-max-ziplist-entries配置);
(2)有序集合保存的所有元素成员的长度都小于64字节(可由参数zset-max-ziplist-value配置);
不能满足以上两个条件的有序集合对象将使用skiplist编码。
以上是关于Redis对象的设计与实现的主要内容,如果未能解决你的问题,请参考以下文章