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或者quicklistLZFunsigned 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: LZFunsigned 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.mzph.cn/news/183342.shtml

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

相关文章

vue项目运行时,报错:ValidationError: webpack Dev Server Invalid Options

在运行vue项目中,遇到报错:ValidationError: webpack Dev Server Invalid Options,如下图截图: 主要由于vue.config.js配置文件错误导致的,具体定位到proxy配置代理不能为空,导致运行项目报错,需…

连接备份1128

深度学习—分类识别篇:http://tr.daheng-imaging.com/watch/1050636http://tr.daheng-imaging.com/watch/1050636 深度学习—目标检测篇:http://tr.daheng-imaging.com/watch/1101141http://tr.daheng-imaging.com/watch/1101141 深度学习—缺陷分割篇&a…

Chrome显示分享按钮

分享按钮不见了! Chrome://flags Chrome Refresh 2023 Disabled 左上角的标签搜索会到右上角。

MATLAB实战 | APP设计

01、应用实战 【例1】生成一个用于观察视点仰角和坐标轴着色方式对三维图形显示效果影响的App,界面如图1所示。界面右上部的列表框用于选择绘图数据、切换按钮组用于选择绘图方法,中间的旋钮用于设置视点方位角和仰角,右下部的分档旋钮用于设…

Thrift RPC Java、Go、PHP使用例子

文章目录 1、Thrift RPC介绍1.1、Protocol 支持的数据传输协议1.2、Transport 支持的数据传输方式1.3、Server 支持的服务模型1.4、IDL语法数据类型1.5、开发步骤 2、接口定义文件2.1、创建接口定义文件2.2、生成对应平台语言代码2.2.1、下载生成工具2.2.2、生成各平台语言代码…

基于web宠颐生宠物医院系统设计与实现

基于web宠颐生医院系统开发与实现 摘要:时代飞速发展,网络也飞速发展,互联网许多的行业都可以用互联网实现了,互联网已经成为了人们生活中重要的一部分,或多或少的影响着我们的生活,互联网在给我带了方便的…

Bert-VITS2本地部署遇到的错误

关于Bert-VITS2本地部署遇到的错误 1、在下载python中相关依赖时报错 building ‘hdbscan._hdbscan_tree’ extension error: Microsoft Visual C 14.0 or greater is required. Get it with “Microsoft C Build Tools”: https://visualstudio.microsoft.com/visual-cpp-bu…

【安全-SSH】SSH安全设置

今天发现自己的公有云服务器被攻击了 然后查看了登录日志,如上图 ls -sh /var/log/secure vim /var/log/secure然后增加了安全相关的设置 具体可以从以下方面增加安全性: 修改默认SSH端口公有云修改安全组策略及防火墙端口设置登录失败次数锁定用户及…

RabbitMQ消息模型之Routing-Direct

Routing Direct 在Fanout模式中,一条消息,会被所有订阅的队列都消费。但是在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。 在Direct模型下: 队列与交换机的绑定,不能是任意…

Spring不再支持Java8了

在今天新建模块的时候发现了没有java8的选项了,结果一查发现在11月24日,Spring不再支持8了,这可怎么办呢?我们可以设置来源为阿里云https://start.aliyun.com/ 。 java8没了 设置URL为阿里云的地址

【Vue3】源码解析-虚拟DOM

【Vue3】源码解析 系列文章什么是虚拟DOMVue 3虚拟DOM获取<template>内容生成AST语法树生成render方法字符串得到最终VNode对象 系列文章 【Vue3】源码解析-前置 【Vue3】源码解析-响应式原理 【Vue3】源码解析-虚拟DOM 什么是虚拟DOM 在浏览器中&#xff0c;HTML页面…

5 面试题--redis

伪客户端&#xff1a; 伪客户端的 fd 属性值为 -1&#xff1b;伪客户端处理的命令请求来源于 AOF ⽂件或者 Lua 脚本&#xff0c;⽽不是⽹络&#xff0c;所以这种客户端不需要套接字连接&#xff0c;⾃然也不需要记录套接字描述符。⽬前 Redis 服务器会在两个地⽅ ⽤到伪客户端…

ThermalLabel SDK for .NET 13.0.23.1113 Crack

ThermalLabel SDK for .NET 是一个 .NET 典型类库&#xff0c;它允许用户和开发人员创建非常创新的条码标签并将其发布在 zebra ZPL、EPL、EPSON ESC、POS 以及 Honeywell intermec 指纹中通过在 VB.NET 或 C# 上编写 .NET 纯代码来实现热敏打印机&#xff0c;以实现项目框架的…

Ubuntu Linux玩童年小霸王插卡游戏

1.下载安装模拟器 在Windows平台模拟器非常多&#xff0c;而且效果也很优秀&#xff0c;Linux平台的用户常常很羡慕&#xff0c;却因为系统的缘故&#xff0c;无法使用这样的模拟器&#xff0c;但是随着时代的发展&#xff0c;Linux平台也出现了许多优秀的模拟器&#xff0c;现…

navigator.clipboard is undefined in JavaScript issue [Fixed]

navigator.clipboard 在不安全的网站是无法访问的。 在本地开发使用localhost或127.0.0.1没有这个问题。因为它不是不安全网站。 在现实开发中&#xff0c;可能遇到测试环境为不安全网站。 遇到这个问题&#xff0c;就需要将不安全网站标记为非不安全网站即可。 外网提供了3…

【HTML】VScode不打开浏览器实时预览html

1. 问题描述 预览HTML时&#xff0c;不想打开浏览器&#xff0c;想在VScode中直接实时预览 2. 解决方案 下载Microsoft官方的Live Preview 点击预览按钮即可预览

Unity中Shader优化通用规则

文章目录 前言一、精度优化1、三种精度 fixed / half / float2、位置坐标、物理坐标类使用float3、HDR颜色、方向向量类使用half4、普通纹理、颜色类使用 fixed5、实际上&#xff0c;使用的精度取决于 平台 和 GPU6、现在桌面级GPU都是直接采用 float , Shader中的 fixed / hal…

J2EE征程——第一个纯servletCURD

第一个纯servletCURD 前言在此之前 一&#xff0c;概述二、CURD1介绍2查询并列表显示准备实体类country编写 CountryListServlet配置web.xml为web应用导入mysql-jdbc的jar包 3增加准备增加的页面addc.html编写 CAddServlet配置web.xml测试 4删除修改CountryListServlet&#xf…

RabbitMQ消息模型之Routing-Topic

Routing Topic Topic类型的Exchange与Direct相比&#xff0c;都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key的时候使用通配符&#xff01;这种模型Routingkey一般都是由一个或多个单词组成&#xff0c;多个单词之间以”…

ESP32-Web-Server编程- WebSocket 编程

ESP32-Web-Server编程- WebSocket 编程 概述 在前述 ESP32-Web-Server 实战编程-通过网页控制设备的 GPIO 中&#xff0c;我们创建了一个基于 HTTP 协议的 ESP32 Web 服务器&#xff0c;每当浏览器向 Web 服务器发送请求&#xff0c;我们将 HTML/CSS 文件提供给浏览器。 使用…