Redis第8章 对象

  • 前面几章介绍的都是Redis基于C优化的基础数据结构,这一章具体介绍五种对象类型的底层结构实现,并且在特定情况下结构会发生转化。

8.1 对象的类型与编码

  • Redis每创建一次键值对时至少要生成两个对象,键对象和值对象,每个对象都是用redisObject结构表示。
typedef struct redisObject {
  //类型
  unsigned type:4;
  //编码
  unsigned encoding:4;
  //指向底层实现数据结构的指针
  void *ptr;
  //...
} redisObject;
8.1.1 类型(TYPE)
  • 键的类型:键总是一个字符串。
  • 值的类型:值类型也就是最常见的5中基本类型,分别是字符串(REDIS_STRING)、列表(REDIS_LIST)、哈希(REDIS_HASH)、集合(REDIS_SET)、有序集合(REDIS_ZSET)。
  • TYPE命令:TYPE + key可以查看这个对象的值类型。
8.1.2 编码和底层实现(encoding或缩写ENC)
  • 编码一共有8种,对应的数据结构分别是:
    • REDIS_ENCODING_INT:long类型整数
    • REDIS_ENCODING_EMBSTR:embstr编码的简单动态字符串
    • REDIS_ENCODING_RAW:简单动态字符串
    • REDIS_ENCODING_HT:字典
    • REDIS_ENCODING_LINKEDLIST:双端链表
    • REDIS_ENCODING_ZIPLIST:压缩列表
    • REDIS_ENCODING_INTSET:整数集合
    • REDIS_ENCODING_SKIPLIST:跳跃表和字典
  • 每种基本类型至少使用了两种不同的编码,不同场景下有不同的解决方案,例如Redis会把数字和字符都算做基本类型里的REDIS_STRING,但底层存储时又会有不同的编码,从而提升效率,下面列举所有基本类型可能会使用到的底层编码。


    基本类型和编码的对应关系.png
  • OBJECT ENCODING命令:OBJECT ENCODING + key可以查看这个对象值的编码。

8.2 字符串对象

  • 字符串编码可以是REDIS_ENCODING_INT、REDIS_ENCODING_RAW或者REDIS_ENCODING_EMBSTR(这章后续用缩写int、raw、embstr缩写表示)。
  • 编码判定条件:
    • 浮点或整形数字不超过32位,用int
    • 超长数字、字符串并且小于等于39字节,用raw
    • 超长数字、字符串并且大于39字节,用embstr
  • raw和embstr的共同点和区别:
    • raw和embstr都是使用SDS结构保存数据,但embstr对SDS做了一些优化。
    • raw调用两次内存分配,分别创建了redisObject和sdshdr两个空间,embstr只调用一次,并且内存空间连续。
    • 释放时raw也是调用两次释放,embstr调用一次。
    • embstr本身就是短字符串时使用,内存连续所以效率更高。
    • embstr是只读的,创建后如果需要修改,直接会转化成raw。
  • 保存浮点类型时存的是embstr结构,执行一些数字操作时再转成浮点型。
  • 浮点型和长整型在数据过长的情况下也会扩展成embstr甚至raw


    embstr.png

    raw.png
8.2.1 编码的转换
  • 之前提到了在不同条件下string的底层数据结构实现不同,int和embstr会升级成raw,最常见的场景就是APPEND命令。
  • 例如初始化一个数字底层是int,拼接一个字符串后就变成了raw。
  • embstr同理,但embstr创建后是只读的,任何的修改操作都会直接升级成raw,int和embstr之间不会互相升级。
8.2.1 字符串命令的实现
字符串指令实现.png

8.3 列表

  • 列表对象编码可以是ziplist或linkedlist。
  • ziplist和linkedlist的区别:
    • ziplist地址连续,而且很少浪费空间,大概就能猜到肯定是节点数少时候会用到ziplist
    • linkedlist是节点间引用连接,每个节点又是一个独立的字符串对象,是上一个小结介绍的字符串对象,字符串对象是唯一被其他对象嵌套的对象。


      linkedlist.png

      ziplist.png
8.3.1 编码的转换
  • 所有元素长度小于64字节,并且元素数量小于512时用ziplist,否则使用linkedlist
8.3.2 列表指令的实现
list指令实现.png

8.4 哈希对象

  • 哈希对象编码可以是ziplist或hashtable。
  • ziplist:使用ziplist时因为内存连续,每次添加节点时都会按key、value的顺序添加到列表尾部。


    ziplist.png
  • hashtable:使用字典实现,字典结构的Key和value都各自是一个字符串对象。


    hashtable.png
8.4.1 编码转换
  • 所有键和值都小于64字节,并且节点数量小于512个时使用ziplist,否则使用hashtable。
8.4.1 哈希命令的实现
hash命令.png

8.5 对象集合

  • 对象集合编码可以是intset或hashtable。
  • intset:


    intset.png
  • hashtable:对象集合的hashtable所有的value都是Null


    hashtable.png
8.5.1 编码的转换
  • 集合都是整数并且元素小于512时,是intset否则用hashtable
8.5.2 对象集合命令的实现
API1.png

API2.png

8.6 有序集合对象

  • 有序集合对象编码可以是ziplist或skiplist。
  • ziplist:


    ziplist.png
  • skiplist:当zset使用skiplist时,会同时包含一个字典和一个跳跃表。字典的key是数据,value是分值,跳跃表的object是数据,score是分值。两种数据结构通过指针共享数据和分值。
  • 为什么同时使用跳跃表和字典:还是性能问题。
    • 字典可以最快的找到对应数据,但zset还有一些范围操作命令,所以引入了跳跃表,只有跳跃表数据查找效率又会低,所以最后的解决方案就是同时引入跳跃表和字典。


      skiplist.png
8.6.1 编码的转换
  • 数量小于128并且元素数量小于64字节用ziplist否则用skiplist。
8.6.2 有序集合命令的实现
有序集合API.png

8.7 类型检查与命令多态

  • Redis操作类型分为两种:
    • 可以对任何类型的键执行:DEL、EXPIRE、RENAME、TYPE等等
    • 对特定类型使用:set、get是字符串,HSET、HGET是哈希等等
8.7.1 类型检查的实现
  • 在执行特定类型的命令前,Redis会先检查redisObject结构的type属性是否符合要求,否则报错。
8.7.2 多态命令的实现
  • 命令根据不同的底层数据结构实现,执行不同的操作。

8.8 内存回收

  • Redis的回收使用引用计数的方式实现。
    • 创建时计数+1
    • 对象被新程序使用时+1
    • 对象不被程序使用时-1
    • 对象计数为0时释放内存

8.9 对象共享

  • 对象共享和内存回收相关,共享的对象有新引用时计数+1。
  • Redis初始化服务器时会创建0-9999的字符串共享变量供程序使用。
  • Redis只对包含整数的字符串对象进行共享。

8.10 对象的空转时长

  • redisObject属性除了上面提到的三个还有一个属性,lru记录了对象最后一次被访问的时间,OBJECT IDLETIME属性可以打印出来。
  • 服务器可以开启maxmemory选项,空转时间较长的键会被释放回收。

你可能感兴趣的:(Redis第8章 对象)