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

文章目录
-
- 写在前面
- 一、List命令详解
-
- [1.1 List的特点与底层实现](#1.1 List的特点与底层实现)
- [1.2 插入命令](#1.2 插入命令)
- [1.3 弹出命令](#1.3 弹出命令)
- [1.4 查询命令](#1.4 查询命令)
- [1.5 修改命令](#1.5 修改命令)
- 二、List应用场景
-
- [2.1 消息队列](#2.1 消息队列)
- [2.2 最新列表](#2.2 最新列表)
- [2.3 时间线/动态](#2.3 时间线/动态)
- [2.4 分页查询](#2.4 分页查询)
- 三、Set命令详解
-
- [3.1 Set的特点与底层实现](#3.1 Set的特点与底层实现)
- [3.2 基础操作命令](#3.2 基础操作命令)
- [3.3 随机操作命令](#3.3 随机操作命令)
- [3.4 集合运算命令](#3.4 集合运算命令)
- [3.5 迭代命令](#3.5 迭代命令)
- 四、Set应用场景
-
- [4.1 标签系统](#4.1 标签系统)
- [4.2 共同好友/共同关注](#4.2 共同好友/共同关注)
- [4.3 唯一性判断](#4.3 唯一性判断)
- [4.4 抽奖系统](#4.4 抽奖系统)
- [4.5 社交推荐](#4.5 社交推荐)
- 五、踩坑提醒:List阻塞操作
-
- [5.1 BLPOP/BRPOP注意事项](#5.1 BLPOP/BRPOP注意事项)
- [5.2 大列表操作](#5.2 大列表操作)
- [5.3 大集合操作](#5.3 大集合操作)
- 六、List与Set对比
-
- [6.1 功能对比](#6.1 功能对比)
- [6.2 场景选择](#6.2 场景选择)
- 七、面试高频考点
- 八、参考资料
- 九、互动话题
一、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如何实现消息队列?
答案:
- 简单队列:LPUSH + RPOP(先进先出)
- 可靠队列:LPUSH + BRPOPLPUSH(失败可恢复)
- 多消费者: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
八、参考资料
九、互动话题
你在项目中用List做过消息队列吗?遇到过什么问题?或者用Set实现过有趣的社交功能?欢迎在评论区分享你的实战经验!
下一篇我们将深入探讨ZSet和Bitmap的高级用法。