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

第六章:进入Redis的List核心

一.List概念 

列表类型是⽤来存储多个有序的字符串,如图 2-19 所示,a、b、c、d、e 五个元素从左到右组成
了⼀个有序的列表,列表中的每个字符串称为元素(element),⼀个列表最多可以存储
个元素。在 Redis 中,可以对列表两端插⼊(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等(如下图所示。列表是⼀种比较灵活的数据结构,它可以 当栈和队列的角色,在实际开发上有很多应用场景

列表两端插⼊和弹出操作 

 列表的获取、删除等操作 

列表类型的特点: 

  1. 列表中的元素是有序的,这意味着可以通过索引下标获取某个元素或者某个范围的元素列表
  2. 区分获取和删除的区别
  3. 列表中的元素是允许重复的
  4. 列表(List)相当于数组或者顺序表。
  5. 注意:List 内部的结构和访问方式并非是一个简单的数组,而是更接近于 “双端队列”(deque)。
  6. 的边最左的元素下标是 0,Redis 的下标支持负下标,getrange。
  7. 区分获取和删除的区别:
    1. Index 能获取到元素的值。
    2. lrem 也能返回被删除元素的值。
  8. 像 hash 这样的类型,field 是不能重复的。
  9. 因为当前的 List,头和尾都能高效的插入删除元素,所以可以把这个 List 当做一个栈 / 队列来使用了。
  10. Redis 有一个典型的应用场景,就是作为消息队列,最早的时候,就是通过 List 实现。
  11. 后来 Redis 又提供了一个 stream 类型。


二List核心命令 

2.1LPUSH,LPRUSHX

LPUSH命令:将⼀个或者多个元素从左侧放入(头插)到 list 中。
 LPUSH key element [element ...]
时间复杂度:只插⼊⼀个元素为 O(1), 插⼊多个元素为 O(N), N 为插⼊元素个数.
返回值:插⼊后 list 的⻓度

LPRUSH命令:在 key 存在时,将⼀个或者多个元素从左侧放入(头插)到 list 中。不存在,直接返回

LPUSHX key element [element ...]

时间复杂度:只插⼊⼀个元素为 O(1), 插⼊多个元素为 O(N), N 为插⼊元素个数.

返回值:插⼊后 list 的⻓度

  

如何搭配使用? 

  • Redis 中的 list 是一个双端队列。
  • 从两头插入 / 删除元素都是非常高效 O (1)。
  • 搭配使用 rpush 和 lpop,就相当于队列了。
  • 搭配使用 rpush 和 rpop,就相当于栈了。


2.2LPOP

  • LPOP key
  • RPOP key [count]
  • LPOP 和 RPOP 在当前的 redis 5 版本中,都是没有 count 参数。
  • 从 redis 6.2 版本,新增了一个 count 参数。
  • count 就描述这次要删几个元素。

LPOP 命令:从 list 左侧取出元素(即头删) 

LPOP key

时间复杂度:O(1)  

返回值:取出的元素或者 nil。


2.3LRANGE

Lrange 命令:查看 list 中指定范围的元素。

此处描述的区间也是闭区间,下标支持负数。

  • 谈到下标,往往会关注超出范围的情况:
    • C++ 中,下标超出范围,一般会认为这是一个 “未定义行为”。
      • 缺点:程序不一定能第一时间发现问题!!
      • 优点:效率是最高的!!
      • 可能会导致程序崩溃。
      • 也可能会得到一个不合法的数据。
      • 还可能会得到一个看起来对,但是错误的数据。
      • 也有可能恰好得到一个符合要求的数据。[开盲盒]
    • Java 中,下标超出范围,一般会抛出异常。多出一步下标合法性的验证。
      • 缺点:速度比上面要慢。
      • 优点:出现问题能及时发现。
    • 在 Redis 中并没有采用上述的两种设定。
      • Redis 的做法,是直接尽可能的获取到给定区间的元素,如果给定区间非法,比如超出下标,就会尽可能的获取对应的内容。
      • 此处对于越界下标的处理方式,更接近于 python 的处理方式。(python 的切片)
  • “鲁棒性”(你对我越粗鲁,我就表现的越棒),程序的容错能力更强。
  • 是机器执行的快重要?还是程序代码好写的快重要?利益相关。
LRANGE命令:获取从 start 到 end 区间的所有元素,左闭右闭

LRANGE key start stop
时间复杂度:O(N)
返回值:指定区间的元素。


2.4RPUSH,RPUSHX

RPUSH命令:将⼀个或者多个元素从右侧放⼊(尾插)到 list 中 

RPUSH key element [element ...]

时间复杂度:只插⼊⼀个元素为 O(1), 插⼊多个元素为 O(N), N 为插⼊元素个数.

返回值:插⼊后 list 的⻓度

RPUSHX命令:在 key 存在时,将⼀个或者多个元素从右侧放⼊(尾插)到 list 中 

RPUSHX key element [element ...]
时间复杂度:只插⼊⼀个元素为 O(1), 插⼊多个元素为 O(N), N 为插⼊元素个数.
返回值:插⼊后 list 的⻓度


2.5RPOP

  • LPOP key
  • RPOP key [count]
  • LPOP 和 RPOP 在当前的 redis 5 版本中,都是没有 count 参数。
  • 从 redis 6.2 版本,新增了一个 count 参数。
  • count 就描述这次要删几个元素。
RPOP命令:从 list 右侧取出元素(即尾删)。
 RPOP key
时间复杂度:O(1)
返回值:取出的元素或者 nil。

 


2.6LINDEX ,LINSERT ,LLEN

LINDEX 命令:获取从左数第 index 位置的元素 

 LINDEX key index

 时间复杂度:O(N)

返回值:取出的元素或者 nil。

(注意插入有多个基准值后端情况是怎么样的) 

LINSERT 命令:在特定位置插⼊元素。
  • 返回值是插入之后,得到的新的 list 的长度。
  • 万一要插入的列表中,基准值,存在多个,咋办?
    • linsert 进行插入的时候,要根据基准值,找到对应的位置,从左往右找,找到第一个符合基准值的位置即可。
    • O (N),N 表示列表的长度。
 LINSERT key <BEFORE | AFTER> pivot element

LLEN 命令:获取 list ⻓度。 
LLEN key
时间复杂度:O(1)
返回值:list 的长度


 2.7LREM  

当 count > 0 时

  • 规则:从列表的头部开始向尾部查找,删除最多 count 个值等于 value 的元素。

 

当 count < 0 时

  • 规则:从列表的尾部开始向头部查找,删除最多 Math.abs(count) 个值等于 value 的元素。

当 count = 0 时

  • 规则:删除列表中所有值等于 value 的元素。


2.8LSET

LSET命令:根据下标修改元素 

  • LSET key index element
  • O(N)
  • lindex 可以很好的处理下标越界的情况,直接返回 nil。
  • lset 来说则会报错,不会像 js 那样,直接在 10 这个下标这里搞出个元素来


 2.9LTRIM 

  • LTRIM key start stop
  • 保留 start 和 stop 之间区间内的元素(区间外面两边的元素就直接被删除了)

 


 2.10BLPOP,BRPOP(阻塞版本)

阻塞队列相关: 

  • 但阻塞不会本质慢很多,阻塞一段时间后,回到 Redis 可以执行其他命令。
  • 使用 brpop 和 blpop 的时候,这里是可以显式设置超时时间的!!(不一定是无休止的等待)
  • 此处的 blpop 和 brpop 看起来好像耗时很久,但是实际上并不会对 redis 服务产生负面影响!
  • 命令中如果设置了多个键,那么会从左到右进行遍历,一旦有一个对应的列表中可以弹出元素,命令立即返回。
  • blpop 和 brpop 都可以同时去尝试获取多个 key 的列表的元素的。
    • 多个 key 对应多个 list。
    • 这多个 list 哪个有元素了,就会返回哪个元素。
  • 如果多个客户端同时对一个键进行 lpop,则最先执行命令的客户端会得到弹出的元素。

  • blpop 和 brpop 是 lpop 和 rpop 的阻塞版本,和对应非阻塞版本的作⽤基本⼀致
  • 在列表中有元素的情况下,阻塞和非阻塞表现是⼀致的。但如果列表中没有元素,非阻塞版本会立即返回 nil,但阻塞版本会根据 timeout,阻塞⼀段时间,期间 Redis 可以执行其他命令,但要求执行该命令的客⼾端会表现为阻塞状态
  • 命令中如果设置了多个键,那么会从左向右进⾏遍历键,⼀旦有⼀个键对应的列表中可以弹出元素,命令立即返回。 如果多个客⼾端同时多⼀个键执行pop,则最先执行命令的客⼾端会得到弹出的元素

 

 


 BROPO和上述同理。

  • BLPOP key [key ...] timeout
  • 此处可以指定一个 key 或者多个 key,每个 key 都对应一个 list。
  • 如果这些 list 有任何一个非空,blpop 都能够把这里的元素给获取到,立即返回。
  • 如果这些 list 都为空,此时就需要阻塞等待,等待其他客户端往这些 list 中插入元素了。
  • 此处还可以指定超时时间,单位是秒(Redis 6,超时时间允许设定成小数,Redis 5 中,超时时间,得是整数)。
    1. 针对一个非空的列表进行操作:
      • 返回的结果相当于一个 pair(二元组)。
      • 一方面是告诉我们当前的数据来自于哪个 key。
      • 一方面是告诉我们取到的数据是啥。
    2. 针对一个空的列表进行操作。
    3. 针对多个 key 进行操作。
  • brpop 效果和 blpop 完全一样。(这里是尾端了)
  • 此处的这俩阻塞命令用途主要就是用来作为 “消息队列”。
  • 当前这俩命令虽然可以一定程度的满足 “消息队列” 这样的需求,整体来说,这俩命令功能还是比较有限。
  • blpop 和 brpop 有些类似逻辑。


三.内部编码

  • 压缩列表(ziplist):一种把数据按照更紧凑的压缩形式进行表示,以节省空间的数据结构,但当元素个数增多时,操作效率会降低。
  • 链表(linkedlist):当列表类型⽆法满⾜ ziplist 的条件时,Redis 会使⽤ linkedlist 作为列表的内部实现。
  • 快速列表(quicklist):相当于链表和压缩列表的结合体,整体是一个链表,链表的每个节点是一个压缩列表。它通过控制每个压缩列表的大小,并将多个压缩列表通过链式结构连接起来,在空间和效率之间取得较好的平衡。
  • 相关配置
  • list-max-ziplist-entries 配置和 list-max-ziplist-value 配置,不过现在已经不再使用。
    list-max-ziplist-size 配置,有不同的取值对应不同的最大大小,
  • -5 对应 64KB(不推荐用于常规工作负载)、
  • -4 对应 32KB(不推荐
  • -3 对应 16KB(可能不推荐)、
  • -2 对应 8KB(良好)、
  • -1 对应 4KB(良好)等。
    可以通过 OBJECT encoding key 命令查看某个键对应的数据结构编码,例如图中显示为 quicklist。


 四.list的经典应用场景

列表(list)类型的应用场景

  • 用 list 作为 “数组” 这样的结构,来存储多个元素。
  • redis 提供的查询功能,不像 mysql 这么强大。

举个栗子~

  • MySQL 表结构
    • 学生表(student):包含 studentId(学生 ID)、studentName(学生姓名)、age(年龄)、score(分数)、classId(班级 ID)字段。
    • 班级表(class):包含 classId(班级 ID)、className(班级名称)字段。
    • 在上表结构中,可以很方便地实现 “查询指定班级中有哪些同学”。
  • Redis 存储结构
    • 以特定的 key 来存储学生和班级信息,如 student:1 存储学生 1 的姓名、年龄、分数;class:1 存储班级 1 的名称等。
    • 还通过 classStudents:1 、classStudents:2 等键来存储对应班级的学生 ID 列表。
    • 具体 redis 中的数据如何组织,都要根据实际的业务情况来决定。

4.1消息队列 和 分频道的消息队列 

消息队列 :

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

 Redis 阻塞消息队列模型:

  • 生产者通过 lpush 操作向 Redis 的列表中添加元素。
  • 多个消费者通过 brpop 操作从列表中获取元素,只有一个消费者能 “抢到” 元素,谁先执行 brpop 命令谁就能拿到新来的元素,这样的设定能构成一个 “轮询” 式的效果。
  • 假设消费者执行顺序是 1、2、3,当新元素到达后,首先是消费者 1 拿到元素(按照执行 brpop 命令的先后顺序决定)。消费者 1 拿到元素后,就从 brpop 中返回(相当于这个命令执行完了),如果消费者 1 还想继续消费,就需要重新执行 brpop。此时再来新元素,就是消费者 2 拿到该元素并从 brpop 中返回,如果消费者 2 还想继续消费,也需要重新执行 brpop。接着再来新元素,就是消费者 3 拿到这个元素。
  • brpop 操作是阻塞操作,当列表为空时,brpop 就会阻塞等待,一直等到其他客户端 push 了元素。

分频道的消息队列 :

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

举个栗子~ 

假设我们有一个社交应用,有 “通知”(notifications)和 “私信”(messages)两个频道。

  • 生产者方面,当有新通知时,用lpush notifications "new like"生产通知消息;当有新私信时,用lpush messages "hello"生产私信消息。
  • 消费者方面,有的消费者可能只关心通知,就执行brpop notifications 0;有的消费者可能只关心私信,就执行brpop messages 0;还有的消费者可能同时关心通知和私信,就执行brpop notifications messages 0,这样当notifications或messages列表中有新消息时,这个消费者就可能获取到消息进行处理。并且对于notifications列表来说,多个只订阅它的消费者中只有一个能抢到新通知消息;对于messages列表同理。

4.2微博 Timeline

每个用户都有属于自己的 Timeline(微博列表),现需要分页展示文章列表。此时可以考虑使用列表,因为列表不但是有序的,同时支持按照索引范围获取元素。
1.每篇微博使用哈希结构存储,例如微博中 3 个属性
hmset mblog:1 title xx timestamp 1476536196 content xxxxx
...
hmset mblog:n title xx timestamp 1476536196 content xxxxx
2.向用户 Timeline 添加微博,user:<uid>:mblogs 作为微博的键:
lpush user:1:mblogs mblog:1 mblog:3
...
lpush user:k:mblogs mblog:9

3.分页获取用户的 Timeline,例如获取用户 1 的前 10 篇微博:

keylist = lrange user:1:mblogs 0 9
for key in keylist {hgetall key
}

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

  1. 1 + n 问题。即如果每次分页获取的微博个数较多,需要执行多次 hgetall 操作,此时可以考虑使用 pipeline(流水线)模式批量提交命令,或者微博不采用哈希类型,而是使用序列化的字符串类型,使用 mget 获取。
  2. 分裂获取文章时,lrange 在列表两端表现较好,获取列表中间的元素表现较差,此时可以考虑将列表做拆分。


http://www.lryc.cn/news/604834.html

相关文章:

  • 「Spring Boot + MyBatis-Plus + MySQL 一主两从」读写分离实战教程
  • Tomcat线程池、业务线程池与数据库连接池的层级约束关系解析及配置优化
  • 《Java 程序设计》第 12 章 - 异常处理
  • 配置国内镜像源加速Python包安装
  • Three.js 与 React:使用 react-three-fiber 构建声明式 3D 项目
  • 数据仓库深度探索系列:架构选择与体系构建
  • 标准七层网络协议和TCP/IP四层协议的区别
  • rsync+sersync实现文件实时同步
  • C++实战:抖音级视频应用开发精髓
  • 力扣219:存在重复元素Ⅱ
  • 基于deepseek的事件穿透分析-风险传导图谱
  • Java源码构建智能名片小程序
  • FastAPI入门:表单数据、表单模型、请求文件、请求表单与文件
  • CVAE 回顾版
  • springcloud03-Nacos配置中心
  • Apache Ignite 2.8 引入的新指标系统(New Metrics System)的完整说明
  • 如何通过项目管理系统提升交付率
  • 【ip】IP地址能否直接填写255?
  • Ubuntu22.04中搭建GN编译环境
  • 用Python+MySQL实战解锁企业财务数据分析
  • 深入浅出:C++ STL简介与学习指南
  • 文件加密工具(勒索病毒加密方式)
  • Dify 从入门到精通(第 4/100 篇):快速上手 Dify 云端:5 分钟创建第一个应用
  • VS2022 libtorch-win-shared-with-deps-2.7.1+cu126 配置记录
  • 程序开发中常用的 Emoji 符号
  • Python爬虫04_Requests豆瓣电影爬取
  • 生成模型实战 | GLOW详解与实现
  • JavaFX CSS @font-face 错误全面分析 loadStylesheetUnPrivileged / reportException
  • 快速删除Word和WPS文字中的空白行
  • Redis实现数据传输简介