Redis List 应用指南:命令、编码与阻塞操作全解析

news2024/11/15 13:41:05

list 类型

    • 一 . 常见命令
      • 1.1 lpush、lrange
      • 1.2 lpushx
      • 1.3 rpush
      • 1.4 rpushx
      • 1.5 lpop、rpop
      • 1.6 lindex
      • 1.7 linsert
      • 1.8 llen
      • 1.9 lrem
      • 1.10 ltrim
      • 1.11 lset
      • 1.12 blpop 和 brpop
        • 原理
        • 使用
      • 小结
    • 二 . 内部编码
    • 5.3 应用场景
      • 5.3.1 作为 "数组" 这样的结构来存储多个元素
      • 5.3.2 消息队列
      • 5.3.3 微博的文章列表

Hello , 大家好 , 这个专栏给大家带来的是 Redis 系列 ! 本篇文章给大家讲解的是 Redis 中 list 类型的相关内容 , 我们会从常见命令、底层编码方式、应用场景三方面给大家介绍 . list 类型比较特殊的是 , list 提供了阻塞式的命令 , 这是我们第一次遇见 , 大家请耐心研究 .

在这里插入图片描述

本专栏旨在为初学者提供一个全面的 Redis 学习路径,从基础概念到实际应用,帮助读者快速掌握 Redis 的使用和管理技巧。通过本专栏的学习,能够构建坚实的 Redis 知识基础,并能够在实际学习以及工作中灵活运用 Redis 解决问题 .
专栏地址 : Redis 入门实践


正文开始

list 我们可以理解成我们之前学习过的数组 / 顺序表的结构
image.png
key 依然是 string 类型 , value 就是我们的列表类型了
此处我们约定最左侧元素下标为 0 , 另外 Redis 的下标也支持负数下标
那 list 虽然类似于数组的结构 , 但是他底层并不是由数组去实现的 , 而是通过一个双端队列来去维护的

那我们再来看一下 list 的特点

  1. 列表中的元素允许重复

我们之前学习过的 hash 类型 , field 是不能重复的 , 如果重复就会覆盖掉原有的 value
后续学到的集合以及有序集合也都是不能重复的

  1. list 的头部和尾部都能高效的插入和删除元素 , 那就可以把 list 当做一个 栈 / 队列 来使用

我们之前就介绍过 , Redis 有一个典型的应用场景就是作为消息队列

一 . 常见命令

1.1 lpush、lrange

lpush 的作用是将一个或者多个元素从左侧插入到 list 中 (头插法)
语法 : lpush key element1 [element2 …]

注意 : 我们依次插入 1 2 3 4 , 因为是头插法 , 那全部插入完毕之后 , 元素的顺序就是 4 3 2 1

返回值 : list 插入成功之后的 list 的长度
要注意 , 如果 key 已经存在 , 并且 key 对应的 value 类型并不是 list , 那使用 lpush 命令就会报错

Redis 中所有的这些数据结构的操作 , 都是这样的要求 , 都要求相对应的命令只能执行相对应的操作

lrange 的作用是查看 [start , end] 范围内的数据 (闭区间) , 下标也是支持负数的
语法 : lrange key start end

lrange key 0 -1 就是获取整个 list 的元素

127.0.0.1:6379> lpush key 1 2 3 4 # 头插法向 list 中插入元素
(integer) 4 # 返回值代表插入成功之后 list 的长度
127.0.0.1:6379> lpush key 5 6 7 8
(integer) 8
127.0.0.1:6379> lrange key 0 -1 # 获取 list 中所有元素
1) "8" # 头插法, 最后插入的 8 在最前面
2) "7"
3) "6"
4) "5"
5) "4"
6) "3"
7) "2"
8) "1"

那如果说我们给定了一个不合法的下标 , 那 Redis 会怎样处理呢 ? 报错 ?

127.0.0.1:6379> lrange key 0 10000000000 # 我们设置了一个不合法的下标
1) "8"
2) "7"
3) "6"
4) "5"
5) "4"
6) "3"
7) "2"
8) "1"

那我们通过这个结果可以看到 , Redis 的做法就是尽可能的获取到给定区间的元素 , 如果给定区间非法的话 , 就会尽可能的获取到对应的元素
那如果左右区间都不合法呢

127.0.0.1:6379> lrange key 10000 10000000000
(empty list or set)

那他尽可能的获取到对应的元素 , 但是他尽力了 , 所以就找不到返回空表的信息

1.2 lpushx

lpushx 要求 key 存在才能将一个或者多个元素头插到 list 中 . 如果 key 不存在 , 直接返回 0 代表插入失败 .
语法 : lpushx key element1 [element2 …]
时间复杂度 : O(1)
返回值 : 插入成功之后 list 的长度

127.0.0.1:6379> lrange key 0 -1 # 此时 key 是存在的
1) "8"
2) "7"
3) "6"
4) "5"
5) "4"
6) "3"
7) "2"
8) "1"
127.0.0.1:6379> lpushx key 9 10 11 12 # 存在才去插入
(integer) 12 # 返回值代表插入成功之后 list 的长度
127.0.0.1:6379> lpushx key2 1 2 3 4 # 不存在就会插入失败
(integer) 0
127.0.0.1:6379> lrange key 0 -1 # key 是插入成功的
 1) "12"
 2) "11"
 3) "10"
 4) "9"
 5) "8"
 6) "7"
 7) "6"
 8) "5"
 9) "4"
10) "3"
11) "2"
12) "1"
127.0.0.1:6379> lrange key2 0 -1 # key2 插入失败
(empty list or set)
127.0.0.1:6379> exists key2 # key2 也是不存在的
(integer) 0

1.3 rpush

将一个 / 多个元素从右侧插入 (尾插法)
语法 : rpush key element1 [element2 …]
时间复杂度 : O(1)
返回值 : 插入成功后的 list 的长度
注意 : 如果 key 存在才能插入成功

127.0.0.1:6379> rpush key 1 2 3 4 # 尾插法
(integer) 4 # 返回值代表插入成功之后 list 的长度
127.0.0.1:6379> rpush key 5 6 7 8 
(integer) 8
127.0.0.1:6379> lrange key 0 -1 # 查看 list 所有元素
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
8) "8"

那我们 lpush 对应 rpush , lpop 对应 rpop . 那 lrange 有没有对应的 rrange 呢 ?
注意 : lpush、lpop 中的 l 指的是 left , 而 lrange 中的 l 指的是 list 而不是 left , 所以 lrange 并没有相对的命令

1.4 rpushx

与我们刚才的 lpushx 类似 , rpushx 的作用也是尾插 , 只不过存在才会插入 , 不存在就会直接返回
语法 : rpushx key element1 [element2 …]
时间复杂度 : O(1)
返回值 : 插入成功之后 list 的长度

127.0.0.1:6379> rpushx key 9 10 11 12 # 存在才去尾插
(integer) 12
127.0.0.1:6379> lrange key 0 -1 # 9 10 11 12 尾插成功
 1) "1"
 2) "2"
 3) "3"
 4) "4"
 5) "5"
 6) "6"
 7) "7"
 8) "8"
 9) "9"
10) "10"
11) "11"
12) "12"
127.0.0.1:6379> rpushx key2 1 2 3 4 # 但是 key2 不存在, 就会尾插失败
(integer) 0
127.0.0.1:6379> lrange key2 0 -1 # 此时就获取不到 key2 的相关元素了
(empty list or set)
127.0.0.1:6379> exists key2 # key2 也不存在
(integer) 0

1.5 lpop、rpop

lpop 的作用是从 list 左侧取出元素 (头删)
语法 : lpop key
时间复杂度 : O(1)
返回值 : 如果列表中有元素 , 返回取出的元素 ; 如果没有元素 , 返回 nil

127.0.0.1:6379> rpush key 1 2 3 4 # 尾插法插入元素
(integer) 4
127.0.0.1:6379> lrange key 0 -1 # 获取列表中所有元素
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> lpop key # 头删
"1" # 返回值代表取出的元素
127.0.0.1:6379> lpop key
"2"
127.0.0.1:6379> lpop key
"3"
127.0.0.1:6379> lpop key
"4"
127.0.0.1:6379> lrange key 0 -1 # 此时所有元素已被弹出, 列表为空
(empty list or set)
127.0.0.1:6379> lpop key # 此时再去弹出元素, 就会返回 nil
(nil)

rpop 的作用是从 list 右侧取出元素 (尾删)
语法 : rpop key [count]

那相较于 lpop , rpop 这里增加了一个 count , 这是怎么回事 ?
lpop 和 rpop 在当前的 Redis 5.x 系列中 , 是没有 count 参数的 . count 参数是在 Redis 6.2 版本后引进的 , 表示这次要删除几个元素

127.0.0.1:6379> rpush key 1 2 3 4 # 尾插法插入元素
(integer) 4
127.0.0.1:6379> lpop key 4 # Redis 5.x 没有此命令
(error) ERR wrong number of arguments for 'lpop' command
127.0.0.1:6379> rpop key 4 # Redis 5.x 没有此命令
(error) ERR wrong number of arguments for 'rpop' command
127.0.0.1:6379> rpop key # 尾删法
"4"
127.0.0.1:6379> rpop key
"3"
127.0.0.1:6379> rpop key
"2"
127.0.0.1:6379> rpop key
"1"
127.0.0.1:6379> lrange key 0 -1 # 此时 list 中没有元素了
(empty list or set)
127.0.0.1:6379> rpop key # 再去尾删就会返回 nil
(nil)


那我们目前也清楚了 , Redis 是一个双端队列 , 从两侧插入 / 删除的时间复杂度都是 O(1) 的
所以我们就可以搭配 rpush 和 lpop 就相当于队列
搭配 rpush 和 rpop 就相当于栈了

1.6 lindex

lindex 用来获取从左数第 index 位置的元素

就相当于顺序表的 get 方法 , 比如 : list.get(i)

语法 : lindex key index
时间复杂度 : O(N) , N 指的是 list 中的元素个数

内部并不是完全按照数组进行实现的 , 所以时间复杂度并不是 O(1)

返回值 : 返回该位置的元素 ; 如果下标非法 , 返回的是 nil

127.0.0.1:6379> rpush key 1 2 3 4 5 6 7 8 # 尾插法插入元素
(integer) 8
127.0.0.1:6379> lindex key 3 # 获取 3 位置的元素
"4" # 返回值代表该位置的元素
127.0.0.1:6379> lindex key -1 # lindex 支持负数下标, -1 就代表最后一个元素
"8"
127.0.0.1:6379> lindex key 99 # 下标非法返回 nil
(nil)

1.7 linsert

在指定元素的位置插入元素 (不是按照下标添加元素)
语法 : linsert key <before | after> pivot element

<before | after> 指的是插入到 pivot 之前还是之后
pivot 表示插入到哪
element 表示插入啥

时间复杂度 : O(N) , N 为列表的长度

我们也需要遍历找到 pivot 的位置

返回值表示插入成功之后 list 的长度

127.0.0.1:6379> linsert key before 4 100 # 在 4 之前插入 100
(integer) 9 # 返回值表示插入成功之后 list 的长度
127.0.0.1:6379> lrange key 0 -1
1) "1"
2) "2"
3) "3"
4) "100" # 在 4 之前插入 100
5) "4"
6) "5"
7) "6"
8) "7"
9) "8"
127.0.0.1:6379> linsert key after 4 200 # 在 4 之后插入 200
(integer) 10
127.0.0.1:6379> lrange key 0 -1 
 1) "1"
 2) "2"
 3) "3"
 4) "100"
 5) "4"
 6) "200" # 在 4 之后插入 200
 7) "5"
 8) "6"
 9) "7"
10) "8"

那我们也介绍了 , 它是按照具体的元素来去决定在前面还是在后面插入的 , 他并不是根据下标来去插入的
那假如我们的 list 中有重复元素 , 这该怎么搞 ?

127.0.0.1:6379> rpush key 1 2 3 4 5 6 7 8 4 # 此时 list 中有两个 4
(integer) 9
127.0.0.1:6379> lrange key 0 -1
1) "1"
2) "2"
3) "3"
4) "4" # 第一个 4
5) "5"
6) "6"
7) "7"
8) "8"
9) "4" # 第二个 4
127.0.0.1:6379> linsert key before 4 300 # 在元素 4 之前插入 300
(integer) 10
127.0.0.1:6379> lrange key 0 -1
 1) "1"
 2) "2"
 3) "3"
 4) "300" # 那插入到了第一个 4 的前面
 5) "4"
 6) "5"
 7) "6"
 8) "7"
 9) "8"
10) "4"

通过这个例子 , 我们能看出来 , linsert 进行插入的时候 , 要根据给定的具体元素找到对应的位置 , 他是从左往右找的 , 找到第一个符合基准值的位置即可

1.8 llen

它的作用是获取列表的长度
语法 : llen key

127.0.0.1:6379> lrange key 0 -1
 1) "1"
 2) "2"
 3) "3"
 4) "300"
 5) "4"
 6) "5"
 7) "6"
 8) "7"
 9) "8"
10) "4"
127.0.0.1:6379> llen key
(integer) 10

1.9 lrem

lrem 的作用是删除元素

rem -> remove

语法 : lrem key count element

count 表示要删除的个数
element 表示要删除的值

时间复杂度 : O(N + M) , N 指的是 list 的长度 , M 指的是现在有多少个元素要被删除 (也就是 count)
那我们再来看 count :

  • count > 0 : 从左往右找 , 删除 count 个 element
  • count < 0 : 从右往左找 , 删除 count 个 element
  • count == 0 : 删除所有 element

比如 : list 有 16 个元素 , 为 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4

  • count 为 2 , element 为 1 : 比如 lrem key 2 1 , 那就代表从左往右找 2 个 1 进行删除 , 得到的结果就是 2 3 4 2 3 4 1 2 3 4 1 2 3 4
  • count 为 -2 , element 为 1 : 比如 lrem key -2 1 , 那就代表从右往左找 2 个 1 进行删除 , 得到的结果就是 1 2 3 4 1 2 3 4 2 3 4 2 3 4
  • count 为 0 , element 为 1 : 比如 lrem key 0 1 , 那就代表删除所有 1 , 得到的结果就是 2 3 4 2 3 4 2 3 4 2 3 4

我们具体通过命令来看一眼

127.0.0.1:6379> rpush key 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 # 构造出四组 1 2 3 4
(integer) 16
# count > 0 -> 从左往右删除 count 个 element
127.0.0.1:6379> lrem key 2 1 # 从左往右删除 2 个 1
(integer) 2 # 返回值代表删除成功的个数
127.0.0.1:6379> lrange key 0 -1 
 1) "2" # 第一组的 1 被删除了
 2) "3"
 3) "4"
 4) "2" # 第二组的 1 被删除了
 5) "3"
 6) "4"
 7) "1"
 8) "2"
 9) "3"
10) "4"
11) "1"
12) "2"
13) "3"
14) "4"
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> rpush key 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4
(integer) 16
# count < 0 -> 从右往左删除 count 个 element
127.0.0.1:6379> lrem key -2 1  # 从右往左删除 2 个 1
(integer) 2
127.0.0.1:6379> lrange key 0 -1
 1) "1"
 2) "2"
 3) "3"
 4) "4"
 5) "1"
 6) "2"
 7) "3"
 8) "4"
 9) "2" # 第三组的 1 被删了
10) "3"
11) "4"
12) "2" # 第四组的 1 被删了
13) "3"
14) "4"
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> rpush key 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4
(integer) 16
# count == 0 -> 删除所有 element
127.0.0.1:6379> lrem key 0 1 # 删除所有的 1
(integer) 4
127.0.0.1:6379> lrange key 0 -1
 1) "2" # 第一组的 1 被删除了
 2) "3"
 3) "4"
 4) "2" # 第二组的 1 被删除了
 5) "3"
 6) "4"
 7) "2" # 第三组的 1 被删了
 8) "3" 
 9) "4"
10) "2" # 第四组的 1 被删了
11) "3"
12) "4"

1.10 ltrim

ltrim 也是删除元素 , 他可以删除指定范围区间之外的元素
语法 : ltrim key start stop
注意 : 他是保留 start 和 stop 之间区间内的元素 , 区间外的两侧的元素就直接被删除了
时间复杂度 : O(N) , N 指的是要删除的元素个数

127.0.0.1:6379> rpush key 1 2 3 4 5 6 7 8 # 尾插法插入元素
(integer) 8
127.0.0.1:6379> ltrim key 2 5 # 只保留 [2,5] 之间的元素
OK
127.0.0.1:6379> lrange key 0 -1 # [2,5] 下标之外的元素就被删除了
1) "3"
2) "4"
3) "5"
4) "6"

1.11 lset

与之前的 lindex 类似 , lset 的作用是根据下标来去修改元素
语法 : lset key index element
时间复杂度 : O(N)

需要找到对应的下标然后再去修改元素

127.0.0.1:6379> lset key 2 666 # 修改 2 号下标为 666
OK
127.0.0.1:6379> lrange key 0 -1
1) "1"
2) "2"
3) "666" # 2 号下标就被修改成了 666
4) "4"
5) "5"
6) "6"
7) "7"
8) "8"

如果下标不合法会直接报错

127.0.0.1:6379> lset key 222 666
(error) ERR index out of range

1.12 blpop 和 brpop

原理

blpop 和 brpop 是阻塞版本的弹出元素

阻塞就是当前的线程干不了活了 , 不能继续往下执行了 . 只有满足一定条件之后才能被唤醒

那 Redis 中的 list 也相当于阻塞队列 , 但是 Redis 中只需要考虑队列为空的情况 , 不需要考虑队列满的情况
也就是如果 list 中存在元素 , blpop、brpop 就和 lpop、rpop 作用完全相同 ;
image.png
如果 list 为空 , blpop 和 brpop 就会产生阻塞 , 一直阻塞到队列不空为止 .
image.png
那关于 blpop 和 brpop , 我们还需要额外关注这几点

  1. 使用 blpop 和 brpop 的时候 , 我们是可以显式的设置阻塞时间的 , 不一定是无休止的等待 , 在这期间 Redis 可以执行其他的命令

此处的 blpop 和 brpop 实际上不会对 Redis 服务器产生太大的影响 , 是被特殊处理过的

  1. 命令中如果设置了多个 key , 那么会从左向右进行遍历 , 一旦有某一个 key 对应的列表中被添加了元素 , 那这个 key 对应的列表就可以弹出元素 , 则命令立即返回

也就是 blpop 和 brpop 都是可以同时去尝试获取多个 key 的列表的元素的 , 也就是多个 key 会对应多个 list , 这几个 key 中哪个 list 有元素了 , 就会返回哪个元素

  1. 如果多个客户端同时对一个键进行 pop 操作 , 那最先执行到命令的客户端就会先得到弹出的元素
使用

blpop 的语法 : blpop key1 [key2 …] timeout

此处可以指定多个 key , 每个 key 都对应一个 list

  • 如果这些 list 有任何一个非空 , 那就会把这个 list 中的元素给获取到 , 就会立即返回
  • 如果这些 list 都为空 , 此时就需要阻塞等待 , 等待其他客户端往这些 list 中插入元素了

我们还可以指定超时时间 , 单位是 s (Redis 6 中超时时间允许设置成小数)

首先 , 我们针对一个非空的列表进行操作
blpop 模拟非空列表.png

127.0.0.1:6379> rpush key 1 2 3 4
(integer) 4
127.0.0.1:6379> blpop key 0 # 设置超时时间为 0
# 返回结果是一对
1) "key" # 当前数据来自于哪个 key
2) "1" # 取到的数据是什么

针对一个空列表进行操作

127.0.0.1:6379> rpush key 1 2 3 4
(integer) 4
127.0.0.1:6379> del key
(integer) 1
127.0.0.1:6379> blpop key 100

此时我们也能看到光标正在闪烁
image.png
这时候就需要我们额外的客户端往 key 中添加数据 , 当我们其他客户端往 list 中添加数据之后 , 左侧被阻塞的客户端立马就通了
那左边的客户端就返回了新的结果

127.0.0.1:6379> blpop key 100
1) "key"
2) "1"
(14.23s)

blpop 模拟空列表.png

针对多个 key 进行操作
客户端 1

127.0.0.1:6379> flushdb # 此时我们清空了数据库
OK
127.0.0.1:6379> blpop key key2 key3 key4 500 # 那现在 key key2 key3 key4 都不存在, 就会在这里进行阻塞


当我们其他的客户端去创建 list 并且插入元素的时候

# key key2 key3 key4 都不存在
127.0.0.1:6379> lrange key 0 -1
(empty list or set)
127.0.0.1:6379> lrange key2 0 -1
(empty list or set)
127.0.0.1:6379> lrange key3 0 -1
(empty list or set)
127.0.0.1:6379> lrange key4 0 -1
(empty list or set)
# 此时我们创建一个 list, 客户端 1 就会立即返回数据

左侧的客户端会立马感知到数据并且返回
blpop 针对多个 key 进行操作.png

还有一种多个客户端同时监听一个 key 的情况 , 我们不太好进行模拟 , 就不演示了


那这两个命令主要的用途就是来作为 “消息队列” , 但是这两个命令功能还是比较有限 , 就比较鸡肋了

小结

在这里插入图片描述

二 . 内部编码

列表类型的内部编码有两种

  • ziplist (压缩列表) : 把数据按照更紧凑的压缩形式来表示的 , 它的特点就是节省空间 , 但是如果元素过多 , 操作数据效率就会降低
  • linkedlist (链表) : 当元素过多 , 就会从 ziplist 升级成链表

但是这是老版本 Redis 的实现 , 现在主要采用的是 quicklist (quicklist = ziplist + linkedlist)
quicklist 整体还是一个链表 , 但是链表的每个节点都是一个压缩列表 , 它的特点就是每个压缩列表都不会让他太大 , 同时再把多个压缩列表通过链式结构压缩起来
quicklist 底层实现.png

我们可以看一下 quicklist 的底层配置
image.png
他表示我们每个压缩列表体积设置成多少合适 , 也就是说我们压缩列表体积到达一定程度就会分裂成多个压缩列表
那他也给出了我们几个档位 , -2 代表 8 kb , 也就是说压缩列表达到 8 kb 就会进行分裂

5.3 应用场景

5.3.1 作为 “数组” 这样的结构来存储多个元素

比如我们有这样的一个场景 : 学生和班级信息
那在 MySQL 中 , 是这样处理的
stu (stuId , stuName , age , score , classId)
class (classId , className)
那在这样的表结构中 , 就很容易查询出指定班级中有哪些同学

但是在 Redis 中查询数据并没有 MySQL 那样灵活 , 他的处理方式是这样的

在这里插入图片描述

那我们还需要指定学生和班级的关系

classStudents:1[1,2]
classStudents:2[1]

Redis 查询数据只能通过 key 去查询 value , 所以就需要我们将班级信息构造成 key , 班级成员构造成 value
具体 Redis 中的数据如何组织 , 都需要按照业务具体来去决定

5.3.2 消息队列

虽然 Redis 作为消息队列比较少见 , 但是这也是 Redis 实现的初衷之一
那在消息队列中最核心的概念就是 “生产者-消费者” 模型
image.png
生产者就可以使用 lpush 添加元素 , 消费者就可以使用 brpop 来去获取元素

brpop 是阻塞的操作 , 当列表为空的时候 , brpop 就会阻塞等待 , 需要一直等到其他客户端添加元素

那多个消费者之间 , 谁先执行 brpop , 谁就能获取到新添加的元素
那根据这个特性 , 就能实现一个 “轮询” 的效果

假设消费者执行 brpop 命令的顺序为 1 2 3 , 那当新元素到达之后 , 首先是消费者 1 获取到元素 , 然后他就会从 brpop 中返回了 (相当于这个命令就执行完了) , 如果消费者 1 还想继续消费 , 就需要重新执行 brpop
此时再来一个新元素 , 那就是消费者 2 会获取到新的元素
那这样就构成了一个 “轮询” 的效果

那这样实现的消息队列 , 是比较简陋的 , 再给大家介绍一种 “分频道” 的消息队列
image.png
我们 Redis 中存储了多个列表 , 同时也具有多个消费者
那这种场景也非常常见 , 比如我们日常使用的一些程序 , 比如我们刷 B 站 , 我们既可以看视频 , 还可以看弹幕
那就有一个通道 , 来去传输短视频数据 , 还可以有一个通道来传输弹幕 , 还可以有频道来去传输其他的信息 , 比如 : 点赞、评论 …
那我们搞多个频道 , 就可以在某种数据发生问题的时候 , 不会影响到其他频道 , 这也就是解耦合 .

那根据 Redis 来说 , 每个列表就可以认为是一个频道 , 那我们的消费者也可以从多个列表中读取数据

brpop 是可以读取多个 key 的

5.3.3 微博的文章列表

每个用户都有自己的 Timeline , 也就是微博列表 , 用户可以看到自己什么时候发了什么微博
那他的实现原理跟场景一一样 , 都是将 list 当做数组使用了
那我们可以模拟一下微博是怎样存储用户数据的
那每篇微博使用哈希结构去存储 , 比如我们存储三个属性 : title、timestamp、content

hmset mblog:1 title xx timestamp 1314520 content xxx
...
hmset mblog:n title xx timestamp 521521 content xxx

那用户添加微博 , 我们使用 user::mblogs 作为微博的键

# value 使用上面的 key, 也就是类似 mblog:1 这样的结构
lpush user:1:mblogs mblog:1 mblog:3 # 代表插入了 1 号文章和 3 号文章
...
lpush user:k:mblogs mblog:9

那我们就能通过 mblog:1 来去 hash 中查询文章的具体内容了
那我们要想实现分页 , 也很简单 , 使用 lrange 指定区间范围即可

keylist = lrange user:1:mblogs 0 9 # 获取 [0,9] 之间的文章, 也就是获取前 10 篇文章
for key in keylist {
	hgetall key
}

但是这种方案可能存在两个问题

  1. 如果每次分页获取的微博个数较多 , 那就需要执行多次 hgetall 命令 , 就涉及到许多次网络交互了

image.png
那我们解决策略是使用 “流水线” (“管道”) , 虽然我们是多个 hgetall 命令 , 但是使用管道就可以把这些命令合并成一个请求进行网络通信 , 这就会大大降低客户端和服务器之间的交互次数

  1. lrange 获取列表两端元素效率较高 , 但是获取列表中间的元素表现就会很差

意思就是我们的 lrange 需要传入两个下标作为检索的区间 , 那这两个下标如果在列表两端 , 直接从链表头部 / 链表尾部遍历就好 , 但是如果在中间 , 那就需要花更多的时间

那我们解决的策略就是对用户的文章进行切分 , 假如一个用户发了 1w 个微博 , 那 list 的长度就是 1w , 那就可以把这 1w 个微博拆成 10 份 , 每份就是 1k , 这样虽然业务层代码实现可能复杂一点 , 但是就能够解决一定的效率问题


那今天 list 类型的分享就结束了 , 如果对你有帮助的话还请一键三连~
在这里插入图片描述

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

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

相关文章

Spring高手之路22——AOP切面类的封装与解析

文章目录 1. AOP是如何收集切面类并封装的&#xff1f;2. Advisor 是什么&#xff0c;它是怎么构建的&#xff1f;2.1 什么是 Advisor2.2 Advisor 的构建&#xff08;源码分析时序图说明&#xff09; 3. TargetSource 的构建和作用3.1 TargetSource 的作用3.2 TargetSource 的构…

java实现,PDF转换为TIF

目录 ■JDK版本 ■java代码・实现效果 ■POM引用 ■之前TIF相关的问题&#xff08;两张TIF合并&#xff09; ■问题 ■问题1&#xff1a;无法生成TIF&#xff0c;已解决 ■问题2&#xff1a;生成的TIF过大&#xff0c;待解决 ■相关工具 SVF foxit ■相关知识 Imag…

Fastjson1.2.24(CVE-2017-18349)分析

前言 Fastjson在1.2.24版本之前存在两条利用链&#xff0c;分别是 JNDI com.sun.rowset.JdbcRowSetImplcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 我们本次也是对这两条链进行分析和过程复现 在fastjson漏洞中&#xff0c;我们往往是寻找一个类&#xff0…

【苍穹外卖】Day 5 Redis、店铺营业状态接口

1 基本介绍 Redis是一个基于 内存 的 key-value 结构数据库 基于内存存储&#xff0c;读写性能高适合存储热点数据(热点商品、资讯、新闻)企业应用广泛 运行 在cmd下 redis-server.exe redis.windows.conf 启动状态下&#xff0c;再 redis-cli.exe 测试&#xff1a; 也可以…

Win32远线程注入

远线程注入 远线程(RemoteThread)注入是指一个进程在另一个进程中创建线程的技术&#xff0c;这是一种很经典的DLL注入技术。 虽然比较古老&#xff0c;但是很实用。通过远线程注入&#xff0c;再配合api函数的hook技术&#xff0c;可以实现很多有意思的功能。 实现远线程注入…

自己封装栈和队列

队列 #include <iostream>using namespace std; class queue { private:int *data;int size;int front;int tail;public://无参构造queue():size(20){datanew int [size];front0;tail0;}//有参构造queue(int s){datanew int [size];sizes;front0;tail0;}~queue(){delete …

MySQL三大日志详解

binlog相关 bin log是什么?作用是什么呢? 答: bin log实际上是一个物理日志,当我们对某个数据页进行修改操作时我们就会将这个操作写到bin log中,当我们数据库需要进行主备、主从复制等操作时,都可以基于bin log保证数据一致性。 那bin log缓冲区了解嘛? 答: 如下图…

硬件产品经理进阶:产品层次划分的3个方法

目录 1、内容简介 2、产品三层次概念 3、产品四层次概念 4、产品五层次概念 作者简介 1、内容简介 产品本身指的是能够满足需求和欲望的一种媒介物。 可以是实体、也可以是虚拟的服务。 在产品竞争白热化的今天&#xff0c; 如果只是考虑把产品做出来、 仅仅在实际产…

【微信小程序入门】3、微信小程序开发基础及微信开发者工具的使用

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

vue2———组件

一个简单的组件 组件进行注册并使用 结果&#xff1a; 在进行对组件的学习时遇见一些问题&#xff1a; 1、组件的命名 解决方法&#xff1a; 组件的命名 Vue.js 组件的命名遵循一些最佳实践&#xff0c;这些实践有助于保持代码的清晰和一致性。 多单词命名&#xff1a;Vue 官…

【网络安全】Collabora在线存储型XSS(CVE-2024-29182)+代码审计

未经许可,不得转载。 文章目录 前言正文代码审计前言 Collabora 是一家专注于开源软件的公司,主要提供与文档协作、办公套件和企业解决方案相关的服务。 Collabora 提供了 Collabora Online,这是一个基于 Web 的在线办公套件,允许用户在浏览器中实时编辑文档。这种服务特…

Two to One——C语言提高题【7 kyu】

一、原题 链接&#xff1a;Training on Two to One | Codewars Take 2 strings s1 and s2 including only letters from a to z. Return a new sorted string (alphabetical ascending), the longest possible, containing distinct letters - each taken only once - coming…

继承:复杂的菱形继承与虚继承

目录 前言 复杂的菱形继承及菱形虚拟继承 继承方式 virtual关键字 虚拟继承的原理 原理&#xff1a; 额外消耗&#xff1a; 构造顺序为什么是ABCD 不允许使用间接非虚拟基类原理 假设只有A B 为什么virtual加在B C中而不是D中&#xff1f; 如何实现一个不能被继承的类…

AtCoder ABC 359 F 题解

本题要看出性质并进行验证&#xff0c;程序难度低。&#xff08;官方 Editorial 似乎没有写证明过程&#xff1f;难道是过于显而易见了吗…&#xff09; 题意 给你一个数组 a a a&#xff0c;对于一棵 n n n 个节点的树 T T T&#xff0c; d i d_i di​ 为每个节点的度&am…

基于Java+SpringBoot+Vue的校园社团信息管理

基于JavaSpringBootVue的校园社团信息管理 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;&公&粽&号 查找《智能编…

C++ 栈和队列的简单封装(9.3)

1.栈的封装 代码 #include <iostream>using namespace std;typedef int datatype; class Stack { private:datatype *data;int max_size; //栈的大小int the_top; //栈顶 public:Stack(){data new int[50];max_size 50;the_top -1;}Stack(int a){data n…

一个好用的Maven依赖冲突解决插件:Maven Helper

在项目开发&#xff0c;或项目Maven需要新增依赖、项目依赖组件升级时&#xff0c;经常会出现添加后&#xff0c;因为各个模块中有相同的依赖、不同的版本而导致依赖冲突&#xff0c;从而导致项目启动不起来&#xff0c;这种冲突非常恶心&#xff0c;因为是传递依赖所以会看不出…

【数据推荐】我国省市县三级的人口受教育状况数据(分年龄\性别\户籍)

人口数据是我们在各项研究中都经常使用的数据。之前我们为大家分享过基于《2020中国人口普查分县资料》整理的全国范围的第七次人口普查人口数据&#xff0c;具体包括如下8个分表&#xff08;均可查看之前的文章获悉详情&#xff09;&#xff1a; 表1&#xff1a;我国省市县三…

大二暑假去龙旗科技(上海)做了两个月软件测试实习生,讲讲我的经历和感受

目录 1.为什么选择软件测试 2.入职&#xff0c;辞职流程以及实习工作内容 3.行业选择和个人感悟 新的学期开始了兄弟们&#xff0c;我也已经断更几个月了&#xff0c;这几个月我并没有摆烂&#xff0c;我选择了备考蓝桥杯&#xff0c;复习期末&#xff0c;暑假出去实习。结果…

SpringDataJPA系列(5)@Query应该怎么用?

SpringDataJPA系列(5)Query应该怎么用&#xff1f; 之前说到过&#xff0c;DMQ查询策略有两种&#xff1a;方法命令和Query注解的方式。为什么需要两种呢&#xff1f;它们分别适用的场景是怎么样的&#xff1f; Query使用 定义一个通过名字查询用户的方法 以下是测试方法&…