Redis对象
1、对象类型与编码
redis使用集中基础的数据结构构建了一个对象系统,这个对象系统包含字符串对象、列表对象、哈希对象、集合对象以及有序集合对象。
redis中每个对象都由一个redisObject结构表示。
struct robj {
unsigned int type;
unsigned int encoding;
void *ptr;
int refcount;
unsigned int lru;
}
-
type属性记录了对象的类型,对应下表中的常量值
对象 type属性值 TYPE命令的输出 字符串对象 REDIS_STRING string 列表对象 REDIS_LIST list 哈希对象 REDIS_HASH hash 集合对象 REDIS_SET set 有序集合对象 REDIS_ZSET zset -
encoding属性设定了对象所使用的编码,对应下表中的常量值
encoding常量值 编码所对应的底层数据结构 可编码的对象 REDIS_ENCODING_INT long类型的整数 字符串对象 REDIS_ENCODING_EMB_STR embstr编码的简单动态字符串 字符串对象 REDIS_ENCODING_RAW 简单动态字符串 字符串对象 REDIS_ENCODING_HT 字典 哈希对象和集合对象 REDIS_ENCODING_LINKEDLIST 双向链表 列表对象 REDIS_ENCODING_ZIPLIST 压缩列表 哈希对象和有序列表对象 REDIS_ENCODING_INTSET 整数集合 集合对象 REDIS_ENCODING_SKIPLIST 跳跃表和字典 有序列表 -
ptr指针指向对象的底层实现数据结构,这些数据结构由对象的encoding属性决定
-
refcount是当前对象的引用计数,用于内存释放
-
lru记录了程序最后依次访问对象的时间,用于计算空转时长
2、字符串对象
字符串对象的编码可以是int、embstr或raw。
如果一个字符串对象保存的是整数值且这个整数可以用long表示,则字符串对象会将整数值保存在字符串对象结构的ptr属性里边(将void *换为long),并将字符串对象的编码设置为int;若一个字符串对象保存的是一个字符串值且长度小于等于32字节,则将使用embstr编码的方式保存字符串;若一个字符串对象保存的是一个字符串且长度大于32字节,则将使用raw格式的sds保存字符串。
- embstr编码
embstr编码是专门用于保存短字符串的一种优化编码方式,这种编码和raw一样,都使用redisObject和sdshdr结构表示字符串对象,但raw编码会调用两次内存分配函数来分别创建redisObject和sdshdr结构,而embstr只会调用一次内存分配函数来分配一块连续的内存空间,空间中依次包括redisObject和sdshdr结构。embstr编码的好处如下:
-
embstr将raw编码的两次内存分配、两次内存释放降低为一次
-
embstr编码的字符串对象的所有数据保存在一块连续的内存空间里,能够更好地利用缓存的优势
-
编码之间的互相转换
int和embstr编码的字符串在满足特定条件的情况下会被转换为raw编码的字符串对象。当对int编码类型的字符串执行了一定的操作使得字符串对象不再是整数时,编码将会从int转换为raw;当对embstr编码类型的字符串执行了任何修改操作后,编码将会从embstr转换为raw。
3、列表对象
列表对象的编码可以是ziplist或linkedlist。
ziplist编码可以作为列表对象的底层数据结构,每个压缩列表的entry节点保存一个列表元素。
-
编码的转化
当列表对象保存的所有字符串元素长度都小于64个字节且元素个数小于512个时,列表使用ziplist编码;否则,使用linkedlist编码。两种编码方式会按照上述条件自动转换。
注意:以上两个条件中的上限值是可以修改的,分别受redis配置文件中
list-max-ziplist-value
和list-max-ziplist-entries
选项的控制。
4、哈希对象
哈希对象的编码可以是ziplist或hashtable。
ziplist编码可以作为哈希对象的底层数据结构,每当有新的键值对要添加到哈希对象中时,程序会先将保存了键的压缩列表节点push到压缩列表表尾,然后再将保存了值的压缩列表节点push到压缩列表表尾。这样操作之后,保存了同一键值对的两个节点总是紧挨在一起,前一个节点代表键,后一个节点代表值;先添加到哈希对象中的键值对保存在压缩列表表头方向,后添加的键值对保存在压缩列表表尾方向。
-
编码的转化
当哈希对象中保存的所有键值对的键和值的字符串长度都小于64个字节且键值对数量小于512个时,使用ziplist编码;否则,使用hashtable编码。两种编码方式会按照上述条件自动转换。
注意:以上两个条件中的上限值是可以修改的,分别受redis配置文件中
hash-max-ziplist-value
和hash-max-ziplist-entries
选项的控制。
5、集合对象
集合对象的编码方式可以是intset或hashtable。
intset可以作为集合对象的底层数据结构,集合对象中的所有元素都被保存在整数集合中;hashtable是集合对象使用的另一种数据结构,字典的键是集合对象中的元素,字典的值都为null。
-
编码的转换
当集合对象中保存的所有元素都是整数值且元素个数不超过512个时,使用intset编码方式;否则,使用hashtable编码。两种编码方式会按照上述条件自动转换。
注意:以个条件中的上限值是可以修改的,受redis配置文件中
set-max-intset-entries
选项的控制。
6、有序集合对象
有序集合对象的编码方式可以是ziplist或skiplist。
ziplist编码的有序集合对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点保存,第一个节点保存元素的成员,第二个节点保存元素的分值。压缩列表内的集合元素按照分值从小到大进行排序,分值较小的元素放在靠近表头的位置,分值较大的放在靠近表尾的位置。
skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset结构同时包含一个跳跃表和一个字典。
struct zset {
zskiplist *zsl;
dict *dict;
}
zset中的跳跃表zsl按照分值从小到大保存了所有集合元素,每个跳跃表节点都保存了一个集合对象,跳跃表节点的object属性保存了元素的成员,score属性保存了分值;通过跳跃表可以对分值进行范围操作。zet中的字典dict保存了有序集合中所有元素从成员到分值的映射关系,通过字典可以使用O(1)的复杂度取出某个成员的分值。
-
编码的转换
当有序集合中所有成员的长度都小于64字节且保存的元素数量小于128个时,使用ziplist编码;否则,使用skiplist编码。两种编码方式会按照上述条件自动转换。
注意:以上两个条件中的上限值是可以修改的,分别受redis配置文件中
zset-max-ziplist-value
和zset-max-ziplist-entries
选项的控制。
7、内存回收与对象共享
内存回收
redis在自己的对象系统中构建了一个引用计数技术实现内存挥手机制,通过这一机制,程序可以跟踪对象的引用计数信息,在适当的时候释放对象所占用的内存空间。
对象的整个生命周期可以划分为三个阶段:创建对象、操作对象、释放对象。当创建一个对象时,引用计数初始化为1;当对象被一个新程序使用时,引用计数加一;当对象不再被一个程序使用时,引用计数减一;当对象的引用计数变为0时,对象所占用的内存会被释放。
对象共享
对象的引用计数还有对象共享的作用。多个键所对应的值相同时,可以共享同一个字符串对象,将引用计数加上相应的增量即可。共享机制对于节约内存非常有用,数据库中保存的相同值对象越多,就能节约越多的内存。
对于redis2.8来说,redis会在初始化服务器时,创建一万个字符串对象,这些对象包含了从0到9999的所有整数值,当要用到这些字符串对象时,服务器会使用这些共享对象,而不是在创建新对象。
redis不共享包含字符串的对象,因为在共享对象前程序需要检查共享对象和目标对象是否完全相同,如果共享对象是整数值的字符串对象,则复杂度为O(1);如果共享对象是字符串值的字符串对象,则复杂度为O(N);如果共享对象是包含了多个值对象的对象(如列表或hash等),则复杂度是O(N*N)。尽管共享更复杂的对象可以节约更多的内存,但收到CPU时间的限制,redis只会对包含整数值的字符串对象进行共享。
对象的空转
redisObject中的lru属性记录了程序最后依次访问对象的时间,通过将当前时间减去键的值对象的lru时间可以计算出值对象的空转时间。使用OBJECT IDLETIME key
可以打印键key对应的值对象的空转时长,OBJECT IDLETIME
命令在访问键的值对象时,不会改变lru。
如果服务器打开了maxmemory
选项且服务器用于回收内存的算法为 volatile-lru
或allkeys-lru
,那么当服务器 使用的内存超过了maxmemory
时,服务器会优先释放空转时长比较高的那部分键值对,以回收内存。
参考文献
命令在访问键的值对象时,不会改变lru。
如果服务器打开了maxmemory
选项且服务器用于回收内存的算法为 volatile-lru
或allkeys-lru
,那么当服务器 使用的内存超过了maxmemory
时,服务器会优先释放空转时长比较高的那部分键值对,以回收内存。
参考文献
redis设计与实现