Redis五种对象详解

本文详细介绍了Redis的五种对象:字符串、列表、哈希、集合和有序集合,包括它们的对象结构、类型、命令、内存回收机制、共享对象、空转时长以及类型检查命令的多态性。特别地,文章深入探讨了每种对象的存储结构,如int、embstr、raw编码的字符串对象,ziplist、linkedlist编码的列表对象,ziplist、hashtable编码的哈希对象,intset、hashtable编码的集合对象,以及ziplist、skiplist编码的有序集合对象,同时讨论了不同场景下对象编码的转换策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、对象

  • Redis中有六种主要数据结构:简单动态字符串双端链表字典跳跃表整数集合压缩列表
  • Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,每种对象都用到了至少一种数据结构;
  • Redis使用encoding属性来设定对象所使用的编码,而不是为特定类型的对象关联一种固定的编码,这样极大地提高了Redis的灵活性和效率,因为Redis可以根据不同的使用场景来为一个对象设置不同的编码,从而优化对象在某一场景下的效率;
  • 五种类型的对象:字符串对象(REDIS_STRING)列表对象(REDIS_LIST)哈希对象(REDIS_HASH)集合对象(REDIS_SET)有序集合对象(REDIS_ZSET);

1.1 对象结构

typedef struct redisObject{
    // 对象类型
    unsigned type:4;
    
    // 对象编码
    unsigned encoding:4;
    
    // 指向底层实现数据结构的指针
    void *ptr;
    
    // 引用计数
    int refcount;
    
    // 对象空转时长
    unsigned lru:32;
    
    // ...
    
}robj;
  • type:记录了对象的类型,取值有五种:
    • REDIS_STRING:字符串对象;
    • REDIS_LIST:列表对象;
    • REDIS_HASH:哈希对象;
    • REDIS_SET:集合对象;
    • REDIS_ZSET:有序集合对象;
  • encoding:记录了对象所使用的编码,表示对象使用了什么数据结构作为对象的底层实现;
  • ptr:指向底层数据结构的指针;
  • refcount:对象引用计数,用于内存回收;
  • lru:对象空转时长,记录对象最后一次被访问的时间;

1.2 对象类型

  • Redis的键总是一个字符串对象;
  • Redis的值可以是字符串对象、列表对象、哈希对象、集合对象、有序集合对象;
  • 对Redis的键执行type命令,查看的不是键对象的类型,而是对应的值对象的类型;
127.0.0.1:6379> set key1 hello
OK
127.0.0.1:6379> type key1
string
127.0.0.1:6379> lpush key2 1 2 3 4
(integer) 4
127.0.0.1:6379> type key2
list
127.0.0.1:6379> sadd key3 1 2 3
(integer) 3
127.0.0.1:6379> type key3
set
127.0.0.1:6379> hset key4 name tom age 24
(integer) 2
127.0.0.1:6379> type key4
hash
127.0.0.1:6379> zadd key5 24 tom 25 jack
(integer) 2
127.0.0.1:6379> type key5
zset

1.3 对象命令

  • type命令查看的是对象类型,读取对象的type属性,返回值是:stringlistsethashzset;
  • object encoding命令查看对象的编码,即对象的底层实现,读取对象的encoding属性,
  • object refcount命令查看对象的引用计数,读取对象的refcount属性;
  • object idletime命令查看对象的空转时间,是当前时间减去对象的lru时间计算得来的;该命令不会修改对象的lru属性;

1.4 内存回收

C语言不具备自动内存回收功能,所以Redis在自己的对象系统中构建了一个基于引用计数技术实现内存回收机制;

  • 创建一个对象时,引用计数值会被初始化为1;
  • 当一个对象被新程序使用时,引用计数会被增1;
  • 当一个对象不再被一个程序使用时,引用计数值会被减1;
  • 当对象的引用计数值为0时,对象所占用的内存会被释放;

1.5 对象共享

  • Redis使用共享对象机制节约内存使用;
  • 对象的引用计数可以表示对象的共享量;
  • Redis在初始化服务器时,会创建一万个字符串对象,包含了从0到9999的所有整数值,当服务器用到这些值时,直接使用共享对象;
  • 为什么不共享包含字符串的对象?因为只有在共享对象和目标对象完全相同的情况下,程序才会将共享对象作为键的值对象,而一个共享对象保存的值越复杂,验证共享对象和目标对象是否相同所需的复杂度越高,消耗的CPU时间也会越长;

1.6 对象空转时长

  • 对象的lru属性记录对象最后一次被程序访问的时间;
  • 如果服务器打开了maxmemory选项,那么当服务器占用的内存达到最大内存时会进行内存回收,如果选择的回收算法是volatile-lruallkeys-lru,则空转时间较高的键会优先被服务器回收;

1.7 类型检查命令多态

类型检查

Redis会根据值对象的类型(type属性)来判断是否能够执行指定的命令。

Redis中用于操作键的命令基本上可以分为两种类型:

  • 一种可以对任何类型的键执行,如DEL命令EXPIRE命令TYPE命令OBJECT命令
  • 另一种只能对特定类型的键执行:比如SET只能对字符串键执行;

注意:当我们称呼一个数据库键为字符串键时,我们指的是这个数据库键对应的值为字符串对象

在执行一个特定类型的命令之前,Redis会先检查输入键的类型是否正确,然后再决定是否执行给定的命令,执行类型检查是通过redisObject结构的type属性来实现的;

命令多态

一个类型的对象会有多种不同的底层实现方式,Redis会根据值对象的编码方式(encoding属性),选择正确的命令实现代码来执行命令。

  • 基于类型的多态:一个命令可以同时处理多种不同类型的键(对象类型不同);
  • 基于编码的多态:一个命令可以同时处理用多种不同编码实现的对象(对象类型相同,实现的数据结构不同);

二、字符串对象

  • 字符串对象的type是:string;
  • 字符串对象的编码可以是:intembstrraw,对应的encoding属性是:REDIS_ENCODING_INTREDIS_ENCODING_EMBSTRREDIS_ENCODING_RAW
  • 字符串对象是Redis五种类型的对象中唯一一个会被其他四种对象嵌套的对象,即其他四种类型的对象在保存节点值时,有时会使用字符串对象;

2.1 存储结构

int编码

  • 如果一个字符串对象保存的是整数,并且这个整数可以用long类型表示,则使用int编码,ptr指针指向long类型整数;

embstr编码

  • embstr是专门保存短字符串的一种优化编码方式;
  • embstr将redisObject结构和sdshdr结构连续分配在一起,使用一块连续的内存空间分配这两块结构:

使用连续内存空间的好处:

  • 创建字符串对象时,所需的内存分配次数从raw编码的两次降低为1次;
  • 释放字符串对象时,从raw编码的调用两次内存释放函数降低为调用1次;

raw编码

  • raw编码时,使用一个简单动态字符串来保存字符串值,ptr指针指向一个sds结构;

2.2 编码选择与转换

  1. 编码选择:
  • 字符串对象保存的是整数时并且该整数可以用long类型表示时,使用int编码;
  • 字符串对象保存的值长度小于等于39字节时,选用短字符串优化编码embstr方式;
  • 字符串对象保存的值长度大于39字节时,选用raw编码方式;
  • 可以用long double类型表示的浮点数,在Redis中也是作为字符串值来保存的;
  • 长度太大没办法用long保存的整数,也使用字符串值保存;
127.0.0.1:6379> set k1 3
OK
127.0.0.1:6379> object encoding k1
"int"
127.0.0.1:6379> set k2 3.14
OK
127.0.0.1:6379> object encoding k2
"embstr"
  1. 编码转换
  • int编码的值:执行某些操作,使保存的不再是整数值时(如append操作),字符串编码会从int变为raw;
  • embstr编码的字符串对象没有相应的修改程序,是只读的,当对embstr编码的字符串执行任何修改命令时,程序会先将对象的编码从embstr转换为raw,然后再执行修改命令;
127.0.0.1:6379> set k3 hello
OK
127.0.0.1:6379> object encoding k3
"embstr"
127.0.0.1:6379> append k3 thanks
(integer) 11
127.0.0.1:6379> object encoding k3
"raw"

注意:

  • 对于embstrraw编码的字符串对象,不能执行incrbydecrby等数字操作;

三、列表对象

  • 列表对象的typelist
  • 列表对象的编码是ziplist或者linkedlist,对应的encoding属性是REDIS_ENCODING_ZIPLISTREDIS_ENCODING_LINKEDLIST;对应的底层数据结构是压缩列表双端链表

3.1 存储结构

ziplist编码

  • 当列表中元素个数较少,或元素长度较短时,使用压缩列表作为底层存储结构;
  • 压缩列表的好处是节约内存;
  • 每个压缩列表节点entry保存一个列表元素;

linkedlist编码

  • linkedlist编码的列表对象在底层使用双端链表存储对象,存储的是字符串对象
  • 存储结构其中字符串对象是使用的简化的字符串对象表示法,完整的字符串对象参考上小节;

3.2 编码转换

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

  • 列表对象保存的元素数量小于512个(可以由配置文件的list-max-ziplist-entries设置);
  • 列表对象保存的所有字符串元素的长度都小于64B(可以由配置文件的list-max-ziplist-value设置);

ziplist编码所需的两个条件中任意一个不满足时,对象就会执行编码转换操作,原本保存在压缩列表里的所有元素都会被转移并保存到双端链表里,对象的编码从ziplist转换为linkedlist

四、哈希对象

  • 哈希对象的typehash
  • 哈希对象的编码可以是ziplisthashtable,对应的encoding属性是REDIS_ENCODING_ZIPLISTREDIS_ENCODING_HT,对应的底层数据结构是压缩列表字典

4.1 存储结构

ziplist编码

  • 保存了同一键值对的两个节点总是紧挨在一起,保存键的节点在前,保存值的节点在后;
  • 先添加到哈希对象中的键值对会被放在压缩列表的表头方向,而后添加的键值对会在现有键值对后依次添加;
    ziplist编码的哈希对象

hashtable编码

  • hash编码的底层结构使用字典
  • 字典的每一个键都是一个字符串对象,对象中保存了键值对的键;
  • 字典的每一个值都是一个字符串对象,对象中保存了键值对的值;
    hashtable编码的哈希对象

4.2 编码转换

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

  • 哈希对象保存的键值对数量小于512个(可以通过配置文件的hash-max-ziplist-entries设置);
  • 哈希对象保存的所有键值对的键和值的字符串长度都小于64B(可以通过配置文件的hash-max-ziplist-value设置);

ziplist编码所需的两个条件的任意一个不能被满足时,对象会进行编码转换,编码从ziplist转换为hashtable

五、集合对象Set

  • 集合对象的typeset;
  • 集合对象的编码可以是intset或者hashtable,对应的encoding属性值是REDIS_ENCODING_INTSETREDIS_ENCODING_HT,对应的底层数据结构是整数集合字典

5.1 存储结构

intset编码

  • intset编码的集合对象,底层使用整数集合实现;
  • 整数集合的底层实现是数组,这个数组以有序不重复的方式保存集合元素;

hashtable编码

  • hashtable编码的集合对象,底层使用字典实现;
  • 字典的每个键都是一个字符串对象,对象保存集合中的一个元素;
  • 字典的每个值都是NULL;
  • hashtable编码的简略实现如下图所示,这里省略了字典的内部实现:

5.2 编码转换

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

  • 集合对象保存的所有元素都是整数值
  • 集合对象保存的元素数量不超过512个(可以在配置文件的set-max-intset-entries设置)

当使用intset编码所需的两个条件的任意一个不能被满足时,就会执行对象的编码转换操作,原本保存在整数集合里的所有元素都会被转移并保存到字典里面,并且对象的编码也会从intset变为hashtable;

六、有序集合对象ZSet

  • 有序集合对象的typezset;
  • 有序集合的编码可以是ziplist或者skiplist,对应的encoding属性是REDIS_ENCODING_ZIPLISTREDIS_ENCODING_SKIPLIST,对应的底层数据结构是压缩列表跳跃表与字典

6.1 存储结构

ziplist编码

  • ziplist编码的有序集合,底层使用压缩列表存储;
  • 每个集合元素使用两个紧挨着的压缩列表节点保存,第一个节点保存元素的成员,第二个节点保存元素的分值
  • 压缩列表内的集合元素按分值从小到大排序

skiplist编码

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

typedef struct zset{
    // 跳跃表
    zskiplist *zsl;
    
    // 字典
    dict * dict;
}zset;
  • 跳跃表节点的object属性保存了元素的成员,跳跃表节点的score属性保存了元素的分值
  • dict字典为有序集合创建了一个从成员到分值的映射,字典的键保存了元素的成员,字段的值保存了元素的分值;
  • 使用跳跃表可以快速的执行范围型操作,使用字典可以快速的查找成员的分值;

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

  • 字典结构,查找成员分值的时间复杂度是O(1),但是字典是以无序的方式保存集合元素的,在进行范围型操作(比如zrang,zrange)时,需要对字典保存的所有元素进行排序,时间复杂度至少O(NlogN);
  • 跳跃表结构,存储的数据是有序的,执行范围型操作的速度会很快,但是根据成员查找分值时,不如字典的速度快;
  • 所以Redis选择了同时使用字典跳跃表两种结构来实现有序集合,在不同类型的操作时,可以充分利用两种数据结构的优点;
  • 从理论上讲,有序集合可以单独使用字典或者跳跃表的其中一种数据结构来实现,但是无论单独使用字典还是跳跃表,在性能上对比同时使用字典和跳跃表都会有所降低;

6.2 编码转换

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

  • 有序集合保存的元素数量小于128(可以通过配置文件的zset-max-ziplist-entries设置);
  • 有序集合保存的所有元素成员的长度都小于64字节(可以通过配置文件的zset-max-ziplist-value设置);

当使用ziplist编码所需的两个条件中的任意一个不能被满足时,程序就会执行编码转换操作,将原本存储在压缩列表里的所有集合元素转移到zset里面,并将对象的编码从ziplist改为skiplist;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值