适用版本:Redis 7.2.x
学完 String 和 Hash 之后,Redis 最有意思的部分才刚刚开始。因为从这里开始,你会明显感受到:Redis 不是“把值存进去再取出来”这么简单,而是通过不同数据结构,直接表达业务行为。
比如:
- 消息按进入顺序处理,用 List
- 用户标签去重,用 Set
- 排行榜按分数排序,用 ZSet
如果说 String 和 Hash 更偏向“存值”,那么 List、Set、ZSet 就更像是在“存关系、存顺序、存排序结果”。这也是 Redis 在工程实践里非常有魅力的一点。
这篇文章会按三个结构分别讲清:
- 核心概念
- 常用命令
- 典型业务场景
- 使用建议与常见误区
一、List:有顺序、可重复的字符串列表
List 可以理解成一个按插入顺序组织的双端列表。它的特点是:
- 元素有顺序
- 元素可重复
- 可以从左侧或右侧插入、弹出
这决定了它很适合处理:
- 简单消息队列
- 最新动态列表
- 评论流
- 按时间顺序记录事件
二、List 的基本操作
1. 从左侧插入:LPUSH
LPUSH tasks "task1"
LPUSH tasks "task2"
LPUSH tasks "task3"
此时列表从左到右通常会是:
task3 task2 task1
因为每次都从左边压入。
2. 从右侧插入:RPUSH
RPUSH logs "log1"
RPUSH logs "log2"
RPUSH logs "log3"
结果顺序更像自然追加:
log1 log2 log3
3. 查看列表范围:LRANGE
LRANGE logs 0 -1
含义:查看整个列表。
常见写法:
LRANGE logs 0 4
表示查看前 5 个元素。
4. 左侧弹出:LPOP
LPOP tasks
5. 右侧弹出:RPOP
RPOP logs
6. 查看长度:LLEN
LLEN logs
7. 查看指定位置元素:LINDEX
LINDEX logs 0
8. 修改指定下标元素:LSET
LSET logs 1 "log2-updated"
三、List 的典型场景
1. 简单消息队列
生产者把任务推入列表:
RPUSH order:queue "order_1001"
RPUSH order:queue "order_1002"
消费者从另一端取出:
LPOP order:queue
这是 List 最经典的教学案例。
不过要注意,在生产环境中,如果你需要更健壮的消息确认、重试、消费组等机制,往往还要考虑更专业的消息系统或 Redis Stream。
2. 最新动态流
例如保存用户最近浏览记录:
LPUSH history:user:1001 "sku:9001"
LPUSH history:user:1001 "sku:9002"
LPUSH history:user:1001 "sku:9003"
LRANGE history:user:1001 0 9
最新访问记录会自然排在最前面。
3. 时间线数据
例如文章最新评论流、操作日志流,也可以用 List 做基础承载。
四、使用 List 时的实践建议
1. 明确“左进右出”还是“右进左出”
团队里如果没有约定清楚,很容易出现:
- 生产者用
LPUSH - 消费者也用
LPOP
结果顺序和预期不一致。
建议从命名和注释上明确队列方向。
2. 防止列表无限增长
例如“最近浏览记录”通常只需要保留最近 50 条。这时可以配合 LTRIM。
LPUSH history:user:1001 "sku:9004"
LTRIM history:user:1001 0 49
这样每次只保留前 50 个元素。
3. 不要把 List 当通用数据库表来扫
List 很适合顺序型场景,但如果你的需求开始变成:
- 查某个元素是否存在
- 按条件过滤多个元素
- 按分数或权重排序
那说明 List 可能已经不是最佳结构。
五、Set:无序、去重的集合
Set 的核心特点非常鲜明:
- 无序
- 元素唯一,不可重复
这意味着只要业务里有“去重”语义,Set 往往就非常合适。
例如:
- 用户标签集合
- 某活动参与用户集合
- 某篇文章点赞用户集合
- 共同关注、共同好友计算
六、Set 的基本操作
1. 添加元素:SADD
SADD tags:article:100 redis database nosql redis
即使重复添加 redis,集合中也只会保留一份。
2. 查看全部成员:SMEMBERS
SMEMBERS tags:article:100
3. 判断是否存在某成员:SISMEMBER
SISMEMBER tags:article:100 redis
4. 删除成员:SREM
SREM tags:article:100 nosql
5. 统计成员数量:SCARD
SCARD tags:article:100
6. 随机弹出成员:SPOP
SPOP tags:article:100
7. 随机获取成员:SRANDMEMBER
SRANDMEMBER tags:article:100
七、Set 的高级价值:集合运算
Set 很强的一点是支持集合之间的交、并、差运算。这在业务上特别实用。
1. 交集:SINTER
例如:查共同关注。
SADD follow:user:1001 2001 2002 2003
SADD follow:user:1002 2002 2003 2004
SINTER follow:user:1001 follow:user:1002
结果是两人共同关注的用户。
2. 并集:SUNION
SUNION follow:user:1001 follow:user:1002
得到两人关注对象的合集。
3. 差集:SDIFF
SDIFF follow:user:1001 follow:user:1002
得到“1001 关注了但 1002 没关注”的集合。
这类命令的价值在于:
原本你可能要把数据读出来,在代码里遍历比对;现在 Redis 直接帮你在服务端完成了集合计算。
八、Set 的典型场景
1. 点赞用户去重
SADD like:article:100 1001
SADD like:article:100 1002
SADD like:article:100 1002
SCARD like:article:100
无论用户 1002 点多少次,集合里都只会有一份。
2. 标签系统
SADD user:1001:tags "Java" "Redis" "Backend"
SMEMBERS user:1001:tags
3. 活动参与名单
SADD event:618:joined 1001 1002 1003
SISMEMBER event:618:joined 1002
九、Set 使用建议
1. 如果业务需要顺序,Set 不合适
Set 天生无序。如果你又要去重、又要排序,那要考虑 ZSet。
2. 不要依赖 SMEMBERS 的返回顺序
它的输出顺序不具有稳定业务语义,不能当作“最新”“最早”“权重最高”来解释。
3. 集合运算很强,但也要关注数据量
交并差操作在数据量很大时同样有成本。要理解其业务价值,也要对规模心里有数。
十、ZSet:带分数的有序集合
ZSet(Sorted Set)可以理解为:
- 元素唯一
- 每个元素都绑定一个分数(score)
- Redis 会按 score 自动排序
这使得 ZSet 成为 Redis 中极具代表性的高级结构,特别适合:
- 排行榜
- 积分榜
- 延迟任务排序
- 热度排序
- 优先级队列
如果说 Set 解决的是“去重”,那 ZSet 解决的就是“去重 + 排序”。
十一、ZSet 的基本操作
1. 添加元素:ZADD
ZADD rank:game 100 alice 80 bob 95 tom
这里表示:
- alice 分数 100
- bob 分数 80
- tom 分数 95
2. 查看升序结果:ZRANGE
ZRANGE rank:game 0 -1
3. 带分数查看:ZRANGE ... WITHSCORES
ZRANGE rank:game 0 -1 WITHSCORES
4. 查看降序结果:ZREVRANGE
排行榜更常用倒序:
ZREVRANGE rank:game 0 2 WITHSCORES
表示查看前三名。
5. 查看成员分数:ZSCORE
ZSCORE rank:game alice
6. 增加分数:ZINCRBY
ZINCRBY rank:game 10 bob
7. 查看排名:ZRANK / ZREVRANK
升序排名:
ZRANK rank:game alice
倒序排名:
ZREVRANK rank:game alice
8. 删除成员:ZREM
ZREM rank:game tom
9. 按分数范围查询:ZRANGEBYSCORE
ZRANGEBYSCORE rank:game 90 120 WITHSCORES
十二、ZSet 的典型场景
1. 排行榜
这是最经典的用法。
ZADD leaderboard 1200 user1001 980 user1002 1500 user1003
ZREVRANGE leaderboard 0 9 WITHSCORES
ZREVRANK leaderboard user1002
能直接完成:
- 前十名查询
- 某用户当前排名
- 某用户积分更新
2. 热度榜
比如文章热度,分数可能来自阅读数、点赞数、评论数加权后的结果。
ZADD hot:articles 88 article:1 95 article:2 76 article:3
ZREVRANGE hot:articles 0 4 WITHSCORES
3. 延迟任务
把未来执行时间戳作为 score:
ZADD delay:jobs 1718000000 job:1001 1718000300 job:1002
再按 score 找出“到期应执行”的任务。
这类设计在很多调度系统里都很常见。
十三、List、Set、ZSet 三者怎么选
这是非常重要的选型问题。
| 结构 | 是否有序 | 是否可重复 | 是否支持分数排序 | 典型场景 |
|---|---|---|---|---|
| List | 有序 | 可重复 | 否 | 队列、时间线、最新记录 |
| Set | 无序 | 不可重复 | 否 | 去重、标签、参与名单 |
| ZSet | 按 score 有序 | 成员不可重复 | 是 | 排行榜、热度榜、优先级任务 |
可以用下面这套思路快速判断:
- 要顺序,不在意重复:List
- 要去重,不在意顺序:Set
- 既要去重,又要可排序:ZSet
十四、完整练习:一次把三个结构跑通
1. List 练习
DEL queue:demo
RPUSH queue:demo "job1" "job2" "job3"
LRANGE queue:demo 0 -1
LPOP queue:demo
LRANGE queue:demo 0 -1
LLEN queue:demo
2. Set 练习
DEL set:demo
SADD set:demo a b c a
SMEMBERS set:demo
SISMEMBER set:demo b
SREM set:demo c
SCARD set:demo
3. ZSet 练习
DEL zset:demo
ZADD zset:demo 10 tom 30 alice 20 bob
ZRANGE zset:demo 0 -1 WITHSCORES
ZREVRANGE zset:demo 0 -1 WITHSCORES
ZINCRBY zset:demo 15 tom
ZREVRANK zset:demo tom
ZSCORE zset:demo tom
如果你把上面这三组命令全部亲手敲一遍,对这三个结构的直觉会明显提升。
十五、实践建议
1. 先从业务语义出发,再选结构
不要反过来想“我学了 List,所以我要找个地方硬用 List”。正确顺序应该是:
- 业务是否有顺序?
- 是否需要去重?
- 是否需要分数排序?
- 是否只关心最近 N 条?
把这些问题想清楚,结构选择自然就出来了。
2. Key 命名要体现结构含义
例如:
queue:order:createtags:user:1001leaderboard:game:s1history:view:user:1001
命名清晰,排查问题时会省很多时间。
3. 注意控制集合大小
无论是 List、Set 还是 ZSet,如果业务没有边界控制,都可能持续膨胀。设计时要明确:
- 是否只保留最近 N 条
- 是否需要按天分 key
- 是否需要定期清理历史数据
- 是否需要过期时间
4. 命令会用,不等于模型就设计对了
Redis 学习里最重要的一步,是把“命令记忆”升级成“建模能力”。只有当你能看到业务就想到合适结构,Redis 才算真正入门。
十六、小结
这篇文章把 Redis 中非常关键的三个结构串了起来:
- List:解决顺序型数据问题,适合队列、时间线、最近记录
- Set:解决去重型数据问题,适合标签、点赞用户、参与名单
- ZSet:解决去重 + 排序问题,适合排行榜、热度榜、延迟任务
建议你重点记住三句话:
- 需要“按进入顺序处理”时优先想 List。
- 需要“天然去重”时优先想 Set。
- 需要“带权重排序”时优先想 ZSet。
理解这三个结构之后,Redis 在很多业务场景里的用法就会突然变得非常具体,而不是停留在“它是一个缓存”这种模糊印象上。
📝 版权声明:本文为原创技术博客,转载请注明出处。
如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!