对象
redis没有直接使用SDS、链表、字典、压缩列表、整数集合等数据结构来实现 键值对数据库,而是基于这些数据结构创建了一个对象系统,包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象 这五种类型的对象。
1、对象的类型与编码
redis使用对象来表示数据库中的键和值,对象包含字符串(string)、列表(list)、哈希(hash)、集合(set)、有序集合(z-set)5中类型。当在redis数据库中创建一个键值对时,至少创建两个对象,一个用作键(键对象),另一个用作值(值对象)。
好处是:1)可以针对不同的使用场景,为对象设置多种不同的数据结构实现,从而优化不同场景下的使用效率。2)对象系统实现了基于引用计数的内存回收机制。3)通过引用计数实现了对象共享机制,在适当的条件下通过共享同一对象来节约内存。4)redis对象带有访问时间记录信息(最后访问的时间戳),在启动maxmemory功能的情况下,较长时间未访问的键可能会优先被服务器删除
结构
type值
encoding值
2、字符串对象
字符串对象的编码可以是 int、raw、或者 embstr
如果一个字符串对象保存的是整数值,并且可以用long类型表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里(将void*转换成long),并将字符串对象的编码设置为int;
如果字符串对象保存的是一个字符串值,并且长度大于32字节,那么字符串对象将使用SDS来保存这个字符串值,并将编码设置为rew;
如果字符串对象保存的是一个字符串值,并且长度小于等于32字节,那么字符串对象将使用embstr编码的SDS来保存之歌字符串值。
embstr编码是专门用于保存短字符串的一种优化编码方式,和raw编码一样,都使用sdshdr结构来表示。但raw编码会调用两次内存分配分别创建redisObject结构和sdshdr结构,而embstr编码则动过调用一次内存分配来分配一块连续的空间,空间中依次包含redisObject和sdshdr两个结构。
embstr编码字符串对象在执行命令时,产生的效果和raw编码的字符串对象是相同的。
embstr编码的好处:
1)embstr编码将创建字符串对象所需的内存分配次数从raw编码的两次降为一次;
2)释放embstr编码字符串对象只需要调用一次内存释放函数;
3)embstr编码字符串对象所有数据保存在一块连续内存内,能更好地利用缓存的优势。
long double类型表示的数在Redis中作为字符串值来保存,如果要保存一个浮点数到字符串对象,需要先将浮点数转换成字符串值,然后再保存转换所得的字符串值。
3、列表对象
列表对象的编码可以是 ziplist 和 linkedlist
ziplist编码使用压缩列表作为底层实现,么个压缩列表节点(entry)保存一个元素。
linkedlist编码使用双端链表作为底层实现,每个双端链表节点(node)都保存一个字符串对象,么个字符串对象保存一个元素。
linkedlist编码的列表对象在底层的双端列表结构中包含了多个字符串对象,字符串对象是Redis五种类型对象中唯一会被其他四种类型对象嵌套的对象。
使用ziplist编码需要同时满足以下2个条件,否则使用linkedlist编码:
1)列表对象保存的所有字符串元素的长度都小于64字节;
2)列表对象保存的元素数量小于512个;
4、哈希对象
哈希对象的编码可以是 ziplist 或者 hashtable
ziplist编码 使用压缩列表作为底层实现,每当有新的键值对加入到哈希对象时,先将保存了键的节点推入列表表尾,再讲保存了值的节点推入列表表尾。1)保存了同一键值对的连个节点紧挨在一起,保存键的节点在前,保存值的节点在后;2)先添加到哈希对象的键值对会被放在压缩列表的表头方向,后添加的键值对会被放在压缩列表的表尾方向。
hashtable编码 的哈希对象使用字典作为底层实现,每个键值对使用一个字典键值对保存:字典的键都是一个字符串对象,保存哈希对象的键;字典的值都是一个字符串对象,保存键值对的值。
编码转换
哈希对象保存的所有键值对的键和值的字符串长度都小于64字节;
哈希对象保存的键值对数量小于512个;
同时满足以上2个条件使用ziplist编码;否则使用hashtable编码
5、集合对象
集合对象set的编码可以是 intset或者hashtable
intset编码的集合对象使用整数集合作为底层实现
hashtable编码的集合对象使用字典作为底层实现,字典的键保存集合元素,字典的值为null。
当集合对象同时满足以下两个条件时,使用intset编码
1)集合对象保存的所有元素都是整数值;
2)集合对象保存的元素数量不超过512个。
6、有序集合对象
有序集合 zset 编码可以是ziplist或者skiplist
ziplist编码 每个集合元素使用两个相连的节点来保存,第一个节点保存元素成员(member),第二个元素保存元素的分值(score)。压缩列表内的集合元素按照分值的从小到大排序。
skiplist编码 使用zset机构作为底层实现,一个zset结构同时包含一个字典和一个跳跃表。
zset结构中的zsl跳跃表按分值从小到大保存了所有集合元素,每个节点保存一个元素:跳跃表节点的object属性保存元素(member),跳跃表节点的score属性保存分值(score);zset结构中dict字典为有序集合创建了一个从成员到分值的映射,字典中每个键值对保存一个集合元素:字典的键保存元素成员(member),字典的值保存元素的分值(score),通过这个字典可以用O(1)复杂度查找给定成员的分值
当同时满足以下条件时,对象使用ziplist编码
1)由于集合保存的元素数量小于128个;
2)有序集合保存的所有元素成员的长度小于64字节。
7、类型检查与命令多态
7.1 类型检查
Redis中用于操作键的命令基本上分为两种类型:
1)可以对任何类型的键执行,如DEL、EXPIRE、RENAME、TYPE、OBJECT
2)只能UI特定类型的键执行
SET、GET、APPEND、STRLEN命令只能对 string类型执行
HDEL、HSET、HGET、HLEN命令只能对 hash类型执行
RPUSH、LPOP、LINSERT、LLEN 命令只能对 list类型执行
SADD、SPOP、SINTER、SCARD命令只能对 set类型执行
ZADD、ZCARD、ZRANK、ZSCORE命令只能对 zset类型执行
为了确保只有指定类型的键可以执行某些特定的命令,在执行一个类型特定的命令之前,Redis会先检查输入键的类型是否正确,然后再决定是否执行给定的命令。
类型检查通过redisObject结构的type属性来实现。
7.2 多态命令
Redis除了根据值对象的类型判断键是否能够执行命令之外,还会根据值对象的编码方式,选择正确的命令实现代码来执行命令。
DEL、EXPIRE 等命令是基于类型(type)的多态,一个命令可以同时用于处理多种不同类型的键
LLEN、SET等命令是基于编码(encoding)的多态,一个命令可以同时用于处理多种不同的编码
8、内存回收
Redis在自己的对象系统中构建了一个引用计数技术实现的内存回收机制。通过这一机制,程序可以通过跟踪对象的引用计数信息,在适当的时候自动释放对象并进行内存回收。
对象的引用计数会随着对象的使用状态而不断变化:
1)在创建一个新对象时,引用计数的值被初始化为1;
2)当对象被一个新程序使用时,它的引用计数值+1;
3)当对象不再被一个程序使用时,它的引用计数值-1;
4)当对象的引用计数值为0时,对象所占用的内存会被释放。
9、对象共享
除了用于实现引用计数内存回收机制之外,对象的引用计数属性还带有共享的作用。
在Redis中,让多个键共享同一个值对象需要执行以下两个步骤:
1)将数据库键的值指针指向一个现有的值对象;
2)将共享的值对象的引用计数+1。
Redis会在初始化服务器时,创建一万个字符串对象,包含了从 0 到 9999 的所有整数值,当服务器需要用到值为0到9999的字符串对象时,服务器就会使用这些共享对象。
共享对象不仅字符串键可以使用,嵌套了字符串对象的对象都可以使用这些共享对象。
Redis只对 0 到 9999 的字符串对象进行共享
10、对象空转时长
redisObject的 lru 属性记录了对象最后一次被命令程序访问的时间;
空转时长= 当前时间 - lru时间
OBJECT IDLETIME 命令在访问键的值对象时,不会修改值对象的lru属性。