Redis基于所用到的主要数据结构创建了一个对象系统,包含字符串对象,列表对象,哈希对象,集合对象和有序集合对象。根据对象的类型可以判断一个对象是否可以执行给定的命令,也可针对不同的使用场景,对象设置有多种不同的数据结构实现,从而优化对象在不同场景下的使用效率。
对象的类型与编码
Redis中的每个对象都是由如下结构表示(列出了与保存数据有关的三个属性)
typedef struct redisObject {
unsigned type:4;//类型 五种对象类型
unsigned encoding:4;//编码
void *ptr;//指向底层实现数据结构的指针
//...
int refcount;//引用计数
//...
unsigned lru:22;//记录最后一次被命令程序访问的时间
//...
}robj;
1、类型
- Redis的键值对中,键总是字符串对象,值可以是五种对象类型之一
- TYPE命令:
set msg “hello”
OK
type msg
string
不同类型值对象的TYPE命令输出
对象 | type属性的值 | TYPE命令输出 |
---|---|---|
字符串对象 | REDIS_STRING | “string” |
列表对象 | REDIS_LIST | list |
哈希对象 | REDIS_HASH | hash |
集合对象 | REDIS_SET | set |
有序集合对象 | REDIS_ZSET | zset |
2、编码和底层实现
- encoding属性记录了对象所使用的编码
- 每种类型的对象都至少使用了两种不同的编码
- OBJECT ENCODING命令:
SET msg “hello”
OK
OBJECT ENCODING msg
“embstr”
不同类型和编码的对象
类型 | 编码 | OBJECT ENCODING命令输出 | 对象 |
---|---|---|---|
REDIS_STRING | REDIS_ENCODING_INT | “int” | 使用整数值实现的字符串对象 |
REDIS_STRING | REDIS_ENCODING_EMBSTR | “embstr” | 使用embstr编码的简单动态字符串实现的字符串对象 |
REDIS_STRING | REDIS_ENCODING_RAW | “raw” | 使用简单动态字符串实现的字符串对象 |
REDIS_LIST | REDIS_ENCODING_ZIPLIST | “ziplist” | 使用压缩列表实现的列表对象 |
REDIS_LIST | REDIS_ENCODING_LINKEDLIST | “linkedlist” | 使用双端链表实现的列表对象 |
REDIS_HASH | REDIS_ENCODING_ZIPLIST | “ziplist” | 使用压缩列表实现的哈希对象 |
REDIS_HASH | REDIS_ENCODING_HT | “hashtable” | 使用字典实现的哈希对象 |
REDIS_SET | REDIS_ENCODING_INTSET | “intset” | 使用整数集合实现的集合对象 |
REDIS_SET | REDIS_ENCODING_HT | “hashtable” | 使用字典实现的集合对象 |
REDIS_ZSET | REDIS_ENCODING_ZIPLIST | “ziplist” | 使用压缩列表实现的有序集合对象 |
REDIS_ZSET | REDIS_ENCODING_SKIPLIST | “skiplist” | 使用跳跃表和字典实现的有序集合对象 |
encoding属性来设定对象所使用的编码,没有关联一种固定的编码,极大提升Redis的灵活性和效率。
字符串对象
1、字符串对象编码选择
- 如果保存的是整数值,字符串对象的编码设置为int
- 如果保存的是一个字符串值,并且这个字符串值的长度大于32字节,对象的编码设置为raw,使用SDS保存
- 如果保存的是一个字符串值,并且这个字符串值的长度小于等于32字节,对象的编码设置为embstr(专门用于保存短字符串的一种优化编码方式)
2、embstr比raw方式的优势
- embstr编码将创建字符串对象所需的内存分配次数从raw编码的两次降低为一次
- 释放embstr编码的字符串对象只需要调用一次内存释放函数
- embstr编码的字符串对象所有数据都保存在一块连续的内存里面,能够更好地利用缓存带来的优势
3、long double 类型浮点数保存(embstr或raw)
- long double 类型浮点数是作为字符串值保存的,先将浮点数转为字符串值,然后再保存,需要用到的时候再将对象里面的字符串值转为浮点数值,然后再操作,操作完再转换回来保存
4、编码转换
- int编码的字符串对象和embstr编码的字符串对象在条件满足的情况下,会被转换为raw编码的字符串对象
- 如果对int编码的字符串对象进行了一些操作使得对象保存的不再是整数值,则会转换
- embstr编码的字符串对象在执行修改命令之后,总会变成一个raw编码的字符串对象
5、字符串对象是redis五中类型对象中唯一会被其他四种类型对象嵌套的对象
列表对象
1、列表对象编码可以是ziplist或linkedlist
2、编码转换(限制条件的上限值可以修改)
- 使用ziplist编码同时满足两个条件:列表对象保存的所有元素长度都小于64字节;列表对象保存的元素数量小于512个
- 不能满足上述条件的列表对象使用linkedlist编码,当不满足时编码转换操作会被执行,压缩列表里的所有列表元素转移并保存到双端链表里面
哈希对象
1、哈希对象的编码可以是ziplist或hashtable
2、ziplist编码的哈希对象使用压缩列表作为底层实现:
- 保存了同一键值对的两个节点总是紧挨在一起,保存键的节点在前,保存值得节点在后
- 先添加到哈希对象中的键值对会被放在压缩列表的表头方向,而后来添加到哈希对象中的键值对会被放在压缩列表的表尾方向
3、hashtable编码的哈希对象使用字典作为底层实现:
- 字典的每个键都是一个字符串对象,对象中保存了键值对的键
- 字典的每个值都是一个字符串对象,对象中保存了键值对的值
4、编码转换(限制条件的上限值可以修改)
- 哈希对象使用ziplist编码同时满足条件:哈希对象保存的所有键值对的键和值得字符串长度都小于64字节;哈希对象保存的键值对数量小于512个
- 未能满足上述条件的使用hashtable编码,当不满足时编码转换操作会被执行,压缩列表里的所有列表元素转移并保存到字典里面
集合对象
1、集合对象的编码可以是intset或hashtable
2、intset编码的集合对象使用整数集合作为底层实现
3、hashtable编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,每个字符串对象包含了一个集合元素,二字典的值全部被设置为NULL
4、编码转换(限制条件的上限值可以修改)
- 使用intset编码同时满足条件:集合对象保存的所有元素都是整数值;集合对象保存的元素数量不超过512个
- 未能满足上述条件的使用hashtable编码,当不满足时编码转换操作会被执行,整数集合里的所有列表元素转移并保存到字典里面
有序集合对象
1、有序集合的编码可以是ziplist或skiplist
2、ziplist编码的压缩列表对象使用压缩列表作为底层实现
- 每个集合元素使用两个紧挨在一起的压缩泪飚节点来保存,第一个节点保存元素的成员,第二个元素则保存元素的分值
- 压缩列表内的集合元素按分值从小到大进行排序
3、skiplist编码的有序集合对象使用zset结构作为底层实现
- 一个zset结构同时包含一个字典和一个跳跃表
typedef struct zset {
zskiplist *zsl;//按分值从小到大保存所有集合元素,对有序集合进行范围型操作
dict *dict;//为有序集合创建了一个从成员到分值的映射,可以用O(1)复杂度查找给定成员的分值
}zset;
- 有序集合每个元素的成员都是一个字符串对象,而每个元素的分值都是一个double类型的浮点数
- 虽然zset结构同时使用跳跃表和字典来保存有序集合元素,但这两种数据结构都是通过指针来共享相同元素的成员和分值,不会浪费额外的内存
4、编码转换(限制条件的上限值可以修改)
- 使用ziplist编码同时满足条件:有序集合保存的元素数量小于128个;有序集合保存的所有元素成员长度都小于64字节
- 未能满足上述条件的使用skiplist编码,当不满足时编码转换操作会被执行,压缩列表里的所有列表元素转移并保存到zset结构里面
类型检查与命令多态
1、可以对任何类型的键执行的命令:DEL EXPIRE RENAME TYPE OBJECT等
2、只能对特定类型的键执行的命令:
- 字符串键:SET GET APPEND STRLEN等
- 哈希键:HDEL HSET HGET HLEN等
- 列表键:RPUSH LPOP LINSERT LLEN等
- 集合键:SADD SPOP SINTER SCARD等
- 有序集合键:ZADD ZCARD ZRANK ZSCORE等
3、类型特定命令在执行之前会先检查输入键的类型是否正确,是通过redisObject结构的type属性来实现的
4、多态命令实现:根据值对象的类型来判断键是否能够执行指定命令,根据值对象的编码方式,选择正确的命令实现代码来执行命令
内存回收
Redis在自己的对象系统中构建了一个引用计数计数实现内存回收机制,程序可以通过跟踪对象的引用计数信息,在适当的时候自动释放对象并进行内存回收
对象共享
1、对象的引用计数属性还带有对象共享的作用
2、让多个键共享同一个值对象需要执行:将数据库键的值指针指向一个现有的值对象;将被共享的值对象的引用计数增一
3、尽管共享更复杂的对象可以节约更多的内存,但受到CPU时间的限制,Redis只对包含整数值得字符串对象进行共享(完全相同的才能共享,验证相同时的复杂度决定的)
4、Redis会共享值为0~9999的字符串对象
对象的空转时长
1、对象会记录自己的最后一次被访问的时间,可以用于计算对象的空转时间
2、OBJECT IDLETIME命令可以打印给定键的空转时长=当前时间-lru(该命令本身不会更新lru值)
3、超过maxmemory选项上限,在有些回收内存算法下,空转时长较高的那部分键会优先被服务器释放