Redis对象系统

news2025/2/25 7:31:08

前言

        在Redis中有许多数据结构,比如:简单动态字符串(SDS),双端链表,字典,压缩列表,整数集合等。

        Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统。这个系统中包含字符串对象,列表对象,哈希对象,集合对象和有序集合对象这物种类型的对象。通过这五种不同类型的对象,Redis可以在执行命令前,根据对象的类型来判断一个对象是否可以执行给定的命令。使用对象的另一个好处时,我们可以针对不同的使用场景,为对象设置多种不同的数据结构实现,从而优化对象在不同场景下的使用效率。

        除此之外,Redis对象系统还实现了基于引用计数技术的内存回收机制,当程序不再使用某个对象的时候,这个对象所占用的内存会被自动释放;另外,Redis还通过引用计数技术实现了对象共享机制,这一机制可以在适当的条件下,通过让多个数据库键共享同一个对象来节约内存。

        最后,Redis对象带有访问时间记录信息,该信息可以用于计算数据库键的空转时长,在服务器启用了maxmemory功能的情况下,空转时间较长的那些键可能会优先被服务器删除。

一. 对象的类型与编码

        Redis使用对象来表示数据库的键和值,每次当我们在Redis的数据库中新创建一个键值对时,我们至少会创建两个对象,一个对象用作键值对的键(键对象),另一个对象用作键值对的值(值对象)。

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

typedef struct redisObject {
    //类型
    unsigned type:4;
    //编码
    unsigned encoding:4;
    unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
                            * LFU data (least significant 8 bits frequency
                            * and most significant 16 bits access time). */
    int refcount;
    //指向底层实现数据结构指针
    void *ptr;
} robj;

        1.1 类型 

        对象type属性记录了对象的类型,这个属性的值可以是下图中的一个。

        对于Redis数据库保存的键值对来说,键总是一个字符串对象,而值则可以是字符串对象,列表对象,哈希对象,集合对象或有序集合对象的其中一种。

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

        TYPE命令的实现方式也与此类似,当我们对一个数据库执行TYPE命令时,命令返回的结果为数据库键对应的值对象的类型,而不是键对象的类型。

        下图列出了TYPE命令在面对不同类型的值对象时所产生的输出:

        1.2 编码和底层实现

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

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

        每种类型的对象都至少使用两种不同的编码,下图列出了每种类型的对象可以使用的编码:

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

        下图列出了不同编码的对象所对应的OBJECT ENCODING 命令输出:

        通过encoding属性来设定对象所使用的编码,而不是为特定类型的对象关联一种固定的编码,极大的提升了Redis的灵活性和效率,因为Redis可以通过不同的使用场景来为一个对象设置不同的编码,从而优化对象在某一场景下的效率。

        举个例子:在列表包含的元素比较少时,Redis使用压缩列表作为列表对象的底层实现:

  • 因为压缩列表比双端链表更节省内存,并且元素比较少时,在内存中以连续块方式保存压缩列表比双端链表可以更快被加载到内存中。
  • 随着列表对象包含的元素越来越多,使用压缩列表来保存元素的优势逐渐消失时,对象就会将底层实现从压缩列表转向功能更强,也更加适合保存大量元素的双端链表上面。

        其他类型得得对象也通过使用多种不同的编码来进行类似的优化。

        下面介绍Redis中的五种不同类型的对象,说明这些对象底层所使用的编码方式,列出对象从一种编码转化成另外一种编码所需的条件,以及同一个命令在不同编码上的实现方式。

二. 字符串对象

        字符串对象的编码可以是int,raw或者emstr。

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

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

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

        2.1 embstr编码方式

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

        embstr编码的字符串对象在执行命令时,产生的效果和raw编码的字符串对象执行命令时产生的效果相同,但使用embstr编码的字符串对象来保存短字符串值有以下好处:

  • embstr编码将创建字符串对象所需的内存分配次数从raw编码的两次降低为一次。
  • 释放embstr编码字符串对象只需要调用一次内存释放函数,而释放raw编码的字符串对象需要调用两次内存释放函数。
  • 因为embstr编码的字符串对象所有数据都保存在一块连续的内存里,所以这种编码的字符串比起raw编码的字符串对象能够更好地利用缓存带来地优势。

        最后,可以用long double类型表示地浮点数在Redis中也是作为字符串值来保存地。如果我们要保存一个浮点数到字符串对象里面,那么程序会先将这个浮点数转换成字符串值,然后再保存转换所得地字符串值。

        举个例子:执行以下代码将创建一个包含3.14的字符串表示"3.14"的字符串对象,在有需要的时候,程序会将保存的字符串对象里面的字符串值转换回浮点数值,执行某些操作,然后再将执行操作所得的浮点数转换回字符串值,并继续保存子字符串对象里。

        下图总结并列出字符串对象保存各种不同类型的值所使用的编码方式:

         2.2 编码转换

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

        对于int编码的字符串对象来说,如果我们向对象执行了一些命令,使得这个对象保存的不再是整数值,而是一个字符串值,那么字符串对象的编码从int变为raw。

        在下面的示例中,我们通过APPEND命令,向一个保存整数值的字符串对象追加了一个字符串值,因为追加操作只能对字符串值执行,所以程序会先将之前保存的整数值10086转换为字符串值"10086",然后再执行追加操作,操作的执行结果就是一个raw编码的,保存了字符串值的字符串对象:

        因为Redis没有为embstr编码的字符串对象编写任何相应的修改程序(只有int编码的字符串对象和raw编码的字符串对象有这些程序)。所以embstr编码的字符串对象实际上是只读的。当我们对embstr编码的字符串对象执行任何修改命令时,程序会先将对象的编码从embstr编码转换成raw编码,然后再执行修改命令。 因为这个原因,embstr编码的字符串对象在执行修改命令之后,总会变成一个raw编码的字符串对象。

        2.3 字符串命令的实现

        因为字符串键的值为字符串对象,所以用于字符串键的所有命令都是针对字符串对象来构建的,下图列举了其中一部分字符串命令,以及命令在不同编码的字符串对象下的实现方法:

 三. 列表对象

       在redis3.2版本之前的列表对象的编码可以是ziplist(压缩列表)或者linkedlist(双端链表)。

       在redis3.2版本之后,已经不使用ziplist和linkedlist作为底层实现,取而代之的是quicklist。

        3.1 redis3.2版本前的ziplist和linkedlist

        ziplist编码的列表对象使用压缩列表作为底层实现,每一个压缩列表节点(entry)保存了一个列表元素。

        举个例子:如果我们执行RPUSH命令,那么服务器创建一个列表对象作为number键的值:

127.0.0.1:6379> RPUSH NUMBERS 1 "THREE" 5
(integer) 3

        如果numbers键的值对象使用的是ziplist编码,这个值对象将会是下图所展示的样子:

        另一方面,linkedlist编码的列表对象使用双端链表作为底层实现,每一个双端链表的节点都保存了一个字符串对象,而每一个字符串对象都保存了一个列表元素。

        举个例子:如果上面的列表使用的linkedlist编码,那么numbers键的值对象将是下图所示:

         注意:

  • linkedlist编码的列表对象在底层的双端链表结构中包含多个字符串对象,在之后介绍的哈希对象,集合对象和有序集合对象中都会出现,字符串对象是Redis五种类型的对象中唯一一个会被其他四种类型对象嵌套的对象。
  • 为了简化字符串对象,我们在途中使用的是StringObject字样的格子表示一个字符串对象,而实际保存的值,比如一个包含"three"字符串值的字符串对象,如下图:

        3.1.1 编码转换

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

  • 列表对象保存的所有字符串元素长度都小于64字节
  • 列表对象保存的元素数量小于512个,不能满足这两个条件的列表对象需要使用linkedlist编码。

        注意:以上两个条件的上限值是可以修改的,具体请看配置文件中关于list-max-ziplist-value选项和list-max-ziplist-entries选项的说明。

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

        例子:

        1. 列表对象因为保存长度太大的元素而进行编码转换的情况:

127.0.0.1:6379> rpush blah "hello" "world" "again"
(integer) 3
127.0.0.1:6379> object encoding blah
"ziplist"
127.0.0.1:6379> rpush blah "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww"
(integer) 4
127.0.0.1:6379> object encoding blah
"liskedlist"
127.0.0.1:6379> 

        2. 因为保存元素数量过多而进行编码转换

127.0.0.1:6379> EVAL "for i=1, 512 do redis.call('RPUSH', KEYS[1], i)end" 1 "integers"
(nil)
127.0.0.1:6379> llen integers
(integer) 512
127.0.0.1:6379> object encoding integers
"ziplist"
127.0.0.1:6379> rpush integers 513
(integer) 513
127.0.0.1:6379> object encoding integers
"linkedlist"
127.0.0.1:6379> 

        3.1.2 列表命令的实现

        3.2 redis3.2版本之后的quicklist

        3.2.1 为什么引入quicklist 

  • ziplist优缺点

        优点:存储在连续的内存上,不容易产生内存碎片,内存利用率高。

        缺点:插入和删除操作需要频繁的申请和释放内存,同时会发生内存拷贝,数据量大时,内存拷贝开销大。

  • linkedlist优缺点

        优点:插入和删除节点复杂度低。

        缺点:除了报存数据外还需要保存prev和next指针,内存利用率低。并且双向链表各个节点时单独的内存块,不连续,容易造成内存碎片。

        基于上述问题,quicklist的设计是时间和空间的折中,quicklist结合了ziplist和linkedlist的优点,核心思想是使用ziplist的特性,并使用linkedlist结构来减少ziplist的长度。

        3.2.2 quicklist内部结构

        quicklist每一个节点都是一个quicklistNode对象,数据结构如下:

typedef struct quicklistNode {
    //前一个节点
    struct quicklistNode *prev;
    //后一个节点
    struct quicklistNode *next;
    //当前指向的ziplist或者quicklistLZF
    unsigned char *zl;
    //当前ziplist占用字节
    unsigned int sz;             /* ziplist size in bytes */
    //ziplist中存储元素个数,16字节(最大65535个)
    unsigned int count : 16;     /* count of items in ziplist */
    //是否采用LZF压缩算法压缩节点 1:RAW 2: LZF
    unsigned int encoding : 2;   /* RAW==1 or LZF==2 */
    //存储结构
    unsigned int container : 2;  /* NONE==1 or ZIPLIST==2 */
    //当前ziplist是否需要再次被压缩(如果前面被解压过则为true,表示需要被再次压缩)
    unsigned int recompress : 1; /* was this node previous compressed? */
    //
    unsigned int attempted_compress : 1; /* node can't compress; too small */
    unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode;

         Redis使用quicklist来管理所有的quicklistNode,结构如下:

typedef struct quicklist {
    //列表头节点
    quicklistNode *head;
    //列表尾节点
    quicklistNode *tail;
    //ziplist一共存储了多少元素,即所有quicklistNode节点count和
    unsigned long count;        /* total count of all entries in all ziplists */
    //双向链表的长度,即quicklistNode节点数
    unsigned long len;          /* number of quicklistNodes */
    //填充因子
    int fill : QL_FILL_BITS;              /* fill factor for individual nodes */
    //压缩深度,0不压缩
    unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off */
    unsigned int bookmark_count: QL_BM_BITS;
    quicklistBookmark bookmarks[];
} quicklist;

         quicklist结构实际是一个双向链表,链表中保存的元素是ziplist。这样当ziplist达到某一长度时,会新建一个quicklistNode节点。这样做的优点有:

  • 使用到ziplist相比较于linkedlist,减少内存碎片。
  • 使用到双向链表,减少单个ziplist的长度,减少内存拷贝的开销。

         3.2.3 ziplist大小

        虽然说quicklist结合了ziplist和双向链表的优点,但是现在的问题是ziplist大小的选择。

  • ziplist太小,内存碎片越多
  • ziplist太大,分配大块连续内存空间的难度越大,内存拷贝消耗越大。

        如何保持ziplist的长度,取决于具体应用场景。

        我们可以通过配置list-max-ziplist-size参数来控制ziplist的大小。基于两种原则,元素个数或元素大小。

  • 当list-max-ziplist-size取正值的时候,表示按照数据项个数来限定每一个quicklist中ziplist的长度。比如:当list-max-ziplist-size等于5时,表示quciklist的ziplist节点最多包含5个数据项。
  • 当list-max-ziplist-size取负值的时候,表示按照占用字节数来限定每一个quicklist中ziplist的长度。这时,它只能取-5到-1这五个值。表示的含义如下:

-5: 每个quicklist节点上的ziplist大小不能超过64 Kb。(注:1kb => 1024 bytes)
-4: 每个quicklist节点上的ziplist大小不能超过32 Kb。
-3: 每个quicklist节点上的ziplist大小不能超过16 Kb。
-2: 每个quicklist节点上的ziplist大小不能超过8 Kb。(-2是Redis给出的默认值)
-1: 每个quicklist节点上的ziplist大小不能超过4 Kb。

        3.2.4 quicklist的compress属性

        列表设计最容易访问的是列表两端的数据,中间的访问频率很低,如果符合这个场景,redis中还有一个配置,可以对列表的中将节点进行压缩(采用的LZF——无痕压缩算法),进一步节省内存。

list-compress-depth 0 

        compress属性表示压缩深度,这个属性表示quicklist两端不被压缩的节点数:

        注意:这里压缩的节点是quicklist的节点quicklistNode,而不是ziplist里的数据。实际上压缩quicklist节点,包含的整个ziplist都会被压缩。

          compress属性表示压缩深度可以通过配置list-compress-depth来控制:

  • 0:不压缩(默认值)
  • 1:首尾第1个元素不压缩
  • 2:首位前2个元素不压缩
  • 3:首尾前3个元素不压缩
  • 以此类推

        3.2.5 quicklistNode的zl指针

        zl指针默认指向ziplist,sz属性记录了当前ziplist占用的字节数。

        但是当ziplist被压缩(LZF算法)后,zl指针指向另外一个对象quicklistLZF,quicklistLZF结构是一个4+N字节的结构。

typedef struct quicklistLZF {
    //LZF的大小
    unsigned int sz; /* LZF size in bytes*/
    //被压缩的内容
    char compressed[];
} quicklistLZF;

参考:【Redis】列表对象底层原理之quicklist_quicklist原理_云川之下的博客-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1268699.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LLM面面观之Prefix LM vs Causal LM

1. 背景 关于Prefix LM和Causal LM的区别,本qiang在网上逛了一翻,发现多数客官只给出了结论,但对于懵懵的本qiang,结果仍是懵懵... 因此,消遣了多半天,从原理及出处,交出了Prefix LM和Causal …

SparkRDD及算子-python版

RDD相关知识 RDD介绍 RDD 是Spark的核心抽象,即 弹性分布式数据集(residenta distributed dataset)。代表一个不可变,可分区,里面元素可并行计算的集合。其具有数据流模型的特点:自动容错,位置…

【SparkSQL】基础入门(重点:SparkSQL和Hive的异同、SparkSQL数据抽象)

【大家好,我是爱干饭的猿,本文重点介绍Spark SQL的定义、特点、发展历史、与hive的区别、数据抽象、SparkSession对象。 后续会继续分享其他重要知识点总结,如果喜欢这篇文章,点个赞👍,关注一下吧】 上一…

Python三百行代码实现一简约个人博客网站(全网最小巧)

这是全互联网最小巧的博客,没有比这更小的了。虽然小巧,但功能一点儿也不弱,支持文章的分页展示,文章表格,图片和代码语法高亮。文章无限制分类,访问量统计,按时间和按点击量排序,展…

端口隔离度

端口隔离度 隔离度为(本振或射频信号)泄漏到其他端口的功率与输入功率之比,单位是dB。 比如 RF to LO Isolation 表示 射频输入信号的功率 与 泄漏到LO端口的功率 之比。 而 LO to RF Isolation 则表示 本振输入信号的功率 与 泄漏到RF端口的…

深入Python元编程:了解声明与初始化定制元类

更多资料获取 📚 个人网站:ipengtao.com 简介 在Python中,元编程是指在运行时创建或定制类的编程。元类是Python中最强大的元编程工具之一,允许您控制类的创建过程。元类是类的类,它控制类的实例化,允许您…

Xcode 来自身份不明的开发者且与之前打开的版本不同。你确定要打开它吗?

Xcode新建一个项目,模拟器运行的时候频繁跳出 “x x x”来自身份不明的开发者且与之前打开的版本不同。你确定要打开它吗? 如下图: 这个和在mac上安装应用的情况有点不一样,在mac上安装应用遇到这个问题,只需要在“设置”-->…

深入了解Spring Boot中@Async注解的8大坑点

文章目录 1. 缺少EnableAsync注解2. 异步方法需独立3. 不同的异步方法间无法相互调用4. 返回值为void的异步方法无法捕获异常5. 外部无法直接调用带有Async注解的方法6. Async方法不适用于private方法7. 缺失异步线程池配置8. 异步方法与事务的兼容结语 🎉深入了解S…

对于 ` HttpServletResponse ` , ` HttpServletRequest `我们真的学透彻了吗

对于 **HttpServletResponse , HttpServletRequest**我们真的学透彻了吗 问题引入 PostMapping("/importTemplate") public void importTemplate(HttpServletResponse response) {ExcelUtil<SysUser> util new ExcelUtil<SysUser>(SysUser.class);uti…

深入了解Java8新特性-日期时间API之TemporalQuery、TemporalQueries

阅读建议 嗨&#xff0c;伙计&#xff01;刷到这篇文章咱们就是有缘人&#xff0c;在阅读这篇文章前我有一些建议&#xff1a; 本篇文章大概2000多字&#xff0c;预计阅读时间长需要5分钟。本篇文章的实战性、理论性较强&#xff0c;是一篇质量分数较高的技术干货文章&#x…

网络安全 | 使用人工智能阻止网络攻击

全球范围内分布式拒绝服务 (DDoS) 网络攻击急剧增加&#xff0c;这种数字攻击可以通过大量的互联网流量压垮目标服务器&#xff0c;从而使网站瘫痪。这种攻击每年都会发生数百万起&#xff0c;而且数量和规模都在不断增加。大约三分之一的网站宕机是由于 DDoS 攻击所致。 计算…

UVA 108 Maximum Sum

UVA 108 Maximum Sum 题面翻译 给定一个含有正负数的二维数组&#xff0c;找出有最大和的子矩阵。矩阵的和指矩阵中所有元素的和。 一个子矩阵是任意在总矩阵中大小为1x1或更大的邻近子数组&#xff0c;例如在下面的矩阵中: 0 −2 −7 0 9 2 −6 2 −4 1 −4 1 −1 8 0 −…

C++学习寄录(八.继承)

继承的语法&#xff1a;class 子类 : 继承方式 父类 class A : public B; A 类称为子类 或 派生类 B 类称为父类 或 基类 1.基本使用 未使用继承的代码比较冗余重复 #include <iostream> #include <fstream> #include <string> #include <chrono>…

搞定这三个问题 伦敦金止损就没问题

笔者多次强调&#xff0c;做伦敦金交易&#xff0c;重要的是风险控制。而止损是我们风险控制中一个很重要的概念。设定好止损&#xff0c;就是风险控制的好开始。下面我们通过三个问题&#xff0c;来解决止损的问题。 问题一&#xff0c;你的止损位在哪里&#xff1f;要做止损&…

python -- python安装

1、python的诞生和发展&#xff1a; python语言是一种解释型、面向对象型、动态数据类型的高级程序设计语言。 2、python的安装&#xff1a; 1、安装解析器&#xff1a; 在安装的过程中需要注意的是&#xff1a; 在安装pycharm的时候也是同样的道理&#xff0c;需要指定安装…

解决:IDEA的debug模式只有第一次能拦截请求进行debug,后续所有请求全部失效

解决&#xff1a;IDEA的debug模式只有第一次能拦截请求进行debug&#xff0c;后续所有请求全部失效 一问题描述&#xff1a;IDEA的debug模式只有第一次能拦截请求进行debug&#xff0c;后续所有请求全部失效二问题原因&#xff1a;对IDEA的debug功能不熟悉或者理解有偏差三解决…

vs配置64位汇编

vs开发64位程序无法使用内联汇编&#xff0c;需要将汇编放到一个单独文件中编译链接。 步骤如下&#xff1a; 生成汇编代码。以asm.asm为例&#xff0c;以下是模板&#xff1a; ;64位汇编程序模板 (Template) ;声明一个ExitProcess函数 ExitProcess PROTO.data;在这里声明变量…

外汇天眼:外汇市场中的“双向交易”是什么意思?

说到外汇市场&#xff0c;总免不了提到它双向交易的优势&#xff0c;很多新手会对这一点有所疑问&#xff0c;今天我们就帮大家解决这一个疑问。 何谓双向交易&#xff1f; 金融市场上&#xff0c;交易者最常接触到的股票&#xff0c;多属于单向交易。 单向交易的模式便是「先…

如何快速生成项目目录结构树?

经常在网上看到下面这种由一个项目&#xff0c;生成一个结构树&#xff0c;你知道它是怎么生成的吗&#xff1f; 这就是利用本文要介绍的一个工具——Treer&#xff0c;treer就是一款专门用来快速生成目录结构树的命令行工具。 第一步&#xff1a;安装treer 在终端执行全局…

分布式机器学习、联邦学习、多智能体的区别和联系——一文进行详细解释

1 分布式机器学习、联邦学习、多智能体介绍 最近这三个方面的论文都读过&#xff0c;这里写一篇博客归纳一下&#xff0c;以方便搞这几个领域的其他童鞋入门。我们先来介绍以下这三种机器学习范式的基本概念。 1.1 分布式机器学习介绍 分布式机器学习(distributed machine l…