当前位置: 首页 > news >正文

【Redis】list 类型

list

  • 一. list 类型介绍
  • 二. list 命令
    • lpush、lpushx、rpush、rpushx
    • lpop、rpop
    • lindex、lrange
    • linsert、lrem、ltrim
    • lset、llen
    • 阻塞版本命令:blpop、brpop
  • 三. list 命令小结
  • 四. list 内部编码方式
  • 五. list 使用场景
    • 缓存功能
    • 消息队列

一. list 类型介绍

  • 列表 (list) 相当于数组或者顺序表,但是 list 内部的结果 (编码方式),并非是一个简单的数组,而是更接近于 “双端队列”。
  • 列表类型是用来存储多个有序的字符串,列表中的每个字符串称为元素,一个列表最多可以存储 2^32 - 1 个元素。
  • 在 Redis 中,可以对列表两端插入和弹出,还可以获取指定范围的元素列表、获取指定索引下标的元素等。
  • 列表是一种比较灵活的数据结构,头部和尾部都能高效的插入和删除元素,它可以充当栈和队列的角色,在实际开发上有很多应用场景。
    • Redis 有一个典型的场景,就是作为消息队列,最早的时候,就是通过 list,但是功能有限,后来 Redis 又提供了一个 stream 类型。

列表两端插入和弹出操作:

在这里插入图片描述

列表的获取、删除等操作:

在这里插入图片描述

列表类型的特点:

  • 约定列表最左侧元素的下标是 0,且 Redis 的下标支持负数下标,最右侧元素的下标是 -1
  • 列表中的元素是有序的,这意味着可以通过索引下标获取某个元素或者某个范围的元素列表。
    • 例如:要获取第5个元素,可以执行 lindex user:1:messages 4,或者倒数第 1 个元素,lindex user:1:messages -1 就可以得到元素 e
  • 区分获取和删除的区别。
    • 例如:lrem 1 b 是从列表中把从左数遇到的前 1 个 b 元素删除,这个操作会导致列表的长度从 5 变成 4。
    • 例如:lindex 4 只会获取元素,但列表长度是不会变化的。
  • 列表中的元素是允许重复的。
    • 例如:上图中列表包含了两个 a 元素的。
    • 像 hash 这样的类型,field 是不能重复的。

列表中的元素是有序的。

  • 有序的含义,要根据上下文进行区分。
    • 有的时候,谈到有序,指的是 “升序”、“降序”。
    • 有的时候,谈到有序,指的是 顺序很关键。
      • 如果把元素的位置颠倒,顺序调换,此时得到的新的列表和之前的列表是不等价的。
  • 同一个词,怎么理解,务必要结合上下文,结合具体的场景。
    • 例如:栈和堆指的是什么?
      • 数据结构的栈和堆?操作系统的栈和堆?JVM的栈和堆?
    • 再例如:同步是什么?
      • 同步和互斥中的同步:多线程/进程按照一定的顺序访问共享资源。
        • 目的:防止竞态条件 (多个线程同时访问共享数据导致数据不一致)
      • 同步和异步中的同步:调用方在调用一个函数后,会等待该函数执行完成并返回结果,才继续执行后续代码。
        • 结果:调用方的执行流是阻塞的,直到被调用方完成。

二. list 命令

lpush、lpushx、rpush、rpushx

  • lpush:将一个或者多个元素从左侧插入,也就是头插到 list 中。
    • 若 key 不存在,则先创建 list,再插入数据。
  • 语法:lpush key element [element ...]
  • 时间复杂度:只插入一个元素为 O(1),插入多个元素为 O(N),N 为插入元素个数。
  • 返回值:插入后 list 的长度。

在这里插入图片描述

如果 key 已经存在,并且 key 对应的 value 类型不是 list,此时 lpush 命令就会报错,Redis 中所有的这些各种数据类型的操作,都是类似的效果。

  • lpushx:将一个或者多个元素从左侧插入,也就是头插到 list 中。
    • 若 key 不存在,创建 list 失败,也就是插入数据失败。
  • 语法:lpushx key element [element ...]
  • 时间复杂度:只插入一个元素为 O(1),插入多个元素为 O(N),N 为插入元素个数。
  • 返回值:插入后 list 的长度。

在这里插入图片描述

  • rpush:将一个或者多个元素从右侧插入,也就是尾插到 list 中。
    • 若 key 不存在,则先创建 list,再插入数据。
  • 语法:rpush key element [element ...]
  • 时间复杂度:只插入一个元素为 O(1),插入多个元素为 O(N),N 为插入元素个数。
  • 返回值:插入后 list 的长度。

在这里插入图片描述

  • rpushx:将一个或者多个元素从右侧插入,也就是尾插到 list 中。
    • 若 key 不存在,创建 list 失败,也就是插入数据失败。
  • 语法:rpushx key element [element ...]
  • 时间复杂度:只插入一个元素为 O(1),插入多个元素为 O(N),N 为插入元素个数。
  • 返回值:插入后 list 的长度。

在这里插入图片描述

注意:

  • lpushx 和 rpushx 中,结尾的 x,表示的是 exists
  • lrange 中起始的 l,表示的是 list,而并非 left,所以不存在 rlange

lpop、rpop

  • lpop:从 list 左侧取出元素,也就是头删。
  • 语法:lpop key
  • 时间复杂度:O(1)
  • 返回值:取出的元素或者 nil

在这里插入图片描述

  • rpop:从 list 右侧取出元素,也就是尾删。
  • 语法:rpop key
  • 时间复杂度:O(1)
  • 返回值:取出的元素或者 nil

在这里插入图片描述

注意:

  • 再当前 Redis 5 版本中,lpop 和 rpop 都是没有 count 参数的。
  • 从 Redis 6.2 版本,新增了一个 count 参数,描述这次要删几个元素。
    • lpop key countrpop key count
  • Redis 中的 list 是一个双端队列,从两头插入/删除元素的效率非常高效 O(1)
    • 搭配 rpush 和 rpop,也就是尾插和尾删,就相当于 “栈”。
    • 搭配 rpush 和 lpop,也就是尾插和头删,就相当于 “队列”。

lindex、lrange

  • lindex:给定下标,获取对应的元素。
  • 语法:lindex key index
  • 时间复杂度:O(N),N 指的是 list 中元素的个数。
  • 返回值:取出的元素或者 nil

在这里插入图片描述

  • lrange:获取 list 中,从 start 到 end 区间的所有元素,左闭右闭。
  • 语法:lrange key start stop
  • 时间复杂度:O(N)
  • 返回值:指定区间的元素。

在这里插入图片描述

注意:此处的序号,是专门给结果集使用的序号,与下标无关。

如下图,hash 操作有可能会得到这种带有序号的结果,此处的序号仅仅是标识返回元素的顺序,和下标无关,hash 类型就没有下标的概念。

在这里插入图片描述

谈到下标,往往会关注到超出范围的情况。

  • C++ 中,下标超出范围,一般会认为这是一个 “未定义行为”
    • 可能会导致程序崩溃、可能会得到一个不合法的数据、可能会得到一个看起来合法,但是错误的数据、可能恰好得到一个符合要求的数据。
    • 优点:效率是最高的。
    • 缺点:程序员不一定能第一时间发现问题。
  • Java 中,下标超出范围,一般会抛异常。
    • 优点:出现问题即使发现。
    • 缺点:多出一步下标合法的验证,速度比上面慢。
  • 在 Redis 中并没有采取上述的两种设定,Redis 的做法,是直接尽可能的获取到给点区间的元素。
    • 如果给定区间非法,比如超出下标,就会尽可能的获取对应的内容。
    • 此处对于越界下标的处理方式,更接近与 python 的处理方式 (切片)
    • 特点:鲁棒性,程序的容错能力更强 (你对我越粗鲁,我表现的较棒)

在这里插入图片描述

linsert、lrem、ltrim

  • linsert:在特定位置插⼊元素。
  • 语法:linsert key BEFORE|AFTER pivot element
  • 时间复杂度:O(N),N 指的是 list 中元素的个数。
  • 返回值:插入后的 list 长度。

在这里插入图片描述

万一要插入的列表中,基准值存在多个,怎么办?

linsert 进行插入的时候,要根据基准值,找到对应的位置,从左往右找,第一个符合基准值的位置即可。

在这里插入图片描述

  • lrem:删除 list 中的元素,具体删除的方法如下。
  • 语法:lrem key count element
    • 当 count > 0 时:从前往后,删除 |count| 个值为 element 的元素。
    • 当 count < 0 时:从后往前,删除 |count| 个值为 element 的元素。
    • 当 count = 0 时:删除所有值为 element 的元素。
  • 时间复杂度:O(N + M),N 是 list 的长度,M 是要删除的元素个数。
  • 返回值:返回成功删除的元素个数。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • ltrim:保留区间内的元素,删除区间外的元素。
  • 语法:ltrim key start stop
  • 时间复杂度:O(N),N 是要删除的区间内元素个数。
  • 返回值:返回 OK

在这里插入图片描述

lset、llen

  • lset:根据下标,修改元素。
  • 语法:lset key index element
  • 时间复杂度:O(N),N 指的是 list 中元素的个数。
  • 返回值:下标未越界时,返回OK,下标越界时,报错。

在这里插入图片描述

  • llen:获取 list 长度。
  • 语法:llen key
  • 时间复杂度:O(1)
  • 返回值:list 的长度。

在这里插入图片描述

阻塞版本命令:blpop、brpop

  • 阻塞:当前的线程不走了,代码不继续执行了,会满足一定的条件之后,被唤醒。
  • 阻塞队列:多线程的时候,讲了一个基于 “阻塞队列” 的 “生产者消费者模型”,使用队列作为中间的 “交易场所”,期望这个队列有两个特性:
    • 是线程安全的。
    • 在某些情况下,产生阻塞。
      • 如果队列为空,尝试出队列时,就会产生阻塞,直到队列不为空时,阻塞解除。
      • 如果队列为满,尝试入队列时,就会产生阻塞,直到队列不为满时,阻塞解除。
  • Redis 中的 list 也相当于 “阻塞队列” 一样。
    • 线程安全,是通过单线程模型支持的。
    • 阻塞,则只支持 “队列为空” 的情况,不考虑 “队列为满” 的情况。

lpop、rpop 是非阻塞版本,而 blpop 和 brpop 是阻塞版本,区别如下:

  • 在列表中有元素的情况下,阻塞和非阻塞表现是一致的。
  • 但如果列表中没有元素,非阻塞版本会直接返回 nil,但阻塞版本会根据 timeout 阻塞一段时间,阻塞期间 Redis 可以执行其他命令,但要求执行该命令的客户端会表现为阻塞状态。
    • 此处的 blpop 和 brpop 看起来好像消耗很久,但实际上并不会对 Redis 服务器产生负面影响,类似 IO多路转接。
  • 命令中如果设置了多个 key,那么会从左向右进行遍历 key,一旦有一个 key 对应的列表中可以弹出元素,命令立即返回。
    • blpop 和 brpop 都是可以同时去尝试获取多个 key 的列表元素,多个 key 对应多个 list,这多个 list 中,哪个有元素了,就会返回哪个元素,类似 IO多路转接。
  • 如果多个客户端同时多个 key 执行 pop,则最先执行命令的客户端会得到弹出的元素。

阻塞版本的 blpop 和 非阻塞版本 lpop 的区别:

在这里插入图片描述
在这里插入图片描述

  • blpop:头删的阻塞版本。
  • 语法:blpop key [key ...] timeout
    • 此处可以指定一个 key 或者 多个key,每个 key 的对应一个 list
    • 如果这些 list 有任何一个非空,blpop 都能够把这里的元素给获取到,立即返回。
    • 如果这些 list 都为空,此时就需要阻塞等待,等待其他客户端王这些 list 中插入元素。
    • 此处还可以指定超时时间 timeout,单位是秒 (Redis 6 中允许设定为小数,Redis 5 中必须是整数)
  • 时间复杂度:O(1)
  • 返回值:取出的元素或者 nil

针对一个非空的列表进行操作:

在这里插入图片描述

返回的结果相当于一个二元组 (类似 pair),一方是告诉我们当前的水来自于哪个 list,另一方面告诉我们获取到的数据是什么。

针对一个空的列表进行操作:

先打开一个客户端1,先对多个列表进行 blpop 操作,此时由于列表中没有数据,这是就会阻塞。再打开另一个客户端2,往列表中插入数据后,客户端1 解决阻塞,头删数据并返回。

在这里插入图片描述

针对多个 key 进行操作:

先打开一个客户端1,先进行 blpop 操作,此时由于这些列表中都没有数据,这是就会阻塞。再打开另一个客户端2,往其中一个列表中插入数据后,客户端1 解决阻塞,存在数据的列表头删数据并返回。

在这里插入图片描述

针对多个客户端 blpop 进行操作:

开启连个客户端都进行 blpop 操作,另一个客户端往列表中插入数据,此时第一个执行 blpop 的客户端,返回数据。

在这里插入图片描述

  • brpop 的效果和 blpop 完全一样,就不演示了。
  • 此处的 blpop 和 brpop 这两命令,虽然可以一定程度上满足 “消息队列” 这样的需求,但是整体来说还是比较有限的。

三. list 命令小结

命令执行效果时间复杂度
lpush key element [element …]头插数据,list 不存在时,创建 list,插入数据O(1)
lpushx key element [element …]头插数据,list 不存在时,不会创建 list,插入数据失败O(1)
rpush key element [element …]尾插数据,list 不存在时,创建 list,插入数据O(1)
rpushx key element [element …]尾插数据,list 不存在时,不会创建 list,插入数据失败O(1)
lpop key头删数据O(1)
rpop key尾删数据O(1)
blpop key [key …] timeout阻塞头删数据O(1)
brpop key [key …] timeout阻塞尾删数据O(1)
lindex key index查找下标的数据O(N)
lrange key start stop查找区间内的数据O(N)
linsert key BEFOREAFTER pivot element插入数据
lrem key count element移除数据O(N)
ltrim key start stop保留区间内的数据O(N)
lset key index element修改下标的数据O(N)
llen key获取 list 的长度O(1)

四. list 内部编码方式

  • ziplist (压缩列表):当列表的元素个数小于 list-max-ziplist-entries 配置 (默认 512个),同时列表中每个元素的长度都小于 list-max-ziplist-value 配置 (默认 64 字节) 时,Redis 会选用 ziplist 来作为列表的内部编码实现来减少内存消耗。
    • 当元素的个数多了,操作起来效率会降低。
  • linkedlist (链表):当列表类型无法满足 ziplist 的条件时,Redis 会使用 linkedlist 作为列表的内部实现。
  • quicklist:相当于链表和压缩列表的结合,整体还是一个链表,链表的每个节点是一个压缩列表,每个压缩列表都不让它太大,同时再把多个压缩列表通过链式结构连起来。
    • 优点:可以节约空间,同时效率也不会降低太多。

注意:现在的 list 内部的编码使用的是 quicklist,而不在使用 ziplist 和 linkedlist 了。

在这里插入图片描述

五. list 使用场景

缓存功能

微博 Timeline

每个用户都有属于自己的 Timeline (微博列表),现需要分页展示文章列表。此时可以考虑使用列表,因为列表不但是有序的,同时支持按照索引范围获取元素。

  • 每篇微博使用哈希结构存储,例如微博中 3 个属性:title、timestamp、content:
hmset mblog:1 title xx timestamp 1476536196 content xxxxx
...
hmset mblog:n title xx timestamp 1476536196 content xxxxx
  • 向用户 Timeline 添加微博,user:<uid>:mblogs 作为微博的键:
lpush user:1:mblogs mblog:1 mblog:3
...
lpush user:k:mblogs mblog:9
  • 分页获取用户的 Timeline,例如获取用户 1 的前 10 篇微博:
keylist = lrange user:1:mblogs 0 9
for key in keylist {hgetall key
}

此方案在实际中可能存在两个问题:

  • 如果每次分页获取的微博个数较多,需要执行多次 hgetall 操作,此时可以考虑使用 pipeline (流水线) 模式批量提交命令,或者微博不采用哈希类型,而是使用序列化的字符串类型,使用 mget 获取。
    • 当前一页中有多少数据不确定,可能会导致下面的循环比较大,从而执行多次 hgetall,也就是很多的网络请求。
    • 流水线:虽然我们是多个 Redis 命令,但是把这些命令合并成一个网络请求进行通信,可以大大降低客户端和服务器之间的交互次数。
  • 分裂获取文章时,lrange 在列表两端表现较好,获取列表中间的元素表现较差,此时可以考虑将列表做拆分。
    • 假设摸个用户发了 1w 哥微博,list 的长度就是 1w,就可以把这 1w 哥微博拆分成 10 份,每个就是 1k,如果是想获取到 5k 个左右的微博,就可以先找到哪个列表,再进行某个方向的遍历操作,找到微博,提高效率。

在这里插入图片描述

消息队列

Redis:阻塞消息队列模型

Redis 可以使用 lpush + brpop 命令组合实现经典的阻塞式生产者-消费者模型队列,生产者客户端使用 lpush 从列表左侧插入元素,多个消费者客户端使用 brpop 命令阻塞式地从队列中 “争抢” 队首元素。通过多个客户端来保证消费的负载均衡和高可用性。

在这里插入图片描述

  • Redis 是基于 多路转接 的单线程模型,所有命令的执行是串行的,是线程安全的。
  • brpop 这个操作是阻塞操作,当列表为空的时候,brpop 就会阻塞等待,一直等到其它客户端 push 元素,谁先执行 brpop 操作,谁就可以拿到这个新来的元素。像这样的设定,就能构成一个 “轮询” 式的效果。
  • 假设消费者执行的顺序是 1、2、3,当新元素到达之后,首先是消费者 1 拿到元素 (按照执行 brpop 命令的先后顺序来决定谁获取到的),消费者 1 拿到元素之后,也就从 brpop 中返回了 (相当于这个命令执行完了),如果消费者 1 还想继续消费,就需要重新执行 brpop,此时再来一个新的元素过来,就是消费者 2 拿到该元素,也从 brpop 中返回,如果消费者 2 还想继续消费,也需要重新执行 brpop,再来一个新元素,就是消费者 3 拿到这个元素。

Redis:分频道阻塞消息队列模型

Redis 同样使用 lpush + brpop 命令,但通过不同的键模拟频道的概念,不同的消费者可以通过 brpop 不同的键值,实现订阅不同频道的理念。

在这里插入图片描述

多个频道/列表,这种场景是非常常见的,例如:日常使用的一些程序,抖音。

  • 有一个频道,来传输 短视频 数据。
  • 有一个频道,来传输 弹幕 数据。
  • 有一个频道,来传输 点赞、转发、收藏 数据。
  • 有一个频道,来传输 评论 数据。
  • 搞成多个频道,就可以在某种数据发生问题的时候,不会造成其它数据的影响,做到解耦合。
http://www.lqws.cn/news/92881.html

相关文章:

  • 面向对象系统中对象交互的架构设计哲学
  • (四)动手实现多层感知机:深度学习中的非线性建模实战
  • OpenCV CUDA模块图像处理------双边滤波的GPU版本函数bilateralFilter()
  • 基于SDN环境下的DDoS异常攻击的检测与缓解
  • 二叉树day1
  • 如何使用插件和子主题添加WordPress自定义CSS(附:常见错误)
  • Linux系统-基本指令(5)
  • MQTTX连接阿里云的物联网配置
  • 00 Deep learning 之回归、拟合、逻辑回归
  • Nginx+Tomcat负载均衡集群
  • Oracle 故障实例 - 通过备份恢复到某时间点 故障恢复
  • 网络安全-等级保护(等保) 3-3 GB/T 36627-2018 《信息安全技术 网络安全等级保护测试评估技术指南》-2018-09-17发布【现行】
  • # 将本地UI生成器从VLLM迁移到DeepSeek API的完整指南
  • Unity异常上报飞书工具
  • 飞书常用功能(留档)
  • SpringBoot系列之RabbitMQ 实现订单超时未支付自动关闭功能
  • 内网横向之RDP缓存利用
  • 06-排序
  • B站缓存视频数据m4s转mp4
  • SpringBoot中缓存@Cacheable出错
  • 国产高云FPGA实现视频采集转UDP以太网输出,FPGA网络摄像头方案,提供2套Gowin工程源码和技术支持
  • Rust 学习笔记:使用 cargo install 安装二进制 crate
  • 【设计模式-4.7】行为型——备忘录模式
  • python第31天打卡
  • 多模态大语言模型arxiv论文略读(105)
  • Java-redis实现限时在线秒杀功能
  • Servlet 快速入门
  • 1130 - Host ‘xxx.x.xx.xxx‘is not allowed to connect to this MySQL server
  • 70道Hive高频题整理(附答案背诵版)
  • 如何合理设计缓存 Key的命名规范,以避免在共享 Redis 或跨服务场景下的冲突?