对象

  • redis没有直接使用数据结构来实现键值对数据库,而是基于这些数据结构创建一个对象系统。
  • 对于每种数据结构,redis保存的时候都是以封装对象的形式保存
1. 对象的类型与编码
  • 新创建一个键值对时,redis至少创建两个对象
  1. 一个对象用作键值对的键(键对象)
  2. 一个对象用作键值对的值(值对象)
  • Redis中每个对象都由一个redisObject结构表示,该结构有
  1. type属性
  • 表示值的类型,实现的编码方式由两种或者两种以上构成,在encoding属性中可以查看。
  1. encoding属性
  • 记录了对象所使用的编码,对象底层使用什么数据结构来实现。每种类型的对象至少由两种不同的编码格式。通过以下的语句查看
redis>OBJECT ENCODING key
  1. ptr属性:指向底层实现数据结构的指针
typedef struct redisObject{
	//类型
	unsigned type:4;
	
	//编码
	unsigned encoding:4;
	
	//指向底层实现数据结构的指针
	void *ptr;
}
2. 字符串对象
  • 字符串对象的编码可以是int raw embstr
  • 类型设置
  • 如果字符串对象时整数值,并且这个整数值可以用long表示,那么 void * 会变成long,encoding改成int
  • 如果字符串对象是字符串值,且长度大于32字节,使用SDS来保存。并且encoding设置为raw
  • 如果字符串对象是字符串值,且长度小于等于32字节,使用embstr编码方式保存
  • embstr编码是专门用于保存短字符传的一种优化编码方式。
  • long double类型的浮点数,在redis中也是用作字符串值保存的.encoding是embstr,在有需要的时候,先将字符串值转换成浮点数值,执行某些操作,然后再讲浮点数值,转换回字符串值,并继续保存在字符串对象里面。
  • 编码转换
  • int->raw :保存int的字符串对象,只要将保存的整数改成字符串,即会自动转换
  • embstr -> raw :redis中对embstr没有任何的修改程序,所以embstr字符串对象实际上是只读的。当执行任何修改命令时,会自动转成raw字符串对象
  • 命令
列表对象
  • 编码可以是ziplist或者linkedlist
  • ziplist:使用压缩列表作为底层实现,每个压缩列表节点保存一个列表元素
window_redis:8>lpush design-list a
"1"
window_redis:8>object encoding design-list
"ziplist"
  • linkedlist:使用双端链表作为底层实现,每个双端链表节点都保存一个字符串对象,而每个字符串对象都保存了一个列表元素
  • 编码转换
  • 当列表对象满足以下两个条件,列表对象使用ziplist编码,否则使用linkedlist编码
  1. 列表对象保存的所有字符串元素的长度都小于64字节
  2. 列表对象保存的元素数量小于512个
  • 命令
3. 哈希对象
  • 编码可以是ziplist或者hasttable
  • ziplist编码的哈希对象使用压缩列表作为底层,每当新的键值对加入到哈希对象时,程序会先将保存了键的压缩列表节点推入到压缩列表表尾,然后再讲保存了值的压缩列表节点推入到压缩列表表尾。
  • hashtable编码的哈希对象底层是字典,哈希对象中的每个键值对都使用一个字典键值对来保存
  1. 字典的每个键都是一个字符串对象,对象中保存了键值对的键
  2. 字典的每个值都是一个字符串对象,对象中保存了键值对的值。
  • 编码转换
  • 当哈希对象满足以下两个条件,使用ziplist编码
  1. 所有键值对的字符串长度都小于64字节
  2. 保存的键值对的个数小于512个
  • 命令
4. 集合对象
  • 编码可以是intset或者hashtable
  • intset编码的集合对象使用整数集合作为底层实现。集合对象包含的所有元素都被保存在整数集合里面。
  • hashtable编码的集合使用字典作为底层实现,字典的每个键都是一个字符串对象,每个字符串对象包含了一个集合元素,而字典的值则全部被设置为NULL
  • 编码转换
  • 当集合对象满足以下两个条件,使用intset编码,否则转换为hashtable编码
  1. 集合对象保存的所有元素都是整数值
  2. 集合对象保存的元素数量不超过512个
  • 命令
5. 有序集合对象
  • 编码可以是ziplist或者skiplist
  • ziplist编码的压缩列表对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个元素保存元素的分值。压缩列表内的集合元素按分值从小到大进行排列,分值较小的元素被放置在靠近表头的方向,而分值较大的元素则被放置在靠近表尾的方向。
  • 底层使用的zset结构实现,一个zset结构包含一个字典和一个跳跃表
typedef struct zset{
	zskiplist * zsl;
	dict * dict;
}
  1. 字典
  • dict字典为有序集合创建一个从成员到分值的映射,字典中的每个键值对都保存了一个集合元素:字典的键保存了元素的成员,字典的值则保存了元素的分值。
  • 通过此字典程序可以用O(1)复杂度来查找给定成员的分值。
  1. 跳跃表:
  • 按照分值从小到大保存了所有集合元素,每个跳跃表节点都保存一个集合元素。跳跃表节点的object属性保存了元素的成员,而跳跃表节点的score属性则保存了元素的分值。
  • 通过此跳跃表可以进行范围型操作,比如zrank
  • 使用两种结构的原因,是在范围查找和获取特定元素的分值时,效率更高,避免了一种结构时,多出来的耗费时间,通过空间换取时间的方式。
  • 编码转换
  • 当有序集合同事满足以下两个条件,对象使用ziplist编码,否则使用zset
  1. 有序集合的元素数量小于128
  2. 有序集合保存的所有元素成员的长度都小于64字节
  • 命令

类型检查和命令多态

  1. 命令可以分为两类
  1. 可以对任意类型的键执行,比如expire del
  2. 针对特定类型的键执行
类型检查
  1. 使用非特定类型的命令操作类型时,报错
  2. redis中类型特定命令所进行的类型检查是通过redisObject结构的type属性来实现。
  • 在执行llen命令之前,服务器会先检查输入数据库键的值对象是否为列表类型,也就是检查redisObject结构的type属性的值是否为redis_list,如果是则执行,否则报错。
多态命令的实现
  • 一个命令根据对象内部的不同编码,从而使用不同的方式进行执行
  • 列表对象的底层是通过ziplist或者linkedlist,当使用llen时,会判断encoding是什么类型,然后再去执行
  • 基于类型的多态:一个命令可以同时用于处理多种不同的类型的键,比如del,expire
  • 基于编码的多态:一个命令可以同时用于处理多种不同编码。比如llen.

内存回收

  • C语言不具备自动内存回收功能,所以Redis使用引用计数计数实现的内存回收机制。
  • 过程
  1. 在创建一个新对象时,引用计数的值会初始化为1
  2. 在对象被一个新程序使用时,引用计数值会增1
  3. 当对象不再被一个程序使用时,引用计数值会被减少1
  4. 当对象的引用计数值为0时,对象所占用的内存会被释放。

对象共享

  • Redis只对包含整数值的字符串对象进行共享。
  • Redis会共享值为0到9999的字符串对象。

对象的空转时长

  • redisObject中除了包含type encoding prt和refcount属性外,还包含最后一个属性lru属性。该属性记录了对象最后一次被程序访问的时间。
  • 使用下面语句进行查看
//该命令可以打印出给点键的空转时长,这个时长是通过当前时间减去键的值对象的Lru时间计算出来的
OBJECT IDLETIME key
  • 空转时长的作用
  • 如果浏览器打开了maxmemory选项,并且服务器用于回收内存的算法为volatile-lru或者allkeys-lru,那么当服务器占用的内存数超过了maxmemory选项所设置的上线值时,空转时长较高的那部分键会优先被服务器释放,从而回收。