Redis对象系统

前言

        在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博客

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

脚本格式问题记录

服务器上的一些脚本迁移到其他服务上发生的小问题 问题:执行一个在win10系统编写好的shell脚本,放到Linux上执行报错如下: bash: ./xxx.sh: /bin/bash^M: bad interpreter: No such file or directory 原因:window系统写的脚本&a…

【Spring Boot 源码学习】BootstrapRegistryInitializer 详解

Spring Boot 源码学习系列 BootstrapRegistryInitializer 详解 引言往期内容主要内容1. 初识 BootstrapRegistryInitializer2. 加载 BootstrapRegistryInitializer3. BootstrapRegistryInitializer 的初始化 总结 引言 书接前文《初识 SpringApplication》,我们从 …

A*算法学习

系列文章目录 前言 在总结 2023华为软件精英挑战赛——全赛段思路分享与总结 - 知乎 (zhihu.com)时,发现自己还有很多技术细节没搞懂,这里看静态全局路径规划最常见的A*算法,这个博主讲得很好: A-Star(A*&#xff0…

第十五届蓝桥杯(Web 应用开发)模拟赛 2 期-大学组(详细分析解答)

目录 1.相不相等 1.1 题目要求 1.2 题目分析 1.3 源代码 2.三行情书 2.1 题目要求 2.2 题目分析 2.3 源代码 3.电影院在线订票 3.1 题目要求 3.2 题目分析 3.3 源代码 4.老虎坤(不然违规发不出来) 4.1 题目要求 4.2 题目分析 4.3 源代码 …

mac 聚焦搜索不显示

我是连搜索框都不显示,不是搜索结果显示异常 点右上角的搜索按钮都毫无反应 我检查过快捷键之类的设置,都正常,最后是通过删除文件解决的 cd ~/Library/Preferences/ rm com.apple.Spotlight.plist 重启 mac 参考 Spotlight Search Not W…

“rhdf5filters.so’ not found when install ‘glmGamPoi‘ package

在R中安装glmGamPoi包的时候,出现了如下报错: install.packages(glmGamPoi) 尝试方案一: sudo apt install pkg-config libhdf5-dev安装lighdf5-dev,并将安装路径链接至usr/lib/文件。 locate rhdf5filters.so sudo ln -s /hom…

java-var类型推断的使用时机

写在前面: 在jdk9的时候引入了var关键字,但是这是一把双刃剑,使用的好的话可以简化代码提高可读性,如果使用的不好的话会导致反效果。 文章目录 使用原则推荐使用时机new关键字创建对象类型不重要for循环 不适合与泛型大量结合字…

【Java学习笔记】75 - 算法优化入门 - 马踏棋盘问题

一、意义 1.算法是程序的灵魂,为什么有些程序可以在海量数据计算时,依然保持高速计算? 2.拿老韩实际工作经历来说,在Unix下开发服务器程序,功能是要支持上千万人同时在线,在上线前, 做内测,一…

vuepress-----9、PWA

# 9、PWA 使用babel 的插件形式 [vuepress/pwa,{serviceWorker: true,updatePopup: {message: "New content is available.",buttonText: "Refresh"}}]提供 Manifest 和 icons (opens new window) 拷贝到public目录下 发布后出现 service workers [外链图片…

Spring第三课,Lombok工具包下载,对应图书管理系统列表和登录界面的后端代码,分层思想

目录 一、Lombok工具包下载 二、前后端互联的图书管理系统 规范 三、分层思想 三层架构: 1.表现层 2.业务逻辑层 3.数据层 一、Lombok工具包下载 这个工具包是为了做什么呢? 他是为了不去反复的设置setting and getting 而去产生的工具包 ⚠️工具…

二叉树(判断是否为对称二叉树)

题目(力扣): 观察题目,只需判断该二叉树是否对称。 判断二叉树是否对称,就可以换位去判断该二叉树的左子树和右子树是否对称。 这时就可以写一个辅助函数来方便判断。 该函数是判断两颗树是否镜像对称,这…

【华为数通HCIP | 网络工程师】821刷题日记-IS-IS(2)

个人名片: 🐼作者简介:一名大三在校生,喜欢AI编程🎋 🐻‍❄️个人主页🥇:落798. 🐼个人WeChat:hmmwx53 🕊️系列专栏:🖼️…

Docker—更新应用程序

在本部分中,你将更新应用程序和映像。您还将了解如何停止和移除容器。 一、更新源代码 在以下步骤中,当您没有任何待办事项列表项时,您将把“空文本”更改为“您还没有待办事项!在上面添加一个!” 1、在src/static/…

电子学会C/C++编程等级考试2022年12月(三级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:鸡兔同笼 一个笼子里面关了鸡和兔子(鸡有2只脚,兔子有4只脚,没有例外)。已经知道了笼子里面脚的总数a,问笼子里面至少有多少只动物,至多有多少只动物。 时间限制:1000 内存限制:65536输入 一行,一个正整数a (a < 327…

分发测试应用平台怎么用之应用详情功能

我的应用 应用功能引导 ●您会看到以下页面&#xff0c;下图为功能的解释方便您的运行 我的应用-详情-应用详情 ●我们点击应用详情数字③&#xff0c;点击应用详情&#xff0c;下图是对详情页的功能介绍。 详情-应用设置 ●详情-应用设置-下图为应用设置的上半部分 ●下图为应…

保障海外业务发展,Coremail提供高效安全的海外通邮服务

11月22日&#xff0c;Coremail举办《全球通邮&#xff1a;如何保障安全、快捷的海外中继服务》直播分享会&#xff0c;直播会上Coremail安全团队和直播嘉宾复旦大学校园信息化办公室徐艺扬老师就海外中继服务进行了深度分享。 ​ 海外通邮困难重重 境外垃圾邮件数量居高不下…

力扣日记11.28-【二叉树篇】二叉树的最小深度

力扣日记&#xff1a;【二叉树篇】二叉树的最小深度 日期&#xff1a;2023.11.28 参考&#xff1a;代码随想录、力扣 111. 二叉树的最小深度 题目描述 难度&#xff1a;简单 给定一个二叉树&#xff0c;找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点…

快速入门opencv(python版)

Open Source Computer Vision Library。OpenCV是一个&#xff08;开源&#xff09;发行的跨平台计算机视觉库&#xff0c;可以运行在Linux、Windows和Mac OS操作系统上。它轻量级而且高效——由一系列 C 函数和少量 C 类构成&#xff0c;同时提供了Python、Ruby、MATLAB等语言的…

后端项目连接数据库-添加MyBatis依赖并检测是否成功

一.在pom.xml添加Mybatis相关依赖 在Spring Boot项目中&#xff0c;编译时会自动加载项目依赖&#xff0c;然后使用依赖包。 需要在根目录下pom.xml文件中添加Mybatis依赖项 <!-- Mybatis整合Spring Boot的依赖项 --> <dependency><groupId>org.mybatis.s…

数据结构---树

树概念及结构 1.树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因 为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的 有一个特殊的结点&#xff0c…