Redis读书笔记(二)Redis的对象

Redis的对象

Redis的主要数据结构:简单动态字符串、双端列表、字典、整数结合、跳跃表、压缩列表等。

Redis没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构构建了一个对象系统,这个系统包括字符串对象,列表对象,哈希对象,集合对象有序集合对象这五种类型的对象,每种对象都用到了至少一种前面所介绍的数据结构。

1、对象的结构

Redis使用对象来表示数据库的键和值。每个对象都是一个redisObject结构,是一个按照位段存储的结构,节约内存:

typedef struct redisObject{
    //类型
    unsigned type :4;
    //编码
    unsigned encoding:4;
    //指向底层实现数据结构的指针
    void *ptr;
    ...
} robj;
  • type是类型常量,记录对象的类型:

    图片说明

  • encoding记录对象使用的编码,即对象底层实现使用的具体数据结构:

    image-20201208222607470

    使用object encoding命令可以查看键的值对象的编码,每个对象都至少使用两种不同编码常量:

    图片说明

  • 对象的ptr指针指向对象的底层实现数据结构,而这些数据结构由对象的encoding属性决定。

2、字符串对象

字符串对象可以是int,rawembstr

  • 如果字符串对象保存的是整数值,且这个整数值可用long类型表示,底层就会以REDIS_ENCODING_INT编码来实现。
  • 如果字符串对象保存的是字符串值,且这个字符串值的长度<=39字节,底层编码就是REDIS_ENCODING_EMBSTR,使用embstr编码的方式保存字符串。
  • 如果字符串对象保存的是字符串值,且这个字符串值的长度>39字节,字符串值将使用一个SDS保存,底层编码为REDIS_ENCODING_RAW

embstr编码

embstr编码是专门用于保存短字符串的一种优化编码方式,与raw的效果相同,都使用redisObject和sdshdr结构来表示字符串对象,但raw编码会调用两次内存分配函数分别创建redisObject和sdshdr结构。而embstr编码则通过调用一次内存分配函数来分配一块连续空间,空间依次包括redisObject和sdshdr俩结构。

image-20201208224344424

使用embstr编码保存短字符串的优点

  • 内存分配次数从raw编码的2次降为1次
  • 释放embstr编码的字符串对象只需调用1次内存释放函数,而释放raw编码的字符串对象需要两次。
  • embstr编码的字符串对象的所有数据都保存在一块连续的内存中,能更好地利用缓存带来的优势.

注:浮点数的存储,在Redis底层也会以字符串的形式保存。在有需要时,程序会将字符串对象中的字符串值转为浮点数值执行运算操作,然后再将结果转为字符串值保存。

编码的转换

int->raw:执行一些命令,使得int编码的字符串对象保存的不再是整数值,而是字符串值时。比如整数追加字符串。

embstr->raw:Redis没有为embstr编写修改程序,所以是只读的,当embstr编码的字符串修改后,就变成raw编码的字符串对象。

3、列表对象

列表对象的编码是ziplist或linkedlist。

当列表可以同时满足以下两个条件时,列表对象使用ziplist编码,否则使用linkedlist编码。:

  • 列表对象保存的所有字符串元素的长度都<64字节
  • 列表对象保存的元素数量<512个

使用ziplist编码,执行RPUSH elements 1 "three" 5,后的数据结构:

image-20201208225329666

使用linkedlist编码,执行RPUSH elements 1 "three" 5,后的数据结构:

image-20201208225405424

注:SDS对象都以StringObject代替

4、哈希对象

哈希对象的编码可以是ziplist或hashtable。

  • ziplist编码的哈希对象使用压缩列表作为底层实现:每当有新的键值对插入哈希对象时,Redis会先将保存键的压缩列表节点推入表尾,再将保存值的压缩列表节点推入表尾。

    图片说明

  image-20201208230955232

  • hashtable编码的哈希对象使用字典作为底层实现:字典的每个键都是一个字符串对象,保存键;字典的每个值都是字符串对象,保存值。

    image-20201208231016978

当哈希对象可以同时满足下两个条件时,使用ziplist编码,否则使用hashtable编码:

  • 哈希对象保存的所有键值对的值和键都<64字节
  • 哈希对象保存的键值对数量<512个

5、集合对象

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

intset编码集合对象:

image-20201208231451771

如果以hashtable编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,每个字符串对象包含了一个集合对象,而字典的值则全都是null

图片说明

当集合对象同时满足以下两个条件时,使用intset编码,否则使用hashtable编码:

  • 集合对象保存的所有元素都是整数值
  • 集合对象保存的元素数量<=512个

6、有序集合对象

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

ziplist编码的有序集合对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,而第二个元素则保存元素的分值。

image-20201208231944543

skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset结构同时包含一个字典和一个跳跃表:

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

zset结构中的dict跳跃表按分值从小到大保存了所有集合元素,每个跳跃表节点都保存了一个集合元素:跳跃表节点的object属性保存了元素的成员,score属性则保存了元素的分值。

zset结构中的dict字典为有序集合创建了一个从成员到分值的映射,字典中的每个键值对都保存了一个集合元素:字典的键保存元素,字典值保存分值。通过字典,可以用O(1)复杂度查找给定成员的分值。有序集合元素都是字符串对象,分值都是double类型浮点数。zset的跳跃表和字典通过指针来共享相同元素的成员和分值,不会浪费额外内存。

当有序集合对象同时满足以下两条件时,对象使用ziplist编码,否则使用skiplist编码:

  • 有序集合保存的元素数量<128个
  • 有序集合保存的所有元素成员的长度都<64字节

为什么有序集合需要同时使用跳跃表和字典来实现?

==

7、类型检查与命令多态

Redis中用于操作键的命令可分为两种类型。

  • 一种是可对任何类型的键执行,如del,expire,rename等。

  • 另一种命令只能对特定类型的键执行,如set,get,hdel,hset,rpush等。如果对特定类型使用其他类型的命令,那么就会报错。

类型检查的实现

为了确保只有指定类型的键可以执行某些特定命令,在执行前,Redis会先通过RedisObject的type属性检查输入键的类型是否正确,再决定是否执行给定的命令。

image-20201208233110360

多态命令的实现

Redis除了根据值对象判断键是否能够执行制定命令外,还会根据值对象的编码方式,选择正确的命令实现代码来执行。比如基于编码的多态,列表对象的编码可能是ziplist或linkedlist,所以需要多态命令执行对应编码的API。

基于类型的多态——一个命令可以同时处理多种不同类型的键

基于编码的多态——一个命令可以同时处理多种不同编码

image-20201208233301443

8、内存回收

由于C语言没有内存回收机制,所以Redis在对象系统中构建了一个引用计数器技术实现内存回收机制。每个对象的引用计数器信息由redisObject的refcount来记录。当对象的引用计数值为0时,所占用的内存会被释放

typedef struct redisObject{
    //...
    // 引用计数
    int refcount;
    //...
} robj;

9、对象共享

引用计数器还有共享对象的作用。如果两个不同键的值都一样(必须是整数值的字符串对象),则将数据库键的值指针指向一个现有的值对象,然后将被共享对象的引用计数加一。如果不是整数值的对象,则需要耗费大量的时间验证共享对象和目标对象是否相同,复杂度较高,消耗CPU时间,所以Redis不会共享包含字符串的对象

Redis在初始化服务时,会创建很多字符串对象,包含0~9999的整数(和Integer的常量池有点像),当需要时,就会使用这些共享对象,而不是新创建对象。

image-20201208234100490

10、对象的空转时长

redisObject还包含了lru属性,记录对象最后一个被命令程序访问的时间。object idletime命令可打印键的空转时长,就是当前时间减去lru时间计算得到的。

typedef struct redisObject{
    //...
    // 引用计数
    unsigned lru:22;
    //...
} robj;
全部评论

相关推荐

点赞 评论 收藏
分享
不要停下啊:大二打开牛客,你有机会开卷了,卷起来,去找课程学习,在牛客上看看大家面试笔试都需要会什么,岗位有什么需求就去学什么,努力的人就一定会有收获,这句话从来都经得起考验,像我现在大三了啥也不会,被迫强行考研,炼狱难度开局,啥也不会,找工作没希望了,考研有丝丝机会
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务