8.1对象的类型与编码
Redis中的每个对象都由一个redisObject结构表示,该结构中和保存数据有关的三个属性分别是type属性、encoding属性和ptr属性:
8.1.1 类型
对于Redis数据库保存的键值对来说,键总是一个字符串对象,而值则可以是字符串对象、列表对象、哈希对象、集合对象和有序集合对象其中的一种。
TYPE命令返回的结果为数据库键对应的值对象的类型,而不是键对象的类型。
8.1.2 编码和底层实现
对象的ptr指针指向对象的底层实现数据结构,而这些数据结构由对象的encoding属性决定。
encoding属性记录了对象所使用的编码。每种类型的对象都至少使用了两种不同的编码。通过encoding属性来设定对象所使用的编码,而不是为特定类型的对象关联一种固定的编码,极大地提升了Redis的灵活性和效率。
8.2 字符串对象
字符串对象的编码可以int、raw或者embstr.
如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面,并将字符串对象的编码设置为int
如果字符串对象保存的是一个字符串值,并且这个字符串值的长度大于32字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串的值,并将对象的编码设置为raw
如果字符串对象保存的 是一个字符串值,并且这个字符串值的长度小于等于32字节,那么字符串对象将使用embstr编码的方式来保存这个字符串值。
embstr编码是专门用于保存段字符串的一种优化编码方式,它和raw编码一样,都使用redisObject结构和sdshdr结构来表示字符串对象,raw编码会调用两次内存分配函数来分别创建redisObject结构和sdshdr结构,而embstr编码则通过一次内存分配函数来分配一块连续的空间,空间中依次包含redisObject和sdshdr两个结构。
embstr编码的字符串对象来保存短字符串值有以下好处:
1.embstr编码将创建字符串对象所需内存的次数从raw编码的两次降低为1次。
2.释放embstr编码的字符串对象只需要调用一次内存释放函数,而释放raw编码的字符串对象需要调用2次内存释放函数
3.embstr编码的字符串对象的所有数据都保存在一块连续的内存中,能更好地利用缓存带来的优势。
8.2.1 编码的转换
int编码的字符串对象和embstr编码的字符串对象在条件满足的情况下,会被转化为raw编码的字符串对象。
embstr编码的字符串对象实际上是只读的。当我们对embstr编码的字符串对象指向任何修改命令时,程序会先将对象的编码从embstr转换成raw,然后再执行修改命令。
8.2.2 字符串命令的实现
8.3 列表对象
列表对象的编码可以是ziplist或者linkedlist
ziplist编码的列表对象使用压缩列表作为底层实现,每个压缩列表节(entry)点保存了一个列表元素。
linkedlist编码的列表对象使用双端链表作为底层实现,每个双端链表节点(node)都保存了一个字符串对象,而每个字符串对象都保存了一个列表元素。
8.3.1 编码转换
当列表对象可以同时满足以下两个条件时,列表对象使用ziplist编码:
1.列表对象保存的所有字符串元素的长度都小于64字节
2.列表对象保存的元素数量小于512个
不能满足这两个条件的列表对象需要使用linkedlist编码
8.3.2 列表命令的实现
8.4 哈希对象
哈希对象的编码可以是ziplist或者hashtable
ziplist编码的哈希对象底层是压缩列表实现的。保存了同一键值对的两个节点总是紧挨在一起,保存键的节点在前,值的节点在后
先添加到哈希对象中的键值对会被放在压缩列表的表头方向,后被添加的键值对会被放到表尾方向
hashtable编码的哈希对象使用字典作为底层实现
8.4.1 编码转换
当哈希表同时满足以下两个条件时,哈希对象使用ziplist编码:
1.哈希对象保存的所有键值对的键和值的字符串长度都小于64
2.哈希对象保存的键值对小于512个
不满足这两个条件的哈希对象需要使用hashtable编码
8.5 集合对象
集合对象的编码可以是intset或者hashtable
intset编码的集合对象使用整数集合作为底层实现,集合对象包含的所有对象都被保存在整数集合里面
8.5.1 编码的转换
当集合对象同时满足以下两个条件时,集合对象使用intset编码:
1.集合对象保存的所有元素都是整数值
2.集合对象保存的键值对小于512个
不满足这两个条件的对象集合需要使用hashtable进行编码
8.5.2 集合命令的实现
因为集合键的值为集合对象,所以用于集合键的所有命令都是针对集合对象来构建的
8.6 有序集合对象
有序集合对象的编码可以是ziplist或者skiplist
ziplist编码的压缩对象使用压缩列表作为底层实现,每个集合元素使用两个挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,而第二个元素则保存元素的分值。压缩列表內的集合元素按分值从小到大进行排序,分值较小的元素被放置在靠近表头的方向,而分值较大的元素被放置在靠近表尾的方向。
skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset结构同时包含一个字典和一个跳跃表:
zset结构同时使用跳跃表和字典来保存有序集合元素,但这两种数据结构都会通过指针来共享相同元素的成员和分值,所以同时使用跳跃表和字典来保存集合元素不会产生任何重复成员或分值
8.6.1 编码的转换
当有序集合对象可以同时满足以下两个条件时,对象使用ziplist编码:
1.有序集合保存的元素数量小于128个
2.有序集合保存的所有元素成员的程度都小于64字节
不满足以上两个条件就使用skiplist
8.6.2 有序集合命令的实现
8.7 类型检查与命令多态
Redis中用于操作键的命令基本可以分为2类:
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只能对有序集合键执行
8.7.1 类型检查的实现
类型检查时通过redisObject中的type属性来实现的:
1.在执行一个类型特定命令之前,服务器会先检查输入数据库键的值对象是否为执行命令所需的类型,如果是的话,服务器就对键执行指定的命令
2.否则,服务器将拒绝执行命令,并向客户端返回一个类型错误
8.7.2 多态命令的实现
可以将DEL EXPIRE TYPE等命令也称为多态,是基于类型的多态——一个命令可以同时处理多钟不同类型的键
LLEN命令多态是基于编码的多态——一个命令可以同时用于多种不同编码
8.8 内存回收
Redis构建了一个引用计数技术实现内存回收机制,通过这一机制,程序可以跟踪对象的引用计数信息,在适当的时候自动释放对象并进行内存回收
每个对象的引用计数信息由redisObject结构的refcount属性记录
对象的引用计数信息会随着对象的使用状态而不断变化:
在创建一个新对象时,引用计数的值会被初始化为1
当对象被一个新程序使用时,它的引用计数值会被增一
当对象不再被一个程序使用时,他的引用计数值会被减一
当对象的引用计数值变为0时,对象所占用的内存会被释放
8.9 对象共享
对象的引用计数属性海带有对象共享的作用
在Redis中,让多个键共享同一个值对象需要执行以下两个步骤:
1.将数据库键的值指针指向一个现有的值对象
2.将被共享的值对象的引用计数增一
共享机制对节约内存非常有帮助,数据库中保存的相同值对象越多,对象共享机制就能节约越多内存
目前来说,redis会在初始化服务器时,创建一万个字符串对象,这些对象包含了从0到9999的所有整数值,当服务器需要用到值为0-9999的字符串对象时,服务器就会使用这些共享对象,而不是创建新对象
8.10 对象的空转时长
redisObject结构包含的最后一个属性为lru属性,该属性记录了对象最后一次被命令程序访问的时间
OBJECT IDLETIME命令可以打印出给定键的空转时长,这一空转时长就是通过将当前时间减去键的值对象的lru时间计算得出的。
键的空转时长还有一项作用:如果服务器打开了maxmemory选项,并且服务器用于回收内存的算法为volatile-lru或者allkeys-lru,那么当服务器占用的内存数超过了maxmemory选项所设置的上限值时,空转时长较高的那部分键会优先被服务器释放,从而回收内存。