Redis设计与实现笔记_8.对象

简介

  1. 基于之前的数据结构创建一个对象系统。包括字符串对象,列表对象,哈希对象,集合对象,有序集合对象五种

  2. 可以在执行命令之前,根据对象类型判断一个对象能否执行给定的命令

  3. 可以针对不同的使用场景,为对象设置多种不同的数据结构实现,从而优化对象在不同场景下的使用效率

  4. 实现基于引用计数的内存回收机制,实现内存共享机制,让多个数据库键共享同一个对象节约内存

  5. 对象带有访问时间记录信息,记录数据库键的空转时间

对象的类型和编码

使用对象来表示数据库的键和值,新创建一个键值对时,会创建两个对象分别代表键和值

typedef struct redisObject
{
	unsigned type:4;//类型
	unsigned encoding:4;//编码
	void * ptr;//指向底层实现数据结构的指针
}

类型

type属性记录了对象的类型,即五种对象类型之一
对于一个键值对,键总是字符串对象,值可以是五种对象类型之一

编码和底层实现

  1. 对象的ptr指针指向对象的底层实现数据结构,而这些数据结构由对象的encoding属性决定,即之前学习的几种数据结构
  2. 每种类型的对象都至少使用了两种不同的编码
  3. 通过encoding属性来设定使用的编码,极大地提升了灵活性和效率,可以根据不同的使用场景来为一个对象设置不同的编码,从而优化对象在某一场景下的效率

字符串对象

  1. 字符串对象的编码可以是int,raw,embstr
  2. 如果字符串对象保存整数值,且可以用long表示,则会将整数值保存在ptr中,enconding设为int
  3. 如果保存字符串值,并且长度大于39字节,就用SDS保存,并且设为raw
  4. 如果保存字符串值,并且长度小于39字节,就用embstr编码保存,并且设为embstr

embstr

专门用来保存短字符串的一种优化编码方式,和SDS差不多
不同的是,只调用一次内存分配函数来分配一块连续的空间,空间依次包含redisObject,sdshdr两个结构
优点:内存分配与释放次数由两次变为一次;字符串所有的数据保存在一块连续的内存中,更好利用缓存

编码的转换

  1. int编码的字符串对象如果操作后保存的不再是整数值,而是字符串值,则编码变为raw
  2. embstr编码的字符串对象只是可读的,对它执行任何修改命令,都会编程raw

列表对象

编码可以是ziplist,linkedlist。ziplist使用压缩列表作为底层实现,linkedlist采用双端链表作为底层实现。每个双端链表节点都保持一个字符串对象

字符串对象是Redis 5种类型的对象中唯一一种会被其他四种对象嵌套的对象

编码转换

使用ziplist编码的情况:

  1. 列表对象保存的所有字符串元素都小于64字节
  2. 列表保存的元素数量小于512

哈希对象

编码可以是ziplist,hashtable。

用ziplist则将键值对压入列表表尾,键值对紧挨一起,键在前,值在后。类似队列

hashtable编码用字典作为底层实现,字典的每个键与值都是一个字符串对象

编码转换

使用ziplist编码的情况:

  1. 列表对象保存的所有字符串元素都小于64字节
  2. 列表保存的元素数量小于512

集合对象

编码可以是intset,hashtable。

用intset编码的集合使用整数集合作为底层实现

用hashtable编码的集合使用字典作为底层实现,每个键是一个字符串对象,而值设为NULL

编码转换

使用intset编码的情况:

  1. 列表对象保存的都是整数值
  2. 列表保存的元素数量小于512

有序集合对象

有序集合的编码可以是ziplist或者skiplist

用ziplist编码则每个集合元素使用两个紧挨的压缩列表节点来保存,第一个节点保存元素的成员,第二个保存元素的分值

用skiplist编码实现

typedef struct zset
{
	zskiplist *zsl;
	dict * dict;
} zset;

zset中的zsl跳跃表按分值从小到大保存了所有集合元素

每个跳跃表节点中:object属性保存元素的成员,score属性保存元素的分值

此外,dict中,字典的键保存元素的成员,值保存元素的分值,这样,通过字典可以用O(1)的复杂度查找给定成员的分值

这两种数据结构都会通过指针来共享相同元素的成员和分值,所以使用跳跃表和字典来保存集合元素不会产生任何重复成员或者分值,不会浪费额外内存

编码转换

使用ziplist编码的情况:

  1. 有序集合保存的所有元素长度都小于64字节
  2. 有序集合保存的元素数量小于128个

类型检查和命令多态

在执行一个类型的特定命令之前,服务器会检查输入数据库键的值对象是否为执行命令所需的指令,如果是,服务器就会对键执行指定的命令。否则将拒绝执行命令,并向客户端返回一个类型错误。

类型检查是通过redisObject结构的type属性来实现的

多态命令的实现

Redis除了会根据值对象的类型来判断键是否能够执行指定指令之外,还会根据值对象的编码方式,选择正确的命令实现代码来执行命令

例如,LLEN命令是多态的,只要执行LLEN命令的是列表键,那么无论值对象使用的是ziplist编码还是linkedlist编码,命令都可以正常执行

DEL,TYPE等命令也是多态命令,无论输入的键是什么类型,这些命令都可以正常执行。区别在于,DEL,TYPE等命令是基于类型的多态,一个命令可以处理多种不同的类型的键,LLen命令是基于编码的多态,一个命令可以同时用于处理多种不同编码

内存回收

基于引用计数法实现内存回收机制,通过跟踪对象的引用计数信息,在适当的时候自动释放对象并进行内存回收

  1. 创建对象,引用计数值为1
  2. 对象被新程序使用,引用计数值+1
  3. 对象不被一个程序使用,引用计数值-1
  4. 引用计数值为0时,释放对象占用的内存

对象共享

步骤

让多个键共享同一个值对象步骤:

  1. 将数据库键的值指向一个现有的值对象
  2. 将被共享的值对象引用计数值+1

内置整数

Redis在初始化服务器时,会创建一万个字符串对象,存储[0,9999]的整数值。如果需要用到这些字符串对象,服务器则会使用这些共享对象,而不会新创建对象。

这些共享对象不仅仅只有字符串键可以使用,那些在数据结构中嵌套了字符串对象的对象都可以使用这些共享对象

为什么不共享包含字符串的对象

只有在共享对象和目标对象完全相同的情况下,程序才会将共享对象用作键的值对象

一个共享对象保存的值越复杂,则验证共享对象和目标对象是否相同所需的复杂度越高:

  1. 共享对象是整数值的字符串对象,验证操作为 O ( 1 ) O(1) O(1)
  2. 共享对象是保存字符串值的字符串对象,验证操作为 O ( N ) O(N) O(N)
  3. 共享对象是包含多个值的对象(如列表),验证操作为 O ( N 2 ) O(N^2) O(N2)

对象的空转时间

redisObject结构包含属性lru,记录了对象最后一次被命令程序访问的时间。可用当前时间-键的值对象的lru时间求得空转时间

,验证操作为 O ( 1 ) O(1) O(1)
2. 共享对象是保存字符串值的字符串对象,验证操作为 O ( N ) O(N) O(N)
3. 共享对象是包含多个值的对象(如列表),验证操作为 O ( N 2 ) O(N^2) O(N2)

对象的空转时间

redisObject结构包含属性lru,记录了对象最后一次被命令程序访问的时间。可用当前时间-键的值对象的lru时间求得空转时间

可通过设置,使得当服务器占用的内存数超过了,maxmemory的上限时,空转时间较高的那部分键会优先被服务器释放,从而回收内存

猜你喜欢

转载自blog.csdn.net/weixin_42249196/article/details/108347220