redis五大数据类型的底层实现

         一、redis数据类型简介

二、字符串对象(string)

三、列表对象(list)

四、哈希对象(hash)

五、集合对象(set)

六、有序集合对象(zset)

七、总结


一、redis数据类型简介

redis的五大数据类型,也称五大数据对象。redis有六大数据结构,分别为简单动态字符串SDS、列表、字典、跳跃表、整数集合、压缩列表。redis并没有直接使用这些数据结构来实现键值对数据库,而是使用这些数据结构构建了一个对象系统redisObject。这个对象系统包含了五大数据对象:字符串对象(string)、列表对象(list)、哈希对象(hash)、集合(set)对象和有序集合对象(zset)。这五大对象的底层数据编码,可以用命令OBJECT ENCODING来进行查看。

redisObject的数据结构,如下所示:

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

redis是以键值对存储数据的,所以对象又分为键对象值对象,即存储一个key-value键值对创建两个对象:键对象值对象

键对象总是一个字符串对象值对象可以是五大对象中的任意一种

  • type属性存储的是对象的类型,也就是我们说的string、list、hash、set、zset中的一种,可以使用命令 TYPE key 来查看。
  • encoding属性记录了对象所使用的编码,即这个对象底层使用哪种数据结构实现。

redis编码常量和编码对应的底层数据结构,如下表所示:

表中列出了底层编码常量及对应的OBJECT ENCODING 命令的输出,前三项都是字符串结构。

我们在存入key-value键值对时,并不会指定对象的encoding。Redis会根据不统的使用场景来为一个对象设置不同的编码,可以达到节约内存、加快访问速度等目的。

二、字符串对象(string)

字符串对象的底层数据结构,其实现为简单动态字符串(SDS)直接存储之一其编码方式可以是int、raw或者embstr,区别在于内存结构的不同

(1)int编码

如果一个字符串对象保存的是一个整数值,并且这个整数值可以用long类型来表示,那么这个整数值就会直接保存在redisObject的ptr属性里,并将编码设置为int,如下图所示:

 

(2)raw编码

 如果一个字符串对象保存的是大于44字节的字符串值(redis3.2版本以后为44字节,3.2版本之前为39字节),则使用简单动态字符串(SDS)结构,并将编码设置为raw。此时,内存结构与SDS结构一致,内存分配次数为两次,创建redisObject对象和sdshdr结构,如下图所示:

(3)embstr编码

 如果一个字符串对象保存的是小于等于44字节的字符串值(redis3.2版本以后为44字节,3.2版本之前为39字节),使用的也是简单的动态字符串(SDS结构),但是内存结构做了优化,用于保存较短的字符串。内存分配只需要一次,分配一块连续的空间即可,如下图所示:

 

 字符串对象总结:

  • 在Redis中,存储long、double类型的浮点数是先转换为字符串,再进行存储的。
  • raw与embstr编码效果是相同的,不同在于内存分配与释放:raw两次,embstr一次。
  • embstr内存块连续,能更好地利用缓存带来的优势。
  • 对于int编码和embstr编码,如果做追加字符串等操作,满足条件下会被转换为raw编码。embstr编码的对象是只读的,一旦修改,会先转码到raw。

三、列表对象(list)

列表对象的编码,是压缩列表ziplist双端链表linkedlist之一。

(1) ziplist编码

ziplist编码的列表对象,其底层实现是压缩列表。每个压缩列表节点保存了一个列表元素,如下图所示:

(2)linkedlist编码

linkedlist编码的列表对象,其底层实现采用双端链表。每个双端链表节点都保存了一个字符串对象,在每个字符串对象内保存了一个列表元素,如下图所示:

列表对象编码转换规则:

  • 列表对象使用ziplist编码,需要满足两个条件:一是所有字符串长度都小于64字节,二是元素数量小于512。若任意一个条件不满足,则使用linkedlist编码。
  • 两个条件的数值可以在Redis的配置文件中修改,分别为list-max-ziplist-value选项和list-max-ziplist-entries选项。
  • 图中StringObject就是字符串对象,字符串对象是唯一个在五大对象中作为嵌套对象使用的。

四、哈希对象(hash)

哈希对象的编码,压缩列表ziplist和哈希表hashtable之一。

(1)ziplist编码

ziplist编码的哈希对象,其底层实现是压缩列表ziplist。在ziplist编码的哈希对象中,key-value键值对是以紧密相连的方式放入压缩链表的,先把key放入表尾,再把value放入表尾键值对总是向表尾添加,如下图所示:

(2)hashtable编码

hashtable编码的哈希对象,其底层实现是字典哈希对象中的每个key-value对,都使用一个字典键值对来保存。

在字典键值对中,字典的键和值都是字符串对象,字典的键保存key-value的key,字典的值保存key-value的value,如下图所示:

哈希对象编码转换规则:

  • 哈希对象使用ziplist编码,需要满足两个条件:一是所有键值对的键和值的字符串长度都小于64字节;二是键值对数量小于512个。若任意一个条件不满足,则使用hashtable编码。
  • 以上两个条件的数值,可以在redis配置文件中修改,分别为hash-max-ziplist-value选项和hash-max-ziplist-entries选项。

五、集合对象(set)

集合对象的编码,是整数集合intset哈希表hashtable之一。

(1)intset编码

intset编码的集合对象,其底层实现是整数集合。所有元素都保存在整数集合中,如下图所示:

(2)hashtable编码

hashtable编码的集合对象,其底层实现是字典。字典的每个键都是一个字符串对象(该字符串对象保存一个集合元素),字典的值都是NULL。可以参考java中的hashset结构。

集合对象编码转换规则:

  • 集合对象使用intset编码,需要满足两个条件:一是所有元素都是整数值;二是元素个数小于等于512个。若任意一个条件不满足,则将使用hashtable编码。
  • 第二个条件可以在Redis配置文件中修改,为et-max-intset-entries选项。

六、有序集合对象(zset)

有序集合的编码,是压缩列表ziplist和跳跃表skiplist之一。

(1)ziplist编码 

ziplist编码的有序集合对象,其底层实现是压缩列表。其结构与哈希对象类似,不同的是:两个紧密相连的压缩列表节点,第一个保存元素的成员,第二个保存元素的分值,而且分值小的靠近表头,大的靠近表尾。

(2)skiplist编码

skiplist编码的有序集合对象,其底层实现是一个zset结构。一个zset结构同时包含一个跳跃表zsksiplist和一个字典dict。

每个跳跃表节点都保存一个集合元素,并按分值从小到大排列;节点的object属性保存了元素的成员,score属性保存分值。

字典的每个键值对保存一个集合元素字典的键保存元素的成员,字典的值保存分值。

为何skiplist编码要同时使用跳跃表和字典实现?

  • 跳跃表优点是有序,但是查询分值复杂度为O(logn);字典查询分值复杂度为O(1) ,但是无序。所以,结合这两个数据结构的优点进行实现。
  • 虽然采用两个数据结构,但是集合的元素成员和分值是共享的,通过指针指向同一地址,不会浪费内存。

有序集合编码转换:

  • 有序集合对象使用ziplist编码,需要满足两个条件:一是所有元素长度小于64字节;二是元素个数小于128个。若任意一个条件不满足,则使用skiplist编码。
  • 以上两个条件的数值可以在redis配置文件中修改,分别为zset-max-ziplist-entries选项和zset-max-ziplist-value选项。

七、总结

在redis的五大数据对象中,string对象是唯一可以被其他四种数据对象作为内嵌对象的。

列表(list)、哈希(hash)、有序集合(zset)的底层实现都用到了压缩列表结构,并且使用压缩列表的条件都是“在元素个数比较少、字节长度较短的情况下”。

数据对象使用压缩列表的优点:

(1)节约内存,减少内存开销。redis是内存型数据库,在一定情况下减少内存开销是非常有必要的。

(2)减少内存碎片,压缩列表的内存块是连续的,并且分配内存的次数一次即可。

(3)压缩列表的新增、删除、查找操作的平均时间复杂度是O(N)。N在一定的范围内,这个时间几乎是可以忽略的,并且N的上限值是可以配置的。

(4)四种数据对象都有两种编码结构,增加灵活性。

猜你喜欢

转载自blog.csdn.net/chinawangfei/article/details/104449593