• Redis(6)----对象与数据结构


    1,前言

    Redis中用自定义的数据结构以达到更好的效果。诸如:简单动态字符串、双端链表、字典、压缩列表、整数集合等等。

    Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统。这个系统包括字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象。使用对象的好处之一就是可以根据使用场景的不同,选择相应的数据结构以优化使用效率。

    Redis中相应的数据结构:
    简单动态字符串
    链表
    字典
    整数集合
    压缩列表

    2,对象

    Redis中的每个对象都由一个redisObject结构表示,该结构中保存和数据有关的三个属性是:type属性、encoding属性、ptr属性

    typedef struct redisObject{
    
    	//类型
    	unsigned type:4;
    	
    	//编码
    	unsigned encoding:4;
    	
    	//指向底层实现数据结构的指针
    	void *ptr;
    	
    	//......
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2.1,对象的类型与编码

    Redis使用对象来表示数据库中的键值对,每当创建一个键值对时,Redis会创建两个对象,一个对象用作键值对的键(键对象),另一个用作键值对的值(值对象)

    # 例如下列命令创建了两个对象,一个字符串对象用作键,另一个字符串对象用作值
    redis> SET msg "hello wrold"
    OK
    
    • 1
    • 2
    • 3

    2.1.1,类型

    对象的type属性记录了对象的类型,这个属性的值可以是下表中的任意一个:

    类型常量对象名称
    REDIS_STRING字符串对象
    REDIS_LIST列表对象
    REDIS_HASH哈希对象
    REDIS_SET集合对象
    REDIS_ZSET有序集合对象

    对于Redis来说,键总是一个字符串对象,而值可以是几种对象之一,因此:

    • 当我们称呼一个数据库键为”字符串键“时,我们指的是:这个数据库键对应的值为字符串对象
    • 当我们称呼一个数据库键为”列表键“时,我们指的是:这个数据库键对应的值为列表对象

    TYPE命令

    我们可以使用TYPE命令查看类型,当对一个数据库键执行TYPE命令时,命令返回的结果是数据库键对应的值对象的类型,而不是键对象的类型。

    # 键为字符串对象 值为字符串对象
    redis> SET msg "hello world"
    OK
        
    redis> TYPE msg
    string
    
    # 键为字符串对象 值为字符串对象
    redis> RPUSH numbers 1 3 5
    (integer) 6
        
    redis> TYPE numbers
    list
    
    # 键为字符串对象 值为哈希对象
    redis> HMSET profile name Tom age 25 career Programmer
    OK
        
    redis> TYPE profile
    hash
    
    # 键为字符串对象 值为集合对象
    redis> SADD fruit apple banana cherry
    (integer)3
        
    redis> TYPE fruit
    set
    
    # 键为字符串对象 值为有序集合对象
    redis> ZADD price 8.5 apple 5.0 banana 6.0 cherry
    (integer)3
        
    redis> TYPE price
    zset
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    对象对象type属性的值TYPE命令的输出
    字符串对象REDIS_STRING”string“
    列表对象REDIS_LIST”list“
    哈希对象REDS_HASH”hash“
    集合对象REDIS_SET”set“
    有序集合对象REDIS_ZSET”zset“

    2.1.2,编码和底层实现

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

    encoding属性记录了对象所使用的编码,也就是说这个对象使用了什么数据结构作为对象的底层实现:

    编码常量编码所对应的底层数据结构
    REDIS_ENCODING_INTlong类型的整数
    REDIS_ENCODING_EMBSTRembstr编码的简单动态字符串
    REDIS_ENCODING_RAW简单动态字符串
    REDIS_ENCODING_HT字典
    REDIS_ENCODING_LINKEDLIST双端链表
    REDIS_ENCODING_ZIPLIST压缩列表
    REDIS_ENCODING_INTSET整数集合
    REDIS_ENCODING_SKIPLIST跳跃表和字典

    而每种类型都至少使用了两种不同的编码,如:

    类型编码对象
    REDIS_STRINGREDIS_ENCODING_INT使用整数值实现的字符串对象
    REDIS_STRINGREDIS_ENCODING_EMBSTR使用embstr编码的简单动态字符串实现的字符串对象
    REDIS_STRINGREDIS_ENCODING_RAW使用简单动态字符串实现的字符串对象
    REDIS_LISTREDIS_ENCODING_ZIPLIST使用压缩列表实现的列表对象
    REDIS_LISTREDIS_ENCODING_LINKEDLIST使用双端链表实现的列表对象
    REDIS_HASHREDIS_ENCODING__ZIPLIST使用压缩列表实现的哈希对象
    REDIS_HASHREDIS_ENCODING_HT使用字典实现的哈希对象
    REDIS_SETREDIS_ENCODING_INTSET使用整数集合实现的集合对象
    REDIS_SETREDIS_ENCODING_HT使用字典实现的集合对象
    REDIS_ZSETREDIS_ENCODING_ZIPLIST使用压缩列表实现的有序集合对象
    REDIS_ZSETREDIS_ENCODING_SKIPLIST使用跳跃表和字典实现的有序集合对象

    使用OBJECT ENCODING命令可以查看一个数据库键的值对象的编码

    redis> SET msg "hello world"
    OK
    
    redis> OBJECT ENCODING msg
    "embstr"
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Redis根据不同的使用场景为一个对象设置不同的编码,从而优化对象在当前场景下的使用效率。

    例如在链表对象包含的元素较少的时候,Redis可以使用压缩列表来作为列表对象的底层实现。

    • 压缩列表比双端链表更节约内存,并且当元素数量少的时候,在内存中以连续块的方式保存的压缩列表比起双端链表能更快的被载入缓存中
    • 当元素越来越多时,Redis会使用功能更强、更适合保存大量元素的双端链表来保存数据

    2.2,字符串对象

    字符串对象的编码可以是intraw或者embstr

    int编码

    如果一个字符串保存的值是整数值,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面(将void*转换成long),并将字符串对象的编码设置为REDIS_ENCODING_INT

    例如:

    redis> SET number 10086
    OK
    
    redis> OBJECT ENCODING number
    "int"
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    对于一些浮点数(如3.14)的保存,程序会将这个浮点数转换为字符串值,然后再保存转换所得的字符串值

    raw编码

    如果字符串保存的是一个字符串值,并且这个值的长度大于32字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值,并将编码设置为REDIS_ENCODING_RAW

    例如:

    redis> SET story "Long,long ago there is lived a king......"
    OK
    
    redis> STRLEN story
    (integer) 37
    
    redis> OBJECT ENCODING story
    “raw”
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    embstr编码

    如果字符串对象保存的是一个字符串值,并且这个值的长度小于等于32字节,那么字符串对象会使用embstr编码的方式保存字符串值

    embstrraw编码都是需要创建redisObjectsdshdr两个结构,但embstr是专门用于保存短字符串的一种优化编码方式。embstr在内存重分配的时候将创建出来的两个结构分配到同一块连续的内存空间(所以embstr创建时只需要一次内存重分配,释放时也只需要一次)

    使用embstr的优点:

    • 创建时需要的内存分配次数从raw编码的两次降为一次,释放也是如此
    • 由于embstr的两个结构都是存放在一块连续的内存空间中,所以能更好的利用缓存带来的优势

    例如:

    redis> SET msg "hello"
    OK
    
    redis> OBJECT ENCODING msg
    "embstr"
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    编码的转换

    int编码的字符串对象和embstr编码的字符串对象在条件满足的情况下,会被转换为raw编码的字符串对象。例如:

    redis> SET number 10086
    OK
    
    redis> OBJECT ENCODING number
    "int"
    
    redis> APPEND number " is a good number"
    (integer) 23
    
    redis> GET number
    "10086 is a good number"
    
    redis> OBJECT ENCODING number
    "raw"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    值得一提的是,因为Redis并没有为embstr编码的字符串对象编写任何相应的修改程序(只有intraw编码的有),所以对embstr编码的字符串对象执行任何修改指令,程序会将对象编码转换为raw再进行修改。这也往往导致embstr编码的字符串对象在执行修改指令后,总会变成一个raw编码的字符串对象

    2.3,列表对象

    列表的编码可以是ziplist或者linkedlist编码

    当列表对象同时满足以下两个条件时,列表对象使用ziplist编码

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

    其他不满足条件的需要使用linkedlist编码(这两个条件的上限值是可以修改的)

    创建一个列表对象numbers

    redis> RPUSH numbers 1 "three" 5
    (integer)3
    
    • 1
    • 2

    ziplist编码

    如果numbers键使用的是ziplist编码,那么值对象会是这样的:

    在这里插入图片描述

    linkedlist编码

    如果使用linkedlist编码的话,那么值对象会是这样的:

    在这里插入图片描述

    值得注意的是:这里的StringObject是上面提及到的字符串对象,这里之所以这样画,是为了简化表示。字符串对象是Redis五种类型的对象之中唯一一种会被其他类型对象嵌套的对象

    以例子中的three为例,其完整的表示应该为:

    在这里插入图片描述

    编码转换

    只要不满足使用ziplist编码的任意一个条件,对象的编码转换操作就会被执行。原本保存的元素会被转移并保存到双端链表中。

    redis> RPUSH blah "hello" "world" "again"
    (integer)3
    
    redis> OBJECT ENCODING blah
    "ziplist"
    
    #将一个大于64字节长的元素输入到列表对象中
    redis> RPUSH blah "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww"
    (integer)4
    
    redis>OBJECT ENCODING blah
    "linkedlist"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.4,哈希对象

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

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

    • 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节
    • 哈希对象保存的键值对小于512个

    假设有一个哈希对象使用ziplist作为底层实现,当往这个哈希对象压入新的键值对时,程序会将保存了键的压缩列表节点推入到压缩列表中,随后将保存了值的压缩列表节点推入到压缩列表。所以保存了同一键值对的两个节点总是紧挨着,并且按键-值的顺序进行存放

    ziplist编码

    创建一个列表对象profile

    redis> HSET profile name "Tom"
    (integer) 1
    
    redis> HSET profile age 25
    (integer) 1
    
    redis> HSET profile career "Programmer"
    (integer) 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    如果该列表对象此时使用的是ziplist编码,那么哈希对象的示意图如下:

    在这里插入图片描述

    在这里插入图片描述

    hashtable编码

    hashtable编码的哈希对象使用字典作为底层实现,哈希对象中的每个键值对都是用一个字典键值对来保存(字典的键与值均是字符串对象)

    如果上面的profile其编码是hashtable,那么示意图应为:
    在这里插入图片描述

    编码转换

    当哈希对象不满足使用ziplist编码的条件时,对象的编码转换操作就会被执行,原本保存在压缩列表的所有键值对都会被转移并保存到字典中,对象的编码也会变更。

    2.5,集合对象

    集合对象的编码可以是intset或者hashatble

    当同时满足以下条件时,使用intset编码

    • 集合对象保存的元素都是整数值
    • 集合对象保存的元素数量不超过512个

    intset编码

    intset编码的集合对象使用整数集合作为底层实现,集合对象包含的所有元素都被包含在整数集合中。

    redis> SADD numbers 1 3 5
    (integer) 3
    
    • 1
    • 2

    在这里插入图片描述

    hashtable编码

    hashtable编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,每个字符串对象包含了一个集合元素;字典的值全部被设置为NULL

    例如:

    redis> SADD Dfruits "appl" "banana" "cherry"
    (integer) 3
    
    • 1
    • 2

    在这里插入图片描述

    编码转换

    当不满足使用intset的使用条件时,会触发编码转换。原本保存在整数集合中的所有元素都会被转移并保存到字典中,并更改对象的编码为hashtable

    2.6,有序集合对象

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

    当同时满足以下两个条件时,对象使用ziplist编码:

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

    ziplist编码

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

    压缩列表内的集合元素按分值从小到大进行排序,分值较小的存放在靠近表头的方向,分值较大的存放在靠近表尾的方向。

    例如:

    redis> ZADD price 8.5 apple 5.0 banana 6.0 cherry
    (integer) 3
    
    • 1
    • 2

    假设price使用压缩列表做底层实现,那么示意图应该是:

    在这里插入图片描述

    在这里插入图片描述

    skiplist编码

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

    typedef struct zset{
        zskiplist *zsl;
        
        dict *dict;
    }zset;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    假设price使用的是skiplist编码,那么示意图应该为:

    在这里插入图片描述

    编码转换

    当有序集合对象不满足ziplist编码的使用条件时,会触发编码转换;原本保存在压缩列表中的所有元素都会被转移并保存到zset结构中,并更改对象的编码为skiplist

  • 相关阅读:
    最新 PMP 考试真题概要及答案分析(中文版)(1)
    Python正则表达式
    Java Web 实战 20 - HTTP PK HTTPS ? HTTPS 大获全胜 ?
    NLC: Search Correlated Window Pairs on Long Time Series(VLDB2022)
    【HCIA】交换基础
    力扣刷题记录53.1-----700. 二叉搜索树中的搜索
    695. 岛屿的最大面积
    领域驱动设计——领域的整体设计
    2.Spring的优缺点是什么?
    前端图片文件压缩方案
  • 原文地址:https://blog.csdn.net/weixin_41043607/article/details/125489149