当前位置: 首页 > 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.dtcms.com/a/306013.html

相关文章:

  • 【8月优质EI会议合集|高录用|EI检索稳定】计算机、光学、通信技术、电子、建模、数学、通信工程...
  • 人工智能与家庭:智能家居的便捷与隐患
  • 移动端WebView调试实战 全面排查渲染性能与布局跳动问题
  • ISO 26262 汽车功能安全(腾讯混元)
  • MongoDB系列教程-第二章:MongoDB数据库概念和特点、数据库操作、集合操作、文档操作、规范及常见问题解决、实际应用示例
  • JXD进步25.7.30
  • Thales靶机
  • 《Vuejs设计与实现》第 12 章(组件实现原理 下)
  • 非凸科技受邀出席第九届AIFOF投资创新发展论坛
  • 前端安全防护:XSS、CSRF与SQL注入漏洞深度解析与防御
  • 亚马逊云科技:赋能企业数字化转型,解决实际发展难题
  • 【Axure高保真原型】轮播条形图
  • 让科技之光,温暖银龄岁月——智绅科技“智慧养老进社区”星城国际站温情纪实
  • 【HarmonyOS】鸿蒙应用HTTPDNS 服务集成详解
  • 【Lua】元表常用属性
  • 【MySQL】MySQL索引—B树/B+树
  • 【选型】HK32L088 与 STM32F0/L0 系列 MCU 参数对比与选型建议(ST 原厂 vs 国产芯片)
  • 【LINUX网络】使用TCP简易通信
  • STM32 外部中断 和 定时器中断
  • 使用QT/C++完成的斗地主
  • ECharts从入门到精通:解锁数据可视化的魔法世界
  • 洛谷 P11230:[CSP-J 2024 T4] 接龙 ← 图论+动态规划
  • mac升级安装python3
  • MongoDB系列教程-第三章:PyMongo操作MongoDB数据库(1)—— 连接、基本CRUD操作
  • 代码随想录day50图论1
  • 代码随想录算法训练营第五十五天|图论part5
  • 【CF】Day115——杂题 (构造 | 区间DP | 思维 + 贪心 | 图论 + 博弈论 | 构造 + 位运算 | 贪心 + 构造 | 计数DP)
  • Python 程序设计讲义(43):组合数据类型——元组类型:元组的常用操作
  • KONG API Gateway中的核心概念
  • List 接口