【Redis】List类型和底层原理

news2025/2/24 12:36:50

文章目录

    • List类型
      • 1.List介绍
        • 1.1常用命令
      • 2.List底层实现
      • 3.ziplist剖析
        • 3.1ziplist结构
        • 3.2创建一个空的ziplist
        • 3.3ziplist的元素节点结构
        • 3.4ziplisqt的连锁更新
        • 3.5ziplist的缺陷
      • 4.quicklist剖析
        • 4.1quicklist表头结构
      • 5.List的应用场景----消息队列
        • 5.1保证消息保序
        • 5.2处理重复的消息
        • 5.3保证消息的可靠性
        • 5.List作为消息队列的缺陷

List类型

1.List介绍

单键多值

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

列表的最大长度为 2^32 - 1,也即每个列表支持超过 40 亿个元素。

它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。

image-20221202010758936

1.1常用命令

**插入:**lpush/rpush … 从左边/右边插入一个或多个值。

image-20221202011539290

**取数据:**lpop/rpop 从左边/右边吐出一个值。值在键在,值取完键销毁。

image-20221202011702945

索引

  • lrange 按照索引下标获得元素(从左到右)

image-20221202011956690

  • lrange mylist 0 -1 0左边第一个,-1右边第一个,(0 -1表示获取所有)
  • lindex 按照索引下标获得元素(从左到右)

image-20221202012123017

**llen 获得列表长度 **

linsert <key>  before <value> <newvalue><value>的后面插入<newvalue>插入值
    
lrem <key><n><value>从左边删除n个value(从左到右)
    
lset<key> <index> <value>将列表key下标为index的值替换成value

2.List底层实现

list底层数据结构为ziplist和quicklist。

快速列表quicklistquicklist = 链表+ziplist

image-20221202012654143

  • 在列表元素较少的情况下,会使用一块连续的内存存储,**这个结构就是ziplist,**即压缩列表。它将所有的元素紧挨着一起存储,分配的是一块连续的内存
  • 当数据量变多时,会改成quicklist

3.ziplist剖析

3.1ziplist结构

压缩列表(ziplist)是哈希键的底层实现之一。它是经过特殊编码的双向链表,和整数集合(intset)一样,是为了提高内存的存储效率而设计的。当保存的对象是小整数值,或者是长度较短的字符串,那么redis就会使用压缩列表来作为哈希键的实现。

ziplist的结构

struct ziplist<T> {
	int32 zlbytes; // 整个压缩列表占用字节数
    
	int32 zltail_offset; // 最后一个元素距离压缩列表起始位置的偏移量,用于快速定位到最后一个节点
    
	int16 zllength; // 元素个数
    
	T[] entries; // 元素内容列表,挨个挨个紧凑存储
    
	int8 zlend; // 标志压缩列表的结束,值恒为 0xFF
}

image-20221202013706225

  • zlbytes:占4个字节,记录整个压缩列表占用的内存字节数。
  • zltail_offset:占4个字节,记录压缩列表尾节点entryN距离压缩列表的起始地址的字节数。
  • zllength:占2个字节,记录了压缩列表的节点数量。
  • entry[1-N]:长度不定,保存数据。
  • zlend:占1个字节,保存一个常数255(0xFF),标记压缩列表的末端。

实际上redis并没有提供一个结构体来保存压缩列表的信息,而是用了一组宏对压缩列表进行管理

//  ziplist的成员宏定义
//  (*((uint32_t*)(zl))) 先对char *类型的zl进行强制类型转换成uint32_t *类型,

//  然后在用*运算符进行解引用取内容运算,此时zl能访问的内存大小为4个字节。

//zl的类型为char*类型

#define ZIPLIST_BYTES(zl)       (*((uint32_t*)(zl)))
//将zl定位到前4个字节的bytes成员,记录这整个压缩列表的内存字节数

#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))
//将zl定位到4字节到8字节的tail_offset成员,记录着压缩列表尾节点距离列表的起始地址的偏移字节量

#define ZIPLIST_LENGTH(zl)      (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))
//将zl定位到8字节到10字节的length成员,记录着压缩列表的节点数量

#define ZIPLIST_HEADER_SIZE     (sizeof(uint32_t)*2+sizeof(uint16_t))
//压缩列表表头(以上三个属性)的大小10个字节

#define ZIPLIST_ENTRY_HEAD(zl)  ((zl)+ZIPLIST_HEADER_SIZE)
//返回压缩列表首节点的地址

#define ZIPLIST_ENTRY_TAIL(zl)  ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))
//返回压缩列表尾节点的地址

#define ZIPLIST_ENTRY_END(zl)   ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)
//返回end成员的地址,一个字节。

image-20221202014905801

3.2创建一个空的ziplist

unsigned char *ziplistNew(void) {   //创建并返回一个新的压缩列表
    //ZIPLIST_HEADER_SIZE是压缩列表的表头大小,1字节是末端的end大小
    unsigned int bytes = ZIPLIST_HEADER_SIZE+1;

    unsigned char *zl = zmalloc(bytes); //为表头和表尾end成员分配空间
    
    ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);    //将bytes成员初始化为bytes=11
    ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE);    //空列表的tail_offset成员为表头大小为10
    ZIPLIST_LENGTH(zl) = 0;     //节点数量为0
    
    zl[bytes-1] = ZIP_END;      //将表尾end成员设置成默认的255
    return zl;
}
  • intrev32ifbe()是封装的宏,用来根据主机的字节序按需要进行字节大小端的转换。

3.3ziplist的元素节点结构

redis对于压缩列表节点定义了一个zlentry的结构,用来管理节点的所有信息。

typedef struct zlentry {
    /*
    */
} zlentry;                 

虽然定义了这个结构体,但是Redis并没有使用zlentry对元素列表进行管理。因为该结构体存储短字符串和小整数太浪费空间。【一个结构体在32位机下的大小是28字节,意味着管理一个元素要多付出28字节内存】

因此,在redis中,并没有定义结构体来进行操作,也是定义了一些宏,压缩列表的节点真正的结构如下图所示:

image-20221202020346713

  • prevlen:记录前驱节点的长度(保证了从后往前遍历

prevlen属性的大小与前驱节点大小有关:

  • 如果前一个节点的长度小于 254 字节,那么 prevlen 属性需要用 1 字节的空间来保存这个长度值;
  • 如果前一个节点的长度大于等于 254 字节,那么 prevlen 属性需要用 5 字节的空间来保存这个长度值;
  • encoding:记录当前节点的value成员的数据类型以及长度。
  • entry_data:保存字节数组或整数数据
struct entry {
	int<var> prevlen; // 前一个 entry 的字节长度
    
	int<var> encoding; // 元素类型编码
    
	var entry_data; // 元素内容
}

3.4ziplisqt的连锁更新

ziplist的底层结构图

image-20221202101900053

压缩列表除了查找复杂度高的问题,还存在一个问题,连锁更新问题:

压缩列表新增某个元素或修改某个元素时,如果空间不不够,压缩列表占用的内存空间就需要重新分配。而当新插入的元素较大时,可能会导致后续元素的 prevlen 占用空间都发生变化,从而引起「连锁更新」问题,导致每个元素的空间都要重新分配,造成访问压缩列表性能的下降。

前面提到,prevlen属性的大小与前驱节点大小有关:

  • 如果前一个节点的长度小于 254 字节,那么 prevlen 属性需要用 1 字节的空间来保存这个长度值;
  • 如果前一个节点的长度大于等于 254 字节,那么 prevlen 属性需要用 5 字节的空间来保存这个长度值;

假设当前ziplist有多个大小在250~253字节中间的元素,元素的prevlen属性大小为1字节

image-20221202103629446

这时,如果将一个长度大于等于 254 字节的新节点加入到压缩列表的表头节点,即新节点将成为 e1 的前置节点,因为ziplist是一段连续的内存空间,所以后续的元素都需要更新:

因为 e1 节点的 prevlen 属性只有 1 个字节大小,无法保存新节点的长度,此时就需要对压缩列表的空间重分配操作,并将 e1 节点的 prevlen 属性从原来的 1 字节大小扩展为 5 字节大小,修改后e1的大小也大于了253字节,所以e2的prevlen大小也会被修改为5字节…

image-20221202110051981

这种在特殊情况下产生的连续多次空间扩展操作就叫做「连锁更新」

3.5ziplist的缺陷

ziplist是一段连续的内存空间,所以对ziplist空间扩展会导致空间的重新分配和拷贝。

**如果发生了连锁更新,**可能会导致多次空间扩展和拷贝,这就会直接影响到压缩列表的访问性能。

4.quicklist剖析

quickList就是一个标准的双向链表的配置,有head 有tail;每一个节点是一个quicklistNode,包含prev和next指针。而每一个quicklistNode 包含 一个ziplist,*zp 压缩链表里存储键值。所以quicklist是对ziplist进行一次封装,使用小块的ziplist来既保证了少使用内存,也保证了性能。

image-20221202020935737

4.1quicklist表头结构

typedef struct quicklist {
    //指向头部(最左边)quicklist节点的指针
    quicklistNode *head;
 
    //指向尾部(最右边)quicklist节点的指针
    quicklistNode *tail;
 
    //ziplist中的entry节点计数器
    unsigned long count;        /* total count of all entries in all ziplists */
 
    //quicklist的quicklistNode节点计数器
    unsigned int len;           /* number of quicklistNodes */
 
    //保存ziplist的大小,配置文件设定,占16bits
    int fill : 16;              /* fill factor for individual nodes */
 
    //保存压缩程度值,配置文件设定,占16bits,0表示不压缩
    unsigned int compress : 16; /* depth of end nodes not to compress;0=off */
} quicklist;

quicklist节点结构

typedef struct quicklistNode {
    struct quicklistNode *prev;     //前驱节点指针
    struct quicklistNode *next;     //后继节点指针
 
    //不设置压缩数据参数recompress时指向一个ziplist结构
    //设置压缩数据参数recompress指向quicklistLZF结构
    unsigned char *zl;
 
    //压缩列表ziplist的总长度
    unsigned int sz;                  /* ziplist size in bytes */
 
    //ziplist中包的节点数,占16 bits长度
    unsigned int count : 16;          /* count of items in ziplist */
 
    //表示是否采用了LZF压缩算法压缩quicklist节点,1表示压缩过,2表示没压缩,占2 bits长度
    unsigned int encoding : 2;        /* RAW==1 or LZF==2 */
 
    //表示一个quicklistNode节点是否采用ziplist结构保存数据,2表示压缩了,1表示没压缩,默认是2,占2bits长度
    unsigned int container : 2;       /* NONE==1 or ZIPLIST==2 */
 
    //标记quicklist节点的ziplist之前是否被解压缩过,占1bit长度
    //如果recompress为1,则等待被再次压缩
    unsigned int recompress : 1; /* was this node previous compressed? */
 
    //测试时使用
    unsigned int attempted_compress : 1; /* node can't compress; too small */
 
    //额外扩展位,占10bits长度
    unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode;

基于上面两个结构体,quicklist的大致结构为下图:

image-20221202021320174

5.List的应用场景----消息队列

消息队列在存取消息时,需要满足下面三个条件:消息保序、处理重复的消息和保证消息的可靠性。

5.1保证消息保序

List可以基于生产者消费者模型保证消息保序

List 本身就是按先进先出的顺序对数据进行存取的,所以,如果使用 List 作为消息队列保存消息的话,就已经能满足消息保序的需求了。

image-20221202111328268

  • 生产者使用 LPUSH key value[value...] 将消息插入到队列的头部,如果 key 不存在则会创建一个空的队列再插入消息。
  • 消费者使用 RPOP key 依次读取队列的消息,先进先出。

消费者读取数据时,有一个潜在的性能风险点

在生产者往 List 中写入数据时,List 并不会主动地通知消费者有新消息写入,如果消费者想要及时处理消息,就需要在程序中不停地调用 RPOP 命令。即便是消息已经处理完了,也会不断的调用 RPOP 命令。

这就会导致消费者程序的 CPU 一直消耗在执行 RPOP 命令上,带来不必要的性能损失。

  • 为了解决这个问题,Redis提供了一个 BRPOP 命令,BRPOP命令也称为阻塞式读取,客户端在没有读到队列数据时,自动阻塞,直到有新的数据写入队列,再开始读取新数据。避免了CPU不断调用RPOP命令的性能消耗。

image-20221202113800055

5.2处理重复的消息

消费者要实现重复消息的判断,需要 2 个方面的要求:

  • 每个消息都有一个全局的 ID。
  • 消费者要记录已经处理过的消息的 ID。当收到一条消息后,消费者程序就可以对比收到的消息 ID 和记录的已处理过的消息 ID,来判断当前收到的消息有没有经过处理。如果已经处理过,那么,消费者程序就不再进行处理了。

但是 List 并不会为每个消息生成 ID 号,所以我们需要自行为每个消息生成一个全局唯一ID,生成之后,我们在用 LPUSH 命令把消息插入 List 时,需要在消息中包含这个全局唯一 ID。

LPUSH mq "111000102:stock:99"

5.3保证消息的可靠性

如果消费者程序在处理消息的过程出现了故障或宕机,就会导致消息没有处理完成,那么,消费者程序再次启动后,就没法再次从 List 中读取消息了。

为了留存消息,List 类型提供了 brpoplpush 命令,这个命令的作用是让消费者程序从一个 List 中读取消息,同时,Redis 会把这个消息再插入到另一个 List(可以叫作备份 List)留存作为备份。

5.List作为消息队列的缺陷

  • List不支持多个消费者消费同一条信息。

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

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

相关文章

Ubuntu搭建本地web站点,并内网穿透实现公网访问

本次教程我们通过搭建Apache服务&#xff0c;部署一个简单的静态样例站点&#xff0c;并使用cpolar内网穿透将内网样例站点发布到公网来进行演示。 1. 设置 Apache Web 服务器 1.1 安装Apache Web sudo apt-get install apache2 -y1.2 启动Apache Web sudo service apache2…

[附源码]计算机毕业设计springboot校园招聘微信小程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

[附源码]计算机毕业设计自行车租赁管理系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

CSS盒子模型

&#x1f353;个人主页&#xff1a;bit.. &#x1f352;系列专栏&#xff1a;Linux(Ubuntu)入门必看 C语言刷题 数据结构与算法 HTML和CSS3 目录 1.盒子模型 1.2盒子模型&#xff08;Box Model&#xff09;组成 1.3边框&#xff08;border&#xff09; 1.4 表格的…

异常概述、常见异常 、自定义异常 、重载重写的异常区别

1.异常概述 程序运行过程中任何打断程序正常流程的情况称为异常或错误。比如:文件打不开、网络中断、操作符越界、类加载找不到等。 Java程序在执行过程中所发生的异常事件可分为两类&#xff1a; public class Test {public static void main (string [] args){stri…

靶向嵌合体PEG-ethoxycarbonyl-propanoic/Dodecaethylene glycol

蛋白水解靶向嵌合体(proteolysis targeting chimeras,PROTACs)通过连接基团将靶蛋白配体与E3连接酶配体利用化学键连接,将E3连接酶“募集”到靶蛋白附近,并利用细胞内的泛素-蛋白酶体系统,实现靶蛋白的泛素化标记和蛋白降解。靶蛋白一旦被降解,PROTACs分子便游离出来,参与到下一…

[附源码]JAVA毕业设计基于vue技术的汽车维修检测系统设计与实现(系统+LW)

[附源码]JAVA毕业设计基于vue技术的汽车维修检测系统设计与实现&#xff08;系统LW&#xff09; 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&…

【水光互补优化调度】基于非支配排序遗传算法的多目标水光互补优化调度(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

太厉害,Redis+Nginx+设计模式+Spring全家桶+Dubbo技术精选合集

最近花了很长的时间去搜罗Java核心技术好文&#xff0c;我把每个Java核心技术的优选文章都整理成了一个又一个的文档。昨天也是终于全部整理好了&#xff0c;今天就把这些东西分享给老铁们&#xff0c;也能为老铁们省去不少麻烦&#xff0c;想学什么技能了&#xff0c;遇到哪方…

MetersPhere自动化之后置SQL脚本及随机数变量---注册场景

MetersPhere自动化之后置SQL脚本及随机数变量—注册场景 场景分析&#xff1a; 1、需要一个手机号&#xff0c;不能每次都一样&#xff0c;因为注册一定是之前没有用的手机号&#xff0c;所以需要一个随机数 2、第一个接口&#xff0c;获取验证码&#xff0c;获取验证码后还需…

QT控件 — QScrollArea

例子&#xff1a;在窗体上显示一张大图片&#xff0c;然后使用滚动条来调节。 在窗体上添加控件 Scroll Area&#xff0c;如下图所示 需要说明的是&#xff0c;控件 Scroll Area 也相当于是一个容器。 后面主要说一下这个属性 在窗体上在放一个Label作为图片的容器。之后点…

Docker Toolbox下载安装运行镜像

1、Docker Toolbox下载 这是Docker Toolbox的下载链接&#xff0c;找个最新的下就好了. 阿里云开源镜像站资源目录 2、Docker Toolbox安装 下载之后点击exe文件点击安装&#xff0c;然后一直点next就行了. 安装之后&#xff0c;点击的时候如果显示快捷方式不对显示查找git.bash…

[附源码]Python计算机毕业设计Django贵港高铁站志愿者服务平台

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

GBase 8c 管理平台(一)-部署配置

GBase 8c 管理平台 GBase 8c 提供管理平台对数据库集群进行管理&#xff0c;用户可以通过管理平台提 供的图形化界面进行集群的部署配置、系统管理、运维监控、 告警管理、 备份恢 复、数据同步、数据加载等操作。 部署配置 GBase 管理平台提供可视化的界面&#xff0c;实现…

Hadoop大数据系统架构(深入浅出)

目录1. Hadoop生态体系2. Hadoop集群硬件架构3. Hadoop体系分层功能架构参考资料导读&#xff1a; 从资源管理角度来看&#xff0c;当前的大数据系统架构主要有两种&#xff1a;一种是 MPP数据库架构 &#xff0c;另一种是 Hadoop体系的分层架构。这两种架构各有优势和相应的适…

[附源码]Python计算机毕业设计SSM开小灶线下管理系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

[附源码]Python计算机毕业设计Django驾校预约管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

Jmeter 命令式执行脚本,使用windows bat 命令进行全自动静默获取token

系列文章目录 提示&#xff1a;阅读本章之前&#xff0c;请先阅读目录 文章目录系列文章目录前言一、创建bat文件二、把bat文件&#xff0c;放在jmeter目录里面三、效果更新日志前言 有这样的一个需求场景&#xff0c;我需要用jmeter去自动获取token&#xff0c;然后把这个tok…

将Perl5代码迁移到Perl6上的解决方案

无论您是一个正在将Perl 5代码转换为Perl 6并遇到一些问题的程序员&#xff0c;还是您只是想了解如果您尝试将Perl 5程序移植到Perl 6可能会发生什么&#xff0c;本文应该回答您的问题。 Perl 6文档已经包含了大部分(如果不是全部)你需要的文件要解决将Perl 5代码迁移到Perl 6…

[附源码]Python计算机毕业设计Django会议室预定管理APP

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…