CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Redis 缓存入门/课时/Hash / List / Set / ZSet
RawBlame
课时目录12 节

Redis 缓存入门

  1. 01Redis 是什么
  2. 02CLI 与键命名
  3. 03String 与计数
  4. 04Hash / List / Set / ZSet
  5. 05过期与淘汰
  6. 06缓存模式
  7. 07Spring Data Redis
  8. 08会话与 Sa-Token
  9. 09Docker 运行 Redis
  10. 10Pub/Sub 入门
  11. 11测试与监控
  12. 12综合练习

Hash / List / Set / ZSet

四种结构场景选型。

35 分钟

五种数据结构:选对结构,代码少一半

这一课你将能做到

  1. 用 Hash、List、Set、ZSet 各完成一组真实操作(对象字段读写、队列、收藏去重、排行榜),并说出每种结构「像什么」
  2. 用 TYPE 判断 key 类型,解释 WRONGTYPE 错误的成因和处理方式
  3. 论证本站 tracks 列表为什么用「String 存 JSON」而不是 Hash/List,建立「结构选型跟着读写模式走」的判断力

为什么要学

只会 String 的开发者,遇到业务会硬造:

  • 要「用户收藏的课程」→ 用 String 存逗号分隔的 id?判断收藏过没有还得取出来 split——Set 的 SISMEMBER 一条命令 O(1) 搞定
  • 要「课程学习人数排行」→ 自己查全表排序?ZSet 的 ZINCRBY + ZREVRANGE 两条命令搞定,天然实时有序
  • 要「异步任务队列」→ 数据库轮询?List 的 LPUSH/RPOP 就是现成队列

Redis 官方给自己的定位是数据结构服务器,每种结构都对应一类高频业务场景。选对结构,你的 Java 代码能少写一半,性能还好几个量级;选错了不是「跑不起来」,而是改一个字段要重写整包、去重靠应用层循环、排行每次全量排序——又慢又脆。

先建立图像

String  → 一张便签(整块内容)        coderoadmap:tracks:list
Hash    → 一张名片(多个字段)        user:1001:profile {name:"lin", level:3}
List    → 一根吸管(两端进出)        queue:notify [task3,task2,task1]
Set     → 一袋无序不重的球            user:1001:bookmarks {lesson:5, lesson:12}
ZSet    → 一张自动排序的成绩单        rank:lessons {lesson-7 → 98 分}
结构人话比喻典型场景
String整封信缓存 JSON、计数器、锁
Hash档案袋里多张卡片对象的字段级读写
List排队叫号条队列、最新 N 条
Set去重抽签箱收藏、标签、共同关注
ZSet带分数的成绩榜排行榜、按分数/时间取范围

跟着做

步骤 1:Hash——对象的名片

HSET user:1001 name "xiaolin" level 3
HGET user:1001 name           # "xiaolin"
HGETALL user:1001             # 全部字段和值
HINCRBY user:1001 level 1     # level 变 4 —— 字段级原子自增
HDEL user:1001 level

适合「一个实体的多个字段、字段需要单独读写」的场景。

步骤 2:List——队列与栈

LPUSH queue:notify "task-1" "task-2"
RPUSH queue:notify "task-3"
LRANGE queue:notify 0 -1      # 全量查看(0 -1 = 从头到尾)
LLEN queue:notify
RPOP queue:notify             # 取走队尾一个(消费)

概念了解 BRPOP:阻塞版 RPOP,队列没数据时等待而不是空轮询,是任务队列的标配。本站目前没有用到它,看懂即可——要可靠投递、重试、死信,请用专业 MQ,不要用 List 硬撑。

步骤 3:Set——收藏与去重

SADD user:1001:bookmarks lesson:5 lesson:12
SADD user:1001:bookmarks lesson:5     # (integer) 0 —— 重复元素,集合不变
SISMEMBER user:1001:bookmarks lesson:5   # (integer) 1 —— 已收藏
SCARD user:1001:bookmarks             # (integer) 2 —— 数量
SADD user:1002:bookmarks lesson:12 lesson:9
SINTER user:1001:bookmarks user:1002:bookmarks   # 共同收藏 → lesson:12

步骤 4:ZSet——排行榜

ZADD rank:lessons 98 "lesson-7" 88 "lesson-3"
ZINCRBY rank:lessons 1 "lesson-3"          # 分数 +1,排名自动更新
ZREVRANGE rank:lessons 0 -1 WITHSCORES     # 按分数从高到低
ZRANGEBYSCORE rank:lessons 90 +inf         # 90 分以上

边界提醒:课程在站内导航的排序权威在 MySQL 的 sort_order 字段;ZSet 适合「实时积分榜」这类补充场景,不是取代后台排序字段。

步骤 5:TYPE 与 WRONGTYPE

TYPE user:1001        # hash
TYPE rank:lessons     # zset
LPUSH user:1001 "x"   # (error) WRONGTYPE

每个 key 在第一次写入时确定类型,之后只能执行该类型家族的命令。WRONGTYPE 是类型系统的保护,不是 bug。

步骤 6:论证本站 tracks 列表为什么用 String JSON

看 TrackService(backend/src/main/java/com/codeforge/service/)的 listAll():它把整份 track 列表返回给前端渲染,访问特征是「整体读、整体换、无需按字段改」。

  • 整体读 → 一次 GET 全拿到,不需要 Hash 按字段读
  • 没有按字段改的需求 → Hash 的字段级操作用不上
  • 反序列化一次得到 DTO 列表,和 Java 对象天然对齐

结论写进笔记:**结构选型跟着读写模式走,不是越高级越好。**需要「单条更新 + 单条读」才考虑 Hash,需要服务端排序才上 ZSet;整包只读缓存,String JSON 就是最优解。

讲清楚要点

  1. Hash 字段少时底层是 listpack(紧凑列表),省内存;字段多了自动转哈希表。你不用管转换,但要知道 HGETALL 在大对象上依然危险——单命令返回全部字段,既阻塞又大响应,大 Hash 用 HSCAN。
  2. List 是双端结构(quicklist):头尾操作 O(1),中间 LRANGE 是 O(N)——大列表别全量取。
  3. Set 无序去重,交集并集是杀手锏(共同关注、标签筛选);SISMEMBER O(1)。
  4. ZSet 每个成员带 score,服务端按 score 排序:底层跳表 + 哈希双结构,范围查询和排名都是 O(log N),ZINCRBY 原子加分、排名实时更新。
  5. WRONGTYPE 是保护不是 bug:类型固定才能让底层结构稳定优化。换类型前先 DEL 重建。
  6. 本站缓存里 JSON String 是默认选择;其他结构用于「需要 Redis 服务端能力」的场景——计数、排序、去重、阻塞队列。

你可能会懵的地方

  • 什么时候用 Hash,什么时候用 String JSON? 需要按字段读/改(HGET/HINCRBY)或局部更新时用 Hash;对象整进整出、有嵌套结构时用 String JSON。本站公开 API 缓存全是后者。

  • ZSet 的 score 相同怎么排序? 相同 score 按成员的字典序排。需要严格时间序就把 score 存毫秒时间戳,保证唯一递增。

  • List 做消息队列靠谱吗? 轻量场景(BRPOP + 消费失败 LPUSH 回去)够用;要 ACK、重试、广播就上 Redis Stream 或专业 MQ。第 10 课讲 Pub/Sub 时会系统对比三者。

  • SINTER 性能怎样? 最坏 O(N*M)。小集合(几十几百个成员)没问题,百万级成员慎用,可以改在客户端归并。

  • ZREVRANGE 0 -1 会把排行榜全吐出来吗? 会。线上要限长:ZREVRANGE key 0 9 只取前十名,避免全量响应。

错法对照

错法问题正解
String 存 "1,5,12" 当收藏列表判断收藏要全量解析,去重靠应用层Set + SISMEMBER
用 Hash 存排行榜再客户端排序排序在应用层做,并发更新丢分ZSet + ZINCRBY
对超大 Hash 执行 HGETALL单命令阻塞 + 大响应HSCAN 游标遍历
对 zset key 执行 LPUSHWRONGTYPE先 TYPE 再选命令,换类型先 DEL
List 队列用 RPOP 空轮询空转打爆 RedisBRPOP key 5 阻塞等待

卡点急救

  1. WRONGTYPE:TYPE key 看类型;确认要用其他类型先 DEL(缓存场景重建成本低,注意先看第 6 课再动手删生产 key)。
  2. HGETALL 卡顿:Hash 太大,换 HSCAN key 0 MATCH * COUNT 100。
  3. ZINCRBY 后分数显示成浮点:ZSet score 是双精度浮点,读出来做数值比较,别当字符串判等。
  4. BRPOP 把终端阻塞住了:设超时 BRPOP key 5——5 秒没数据返回 (nil)。
  5. SADD 了重复元素却没报错:Set 天然去重,返回值是「实际新增个数」,返回 0 表示元素已存在。

面试常问

  • Redis 五种基本结构及各自场景? String(缓存 JSON、计数器、锁)、Hash(对象字段级读写)、List(队列、最新列表)、Set(去重、收藏、共同关注)、ZSet(排行榜、延迟任务)。要能结合自己项目各举一例。

  • ZSet 底层怎么实现的? 跳表 + 哈希表双结构:跳表支持 O(log N) 的范围查询和排序(ZREVRANGE/ZRANGEBYSCORE),哈希表支持 O(1) 查成员分数(ZSCORE)。

  • 排行榜怎么设计? ZADD/ZINCRBY 写分数,ZREVRANGE 取 TopN(记得限长),ZRANK 查个人排名。多维度排行(日榜/周榜/总榜)多建几个 ZSet key,各自 TTL 或定期清理。

自测三问

  1. 「判断用户是否收藏过某门课」用什么结构和命令?为什么不用 List 存收藏?
  2. 为什么本站 tracks 列表用 String JSON 而不是 Hash?说出三条理由。
  3. ZSet 和 Set 的核心区别是什么?ZSet 底层靠什么数据结构做到 O(log N)?

小结一句话

五种结构各管一类场景:String 缓存计数、Hash 对象字段、List 队列、Set 去重关系、ZSet 排行——选型跟着读写模式走,本站整包只读缓存坚持 String JSON。

延伸

  • Redis 数据类型总览
  • 按类型分类的命令表
  • 下一课:TTL 与过期淘汰——EXPIRE 的真相、内存满了怎么办、缓存穿透/击穿/雪崩三大经典事故的防御。
加载本节练习…
← 上一课String 与计数下一课 →过期与淘汰