【Redis】列表与集合Day4(2026年)

写在前面

List和Set是Redis中处理序列和集合数据的利器。List的队列特性让它成为消息队列的首选,而Set的集合运算能力让社交功能开发变得简单。今天我们深入探讨这两种类型的实战技巧。

文章目录


一、List命令详解

1.1 List的特点与底层实现

实际场景:List就像一个可以两头操作的管道,非常适合做队列和栈。

List是Redis的列表类型,具有以下特点:

  • 有序:元素按插入顺序排列
  • 可重复:允许重复元素
  • 双向操作:支持左右两端插入和弹出

底层实现

  • 元素少时使用ziplist(压缩列表)
  • 元素多时使用quicklist(双向链表+ziplist)

1.2 插入命令

redis 复制代码
# 左侧插入(头部)
lpush queue:task "task1"

# 左侧插入多个元素
lpush queue:task "task2" "task3" "task4"

# 右侧插入(尾部)
rpush queue:task "task5"

# 右侧插入多个元素
rpush queue:task "task6" "task7"

# 在指定元素前插入
linsert queue:task before "task3" "task2.5"

# 在指定元素后插入
linsert queue:task after "task3" "task3.5"

注意事项

  • LPUSH/RPUSH可以一次插入多个元素
  • LINSERT需要遍历列表,时间复杂度O(n)

1.3 弹出命令

redis 复制代码
# 左侧弹出
lpop queue:task

# 右侧弹出
rpop queue:task

# 阻塞式左侧弹出(等待30秒)
blpop queue:task 30

# 阻塞式右侧弹出(等待30秒)
brpop queue:task 30

# 弹出并插入到另一个列表
rpoplpush queue:task queue:backup

# 阻塞式弹出并插入
brpoplpush queue:task queue:backup 30

注意事项

  • LPOP/RPOP如果列表为空返回nil
  • BLPOP/BRPOP是阻塞操作,会一直等待直到有元素或超时
  • RPOPLPUSH常用于消息队列的可靠性处理

1.4 查询命令

redis 复制代码
# 获取列表长度
llen queue:task

# 获取指定范围元素(0到-1表示全部)
lrange queue:task 0 -1

# 获取前3个元素
lrange queue:task 0 2

# 获取最后3个元素
lrange queue:task -3 -1

# 通过索引获取元素
lindex queue:task 0

# 获取第一个元素
lindex queue:task 0

# 获取最后一个元素
lindex queue:task -1

1.5 修改命令

redis 复制代码
# 设置指定索引的值
lset queue:task 0 "new_task1"

# 只保留指定范围的元素
ltrim queue:task 0 9

# 删除指定值的元素
lrem queue:task 1 "task1"

LREM参数说明

参数值 说明
count > 0 从头开始删除count个匹配元素
count < 0 从尾开始删除
count = 0 删除所有匹配元素

二、List应用场景

2.1 消息队列

经验之谈:List做消息队列简单可靠,但功能有限。复杂场景建议用专业的消息中间件。

redis 复制代码
# 生产者:左侧插入消息
lpush queue:order "order_data_1"

# 消费者:右侧阻塞弹出消息
brpop queue:order 30

# 可靠消费:弹出并备份
brpoplpush queue:order queue:processing 30

消息队列模式对比

模式 命令组合 特点
简单队列 LPUSH + RPOP 先进先出
栈模式 LPUSH + LPOP 后进先出
可靠队列 LPUSH + BRPOPLPUSH 失败可恢复
多消费者 LPUSH + BRPOP 多个消费者竞争

2.2 最新列表

redis 复制代码
# 添加最新文章(左侧插入)
lpush latest:articles "article:1001"

# 只保留最新10篇
ltrim latest:articles 0 9

# 获取最新文章列表
lrange latest:articles 0 9

2.3 时间线/动态

redis 复制代码
# 发布动态
lpush timeline:user:1001 "post:2001"

# 获取用户时间线
lrange timeline:user:1001 0 20

# 分页获取
lrange timeline:user:1001 0 9   # 第1页
lrange timeline:user:1001 10 19 # 第2页

2.4 分页查询

redis 复制代码
# 假设每页10条
# 第1页:索引0-9
lrange list:data 0 9

# 第2页:索引10-19
lrange list:data 10 19

# 第N页:索引(N-1)*10 到 N*10-1
lrange list:data 20 29

三、Set命令详解

3.1 Set的特点与底层实现

实际场景:Set的去重和集合运算能力,让社交功能开发变得简单。

Set是Redis的集合类型,具有以下特点:

  • 无序:元素没有固定顺序
  • 不重复:自动去重
  • 支持集合运算:交集、并集、差集

底层实现

  • 元素少且都是整数时使用intset
  • 否则使用hashtable

3.2 基础操作命令

redis 复制代码
# 添加元素
sadd tags:article:1 "redis" "database" "nosql"

# 添加单个元素
sadd tags:article:1 "cache"

# 获取所有元素
smembers tags:article:1

# 检查元素是否存在
sismember tags:article:1 "redis"

# 移除元素
srem tags:article:1 "nosql"

# 获取集合大小
scard tags:article:1

3.3 随机操作命令

redis 复制代码
# 随机获取一个元素(不移除)
srandmember tags:article:1

# 随机获取多个元素
srandmember tags:article:1 3

# 随机弹出元素(移除)
spop tags:article:1

# 随机弹出多个元素
spop tags:article:1 2

3.4 集合运算命令

redis 复制代码
# 准备测试数据
sadd set:a 1 2 3 4 5
sadd set:b 3 4 5 6 7
sadd set:c 5 6 7 8 9

# 交集:同时属于多个集合的元素
sinter set:a set:b
# 返回:3 4 5

# 并集:所有集合的元素合并
sunion set:a set:b
# 返回:1 2 3 4 5 6 7

# 差集:第一个集合有但其他集合没有的元素
sdiff set:a set:b
# 返回:1 2

# 交集并存储
sinterstore set:a_b set:a set:b

# 并集并存储
sunionstore set:a_b_union set:a set:b

# 差集并存储
sdiffstore set:a_b_diff set:a set:b

集合运算图解

运算 说明 命令
交集 A ∩ B SINTER
并集 A ∪ B SUNION
差集 A - B SDIFF

3.5 迭代命令

redis 复制代码
# 迭代获取元素(适合大集合)
sscan tags:article:1 0 match "re*" count 10

四、Set应用场景

4.1 标签系统

实际场景:文章标签、用户标签、商品标签,Set都能轻松搞定。

redis 复制代码
# 文章添加标签
sadd article:1001:tags "redis" "database" "nosql"

# 获取文章所有标签
smembers article:1001:tags

# 标签下的文章
sadd tag:redis:articles 1001 1002 1003

# 获取标签下的所有文章
smembers tag:redis:articles

# 检查文章是否有某标签
sismember article:1001:tags "redis"

4.2 共同好友/共同关注

redis 复制代码
# 用户关注列表
sadd user:1001:following 1002 1003 1004 1005
sadd user:1002:following 1003 1004 1006 1007

# 共同关注
sinter user:1001:following user:1002:following
# 返回:1003 1004

# 我关注的人中,谁关注了他
sinter user:1001:following user:1002:followers

# 可能认识的人(他关注但我没关注的人)
sdiff user:1002:following user:1001:following
# 返回:1006 1007

4.3 唯一性判断

redis 复制代码
# 投票去重
sadd vote:article:1001 user:1001
sadd vote:article:1001 user:1002
sadd vote:article:1001 user:1001  # 自动去重

# 检查是否已投票
sismember vote:article:1001 user:1001

# 获取投票人数
scard vote:article:1001

# 签到统计
sadd sign:20240101 user:1001 user:1002 user:1003
sadd sign:20240102 user:1001 user:1002 user:1004

# 连续签到用户
sinter sign:20240101 sign:20240102

4.4 抽奖系统

redis 复制代码
# 添加抽奖用户
sadd lottery:20240101 user:1001 user:1002 user:1003 user:1004 user:1005

# 随机抽取1个中奖用户(不移除)
srandmember lottery:20240101

# 随机抽取3个中奖用户
srandmember lottery:20240101 3

# 抽取并移除(不可重复中奖)
spop lottery:20240101

4.5 社交推荐

redis 复制代码
# 用户A的好友
sadd friends:a 1002 1003 1004

# 用户B的好友
sadd friends:b 1003 1004 1005

# 共同好友
sinter friends:a friends:b

# 推荐好友(B的好友中,A不认识的人)
sdiff friends:b friends:a

五、踩坑提醒:List阻塞操作

踩坑提醒:阻塞操作使用不当会导致连接超时或资源浪费!

5.1 BLPOP/BRPOP注意事项

问题1:长时间阻塞

redis 复制代码
# 阻塞时间设置为0表示永久等待
blpop queue:task 0  # 危险!可能导致连接永久挂起

# 建议设置合理的超时时间
blpop queue:task 30  # 30秒超时

问题2:多列表阻塞顺序

redis 复制代码
# 按顺序检查列表,返回第一个非空列表的元素
blpop queue:high queue:low 30
# 如果queue:high有元素,优先返回

问题3:客户端断开

  • 阻塞期间客户端断开,Redis会自动取消阻塞
  • 应用层需要处理重连逻辑

5.2 大列表操作

问题:LRANGE获取大量元素

redis 复制代码
# 危险!获取全部元素可能阻塞Redis
lrange big:list 0 -1

# 建议:分批获取
lrange big:list 0 99
lrange big:list 100 199

问题:LREM删除大量元素

redis 复制代码
# 删除所有匹配元素,时间复杂度O(n)
lrem big:list 0 "value"

# 建议:使用LTRIM或重建列表

5.3 大集合操作

问题:SMEMBERS获取大量元素

redis 复制代码
# 危险!返回所有元素可能阻塞Redis
smembers big:set

# 建议:使用SSCAN迭代获取
sscan big:set 0 count 100

问题:集合运算结果过大

redis 复制代码
# 大集合交集可能很慢
sinter big:set:a big:set:b

# 建议:先计算小集合的交集,再判断

六、List与Set对比

6.1 功能对比

对比项 List Set
有序性 有序 无序
重复性 可重复 不重复
随机访问 支持(LINDEX) 不支持
范围查询 支持(LRANGE) 不支持
集合运算 不支持 支持
阻塞操作 支持 不支持

6.2 场景选择

场景 推荐 理由
消息队列 List 支持阻塞弹出
最新列表 List 有序,支持范围查询
分页 List 支持索引访问
去重 Set 自动去重
标签 Set 无需顺序,支持集合运算
共同好友 Set 支持交集运算
抽奖 Set 支持随机获取

七、面试高频考点

Q1:List如何实现消息队列?

答案

  1. 简单队列:LPUSH + RPOP(先进先出)
  2. 可靠队列:LPUSH + BRPOPLPUSH(失败可恢复)
  3. 多消费者:LPUSH + BRPOP(竞争消费)

注意事项:

  • 消息持久化需要开启AOF或RDB
  • 消息确认需要业务层实现
  • 复杂场景建议用专业消息队列(RabbitMQ、Kafka)

Q2:Set的集合运算时间复杂度是多少?

答案

  • SINTER:O(N*M),N是最小集合大小,M是集合数量
  • SUNION:O(N),N是所有集合元素总数
  • SDIFF:O(N),N是所有集合元素总数

优化建议:

  • 先计算小集合
  • 使用SINTERSTORE缓存结果

Q3:如何实现一个简单的抽奖系统?

答案

redis 复制代码
# 添加参与用户
sadd lottery:20240101 user:1001 user:1002 user:1003

# 抽取中奖者(不可重复中奖)
spop lottery:20240101

# 抽取多个中奖者
spop lottery:20240101 3

# 抽取中奖者(可重复中奖)
srandmember lottery:20240101

八、参考资料

  1. Redis Set命令文档
  2. Redis数据结构详解

九、互动话题

你在项目中用List做过消息队列吗?遇到过什么问题?或者用Set实现过有趣的社交功能?欢迎在评论区分享你的实战经验!

下一篇我们将深入探讨ZSet和Bitmap的高级用法。

相关推荐
AOwhisky1 小时前
Ceph系列第三期:Ceph 集群核心配置与管理
linux·运维·数据库·笔记·ceph
techdashen1 小时前
Rust 中的小字符串:smol_str 与 smartstring 的对决
开发语言·后端·rust
陈天伟教授1 小时前
安装 AutoCAD 时,“可选工具“ 的详细说明。
数据库
zcn1261 小时前
举一反三思路思考形如(列=参数 or decode函数)
数据库·sql优化改写
Devin~Y1 小时前
从内容社区到AIGC客服:Spring Boot、Redis、Kafka、K8s、RAG的三轮大厂Java面试对话(附标准答案)
java·spring boot·redis·spring cloud·kafka·kubernetes·micrometer
Xzh04231 小时前
Redis黑马点评 实战复盘与面试高频考点详解
java·数据库·redis·面试
古韵2 小时前
从 Axios 到 alova:一个页面从 80 行到 5 行的故事
前端·后端
Master_Azur2 小时前
JavaEE之反射、注解、代理设计模式
后端
林的快手2 小时前
MySQL
数据库·oracle