Redis设计与实现(五)

版权声明:@Wrial https://blog.csdn.net/qq_42605968/article/details/88925887

对象

Redis中对象概述

在前面说的是一些Redis的底层数据结构,那些数据结构是对象实现的基础。在Reids的对象系统中有五种类型的对象。

  • 字符串对象
  • 列表对象
  • 哈希对象
  • 集合对象
  • 有序集合对象

在Java中也有对象,也可以将这些对象看作为相对独立的个体。还有一个和Java中很相似的地方就是Redis对象系统实现了内存回收机制(基于引用计数技术),当程序不需要的话就会被自动释放。Redis还可以在引用计数技术下,在适当的条件,可以让多个数据库键共享同一个对象来节约资源。在Redis对象中还有比较好的一点是,Redis对象带有访问事件的记录信息(可以用作我们后边将会讲到的计算空转)。

在了解具体对象之前,先来了解以下对象的主要数据结构。

对象的数据结构

typedef struct redisObject{
    
    //类型
    unsigned type;

    //编码
    unsigned encoding;

    //指向底层数据结构的指针
    void *ptr;
   
     ....
}

比如我们在数据库加入一个键值对,在这个过程中至少会创建两个对象。

  • type:记录着这个对象是什么类型的对象。也就是开篇中提到的五种对象之一。
  • encoding:在Redis中有一些编码常量被用来表示不同对象类型。基于任意一种type,type和encoding之间的一些组合。这些后边也会看到。比如:set类型可以和intset组合就是使用整数集合实现的集合对象。
  • ptr:指向对象实现底层的数据结构,这些数据结构由encoding来决定

type和encoding的区别是:type展示的是最外层的形态,encoding掌握的是底层的原子实现。底层由encoding来掌控可以增加Redis的灵活性,因为如果数据结构的决定因素由多个组成,那就很难分配。例如:在列表对象包含的元素比较少的时候,它的encoding是压缩列表,当它大的时候,encoding就会变为双端链表。这大大提高的灵活性。

深入了解Redis的五大对象

字符串对象

字符串对象的编码可以是int,raw,embstr。
  • int如果字符串对象里保存的是整数。

  • 这个整数可以用long类型的来表示,那么ptr的返回值会从void变为long,并将字符串对象的编码设置为int。

  • 如果超过long的长度就是embstr和raw类型。

  • embstr:当保存的字符串长度小于等于39字节,那字符串对象将使用embstr编码的方式来保存。

  • embstr是专门用来保存短字符串的优化编码方式,它采用的是使用一块连续的内存空间依次保存redisObject和sdshdr。而raw就不用,它要连续的分配两次内存空间分别给redisobject和sdshdr。

  • 释放embstr因此也只需要释放一次资源。

    扫描二维码关注公众号,回复: 5747518 查看本文章
  • 字符串对象的所有数据保存在连续的单元里,能够更好的利用缓存带来的优势。

  • raw:当字符串长度大于39,那字符串对象使用SDS来保存这个对象,并将编码设置为raw。

  • 在前面对SDS已经了解它的底层了,SDS对于embstr的优势就是它可以存储长度很长的字符串,如果embstr存储了超过它极限的字符串它就得重新分配资源转为SDS。

字符串对象编码的转换
  • 对于int和embstr,如果满足转换条件就会转为raw。
  • 对int类型执行一些命令(如append一个字符或者字符串)就从int变为raw。
  • 对embstr执行任何的修改命令时,都会从embstr变为raw。

列表对象

列表对象的编码可以是ziplist,linkedlist。
  • ziplist:使用压缩列表作为底层实现。
  • 列表对象保存的所有字符串长度都小于64字节。
  • 列表对象保存的元素数量小于512个。
  • linkedlist:使用双向链表作为底层实现,每一个节点都是一个字符串对象。
  • 不能满足ziplist的就使用linklist作为对象的编码。

例如对一个短的字符串扩充为长度字符串,或者增加节点数量都有可能使列表对象的编码方式发生变化。

ziplist的极限条件是可以变的,可以在配置文件中设置。

哈希对象

哈希对象的编码可以是ziplist,hashtable。
  • ziplist:
  • 当有新键值对进来的时候,先把键节点推入压缩列表表尾,然后再把值节点推入哈希列表表尾。键的后边紧跟着值。先进的在列表的表头,后进的在表尾。
  • 列表对象保存的所有字符串长度都小于64字节。
  • 列表对象保存的元素数量小于512个。
  • hashlist:使用字典作为底层实现。
  • 不能满足ziplist就转为hashlist。

ziplist的条件都一样,当然也可以和上边一样在配置文件中修改。

集合对象

集合对象可以是intset,hashtable。
  • intset:使用整数集合作为底层实现。
  • 集合中保存的必须都是整数。
  • 集合对象保存的元素小于等于512。
  • hashtable:使用字典作为底层实现,每一个键都是一个字符串对象,值都为空。也就是将所有的键作为一个字符串集合。
  • 不能满足instset条件的都使用字典作为底层实现。

集合对象保存的元素数量可以更改。

有序集合对象

有序集合的编码可以是ziplist,skiplist。
  • ziplist:使用压缩列表作为底层实现。每个集合元素紧挨在一起,第一个是元素成员(member)紧跟其后的是分值(score)。

  • 压缩列表按分值大小进行排序,分值小的靠近表头。

  • 有序集合保存的元素数量小于128

  • 有序集合保存的所有元素长度都小于64字节。

  • skiplist:使用zset作为底层实现。zset的结构体中同时包含一个字典和一个跳跃表。主要用于排序。

  • 在zset中,zsl按照分值由大到小保存了所有的元素,每个跳跃表节点都保存了一个元素(跳跃表节点的obj保存的是元素成员,score保存的分值)。

  • 在zset中dict字典为有序集合创建了一个从成员到分值的映射,字典中每一个键值对都保存一个元素。主要用于查找。

  • 如果不能满足ziplist的所有要求,那就用zset作为底层实现的数据结构。

   typedef struct zset{
      
     //跳跃表
     zskiplist *zsl;
     //字典
     dict *dict;

}

设计者很精巧的设计了这一方案,各取所长,用指针来共享相同元素和分值,所以这样不会产生重复元素,并不会浪费太多的空间。

前面介绍完Redis的五大对象,那如果对不同对象进行不同的命令操作会怎么样?

类型的检测

可以笼统的把Redis中的命令分为两种

  1. 通用型命令:如del,rename,type,object等等

在这里插入图片描述

  1. 对象特有命令

如下操作

  • set,get,append,strlen等只能对字符串键执行

    在这里插入图片描述

  • hdel,hset,hget,hlen等只能对哈希键执行,用别的操作也不能操作哈希键。如下图

    在这里插入图片描述

  • rpush,lpop,linsert,llen,lrange等只能对列表键使用

    在这里插入图片描述

  • sadd,spop,sinsert,scard等命名只能对集合键使用

    在这里插入图片描述

  • zadd,zcard,zrank,zscore等只能对有序集合键使用

    在这里插入图片描述

类型检测的实现流程

  1. 执行命令前,服务器检测输入数据库的键和值是否符合类型,是的话就跳到2,不是的话就跳到3
  2. 执行命令
  3. 服务器拒绝执行并返回错误

多态命令

什么是多态命令?

多态命令就是Reids除了根据值的对象类型来判断指令的能否执行之外,也还会根据值对象的encoding来选择命令是否可执行。

多态命令的执行流程

  1. 客户端发送指令
  2. 服务器检查输入数据库键的值是否相符,相符的话就执行3,不相符的话就返回一个错误
  3. 根据这个命令所对应的实现方式,调用各自的方法。

打个比方就是这个命令相当于是一个总管,给下面的人要收他们中某一个人这一个月的销售报告,不归他管的人就走开了,归他管的人就各自找他们的销售报告,相当于总管这个命令对他的下属都有效。

内存回收

前面说到过Redis使用引用计数技术和LRU(最近最久未使用算法)实现的垃圾回收。

  • 当创建一个新的对象时,计数的值会被初始化为1
  • 当对象被一个新的程序使用,计数的值+1
  • 当对象不再被程序使用时,计数的值会-1
  • 当值变为0时,就会被释放掉

虽然,原理很简单,但是实现的过程也是挺麻烦的呢。比如怎么能认为程序不会再使用这个对象了呢?

算法的实现采用的是HashMap+Double LinkedList。

详情还得看大佬写的文章https://www.cnblogs.com/WJ5888/p/4371647.html

对象共享

什么是对象共享?

就是一段内存存的值,被多个对象使用。

Redis中对象共享的步骤

  1. 让数据库的值指针指到已有的值对象
  2. 将被共享的值的引用计数器+1

这个也正好和垃圾回收对接上了。
比如下边这个例子: refcount是查看引用计数器的值,可以看到在共享后,refcount就+1,删除后refcount就-1.
在这里插入图片描述

这些共享不止字符串键可以使用,那些嵌套了字符串键的其他对象也可以使用。但是Redis中不共享字符串的对象,字符串比对需要的时间复杂度高,而且如果这个对象内含多个字符串对象那验证的事件复杂度将会是O(ne2)

在这里插入图片描述

对象的空转时长

还记得在前面在说对象的时候说的lru属性,它是用来记录最后一次被命令访问的事件。这也就可也顺理成章的和前面说的LRU垃圾回收相应了。可以通过object idletime 来查看空转的时间(now-lastTime),然后根据空转的时长来判断是否进行回收,前提是服务器打开maxmemory选项,超过上限时,会将空转时间长的进行回收。

猜你喜欢

转载自blog.csdn.net/qq_42605968/article/details/88925887