Redis对象
Redis系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象。
Redis还实现了基于引用计数的内存回收机制,通过引用计数实现了对象共享机制,在这机制在,通过让多个数据库键共享同一个对象来节约内存。
Redis的类型与编码
类型
对象的type属性记录了对象的类型
编码和底层实现
REDIS_STRING
- REDIS_ENCODING_INT:使用整数值实现的字符串对象。
- REDIS_ENCODING_EMBSTR:使用embstr编码的简单动态字符串实现字符串对象。
- REDIS_ENCODING_RAW:使用简单动态字符串实现字符串对象。
REDIS_LIST
- REDIS_ENCODING_ZIPLIST:使用压缩列表实现列表对象。
- REDIS_ENCODING_LINKEDLIST:使用双端链表实现列表对象。
REDIS_HASH
- REDIS_ENCODING_ZIPLIST:使用压缩列表实现哈希对象。
- REDIS_ENCODING_HT(HASHTABLE):使用字典实现哈希对象。
REDIS_SET
- REDIS_ENCODING_INTSET:使用整数集合实现集合对象。
- REDIS_ENCODING_HT:使用字典实现集合对象。
REDIS_ZSET
- REDIS_ENCODING_ZIPLIST:使用压缩列表实现有序集合对象。
- REDIS_ENCODING_SKIPLIST:使用跳跃表事项有序集合对象。
Redis可以根据不同的使用场景来为一个对象选择不同的编码。
字符串对象
字符串对象的编码可以是 int、embstr或raw。
- 字符串保存的是一个整数值并且这个整数值可以用long类型表示,那么编码为int;
- 字符串长度小于等于39字节,则编码为embstr;
- 字符串长度大于39字节,则编码为raw;
Embstr和raw的区别
Embstr编码是专门用于保存较短字符串的一种优化编码方式,这种编码方式和raw编码一样,都使用redisObject结构和sdshdr结构来保存字符串对象;
但raw编码会调用两次内存分配函数来分别创建RedisObject和sdshdr结构,而ebmstr编码则通过调用一次内存分配函数来分配一块连续的空间,空间依次包括RedisObject和sdshdr两个结构。
编码的转换
Redis没有为embstr编码的字符串实现任何相应的修改程序,embstr编码的字符串在对象执行修改命令之后,总会变成一个raw编码的字符串对象。
列表对象 List
列表对象的编码可以是ziplist或者linkedlist。
编码的转换
当列表对象可以同时满足以下两个条件时,列表对象使用ziplist编码:
- 列表对象保存的所有字符串元素的长度都小于64字节;
- 列表对象保存的元素数量小于512个;
不满足其中一个都会将列表对象的编码转换为linkedlist编码。
哈希对象 Hash
哈希对象的编码可以是ziplist或者hashtable
ziplist编码
使用ziplist编码(压缩列表)的哈希对象,在新增键值对时,程序会先将保存了键的压缩列表节点推入到压缩列表表尾,然后再将保存了值的压缩列表节点推入到压缩列表表尾;因此:
- 保存了同一键值对的两个节点总是紧挨在一起,保存键的节点在前,保存值的节点在后;
- 先添加的键值对先表头方向,后添加的键值对则在表尾方向;
Hashtable编码
Hashtable编码的哈希对象使用字典作为底层实现,哈希对象中的每个键值对都使用一个字典键值对来保存:
- 字典的每个键都是一个字符串对象,对象中保存了键值对的键;
- 字典的每个值都是一个字符串对象,对象中保存了键值对的值;
编码的转换
当哈希对象同时满足以下两个条件时,哈希对象使用ziplist编码:
- 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节;
- 哈希对象保存的键值对数量小于512个
不同时满足这两个条件时,哈希对象则使用hashtable编码实现;
集合对象 Set
集合对象的编码可以是intset或hashtable;
Intset编码
Intset编码的集合对象使用整数集合作为底层实现,集合对象包含的所有元素都被保存在整数集合里面。
Hashtable编码
Hashtable编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,每个字符串对象包含了一个集合元素,而字典的值则全被设置为null;
编码的转换
当集合对象可以同时满足以下两个条件时,对象使用intset编码:
- 集合对象保存的所有元素都是整数值;
- 集合对象保存的元素数量不超过512个;
不能同时满足这两个条件的集合对象需要使用hashtable编码。
有序集合对象
有序集合的编码可以是ziplist或者skiplist;
Ziplist编码
Ziplist编码实现的有序集合对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员(member),第二元素来保存元素的分值(score);
压缩列表内的集合元素按分值从小到大进行排序,分值较小的元素被放置在靠近表头的位置,而分值较大的元素则放置在靠近表尾的位置;
Skiplist编码
Skiplist编码的有序集合使用zset结构作为底层实现,一个zset结构同时包含一个字典和一个跳跃表;
typedef struct zset {
zskiplist *zsl;
dict *dict;
}
Zset结构中的zsl跳跃表按分值从小到大保存了所有集合元素,每个跳跃表节点都保存了一个集合元素:跳跃表节点的Object属性保存了元素的成员,而跳跃表节点score属性则保存了元素的分值。
Zset结构中的dict字典为有序集合创建了一个成员到分值的映射,字典中的每个键值对都保存了一个集合元素:字典的键保存了元素的成员,而字典的值则保存了元素的分值;
编码的转换
当有序集合对象可以同时满足以下两个条件时,对象使用ziplist编码:
- 有序集合保存的元素数量小于128个;
- 有序集合保存的所有元素成员的长度都小于64字节;
不能满足以上两个条件的有序集合对象将使用skiplist编码。
Redis对象内存回收
因为C语言不具备自动内存回收功能,所以Redis在自己的对象系统中构建了一个引用计数(reference counting)技术实现的内存回收机制,通过这一机制,程序可以通过跟踪对象的引用计数信息,在适当的时候自动释放对象并进行内存回收。
每个对象的引用计数信息有RedisObject结构的refcount属性记录。
对象的引用计数信息会随着对象的使用状态而不断改变:
- 在创建一个新对象时,引用计数的值会被初始化为1;
- 当对象被一个新程序使用时,引用计数值增1;
- 当对象不再被一个程序使用时,引用计数值减1;
- 当对象的引用计数值变为0时,对象所占用的内存就会被释放;
对象共享
对象的引用计数属性除了实现引用计数内存回收机制之外,还带有对象共享的作用。
Redis会共享值为0-9999的字符串对象;
共享对象不仅只有字符串键可以使用,五种Redis对象都可以使用这些共享对象;
受到CPU时间的限制,Redis只对包含整数值的字符串进行共享。只有在共享对象和目标对象完全相同的情况下,程序才会将共享对象 用作键的值对象,而一个共享对象保存的值越复杂,验证共享对象和目标对象是否相同所需要的复杂度就越高,消耗的CPU时间也越多;
对象的空转时间
RedisObject结构包含的最后一个属性为lru属性,该属性记录了对象最后一次被命令程序访问的时间;如果服务器打开了maxmemory选项,那么当服务器占用的内存数超过了maxmemory选项所设置的上限值时,空转时间较长的那部分键会优先被服务器释放,从而回收内存;