返回首页

Redis入门1.3: List、Set、ZSet 详解

适用版本: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:create
  • tags:user:1001
  • leaderboard:game:s1
  • history:view:user:1001

命名清晰,排查问题时会省很多时间。

3. 注意控制集合大小

无论是 List、Set 还是 ZSet,如果业务没有边界控制,都可能持续膨胀。设计时要明确:

  • 是否只保留最近 N 条
  • 是否需要按天分 key
  • 是否需要定期清理历史数据
  • 是否需要过期时间

4. 命令会用,不等于模型就设计对了

Redis 学习里最重要的一步,是把“命令记忆”升级成“建模能力”。只有当你能看到业务就想到合适结构,Redis 才算真正入门。


十六、小结

这篇文章把 Redis 中非常关键的三个结构串了起来:

  • List:解决顺序型数据问题,适合队列、时间线、最近记录
  • Set:解决去重型数据问题,适合标签、点赞用户、参与名单
  • ZSet:解决去重 + 排序问题,适合排行榜、热度榜、延迟任务

建议你重点记住三句话:

  1. 需要“按进入顺序处理”时优先想 List。
  2. 需要“天然去重”时优先想 Set。
  3. 需要“带权重排序”时优先想 ZSet。

理解这三个结构之后,Redis 在很多业务场景里的用法就会突然变得非常具体,而不是停留在“它是一个缓存”这种模糊印象上。


📝 版权声明:本文为原创技术博客,转载请注明出处。

如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!

上一篇

Reds入门1.2: String 与 Hash

下一篇

Redis入门1.4: 常用命令速查与 CLI 技巧