Redis 五大数据结构:什么场景选哪个,一张表记牢
作者:鱼宵 | 实战驱动系列 · 第 8 篇
完整课程与可运行源码已开源在 Gitee:gitee.com/j67mk2/redi… (本文对应 lesson-02/)
一、面试题预告:这五个问题几乎必考
- String / Hash / List / Set / ZSet 底层分别是什么?(一句话版)
- 为什么
INCR是原子的?"原子"到底啥意思? - 用 List 当消息队列有什么坑?
- ZSet 为什么用跳表?跳表是什么?
- 给你一个业务场景,怎么挑数据类型?
上一篇我们"混了个脸熟",这篇一个个拆开,每种类型:底层一句话 + 典型场景 + 高频命令 + 实测。
二、String:贴标签的盒子
- 底层一句话:SDS(简单动态字符串),就是"会自动扩容的字符串数组",不用记源码。
- 典型场景:缓存接口结果;点赞数/访问量/库存(
INCR/DECR);多台机器一起INCR同一个键拿全局唯一递增 ID。
"原子"是啥意思? 人话:INCR 在 Redis 里是一口气做完、中间不会被别的命令插队。两个请求同时给点赞数 +1,如果不是原子,可能都读到 100、都写成 101,少加一次;原子操作保证最终一定是 102。
实测(计数器):
SET lesson:str:counter 0 -> OK
INCR lesson:str:counter -> 1
INCR lesson:str:counter -> 2
INCRBY lesson:str:counter 5 -> 7
三、Hash:一格多抽屉
- 底层一句话:一张哈希表(key → 多个 field-value)。
- 典型场景:存一个经常只改几个字段的对象(用户信息)。相比把整个对象序列化成 String,Hash 可以只改
age一个字段,不用重写整条。
实测:
HSET user:1 name "王小明" age 25 city "郑州" -> 3
HGET user:1 name -> 王小明
HGETALL user:1
name / 王小明 / age / 25 / city / 郑州
思考题:为什么存"经常只改单个字段"的对象用 Hash 更合适?答:序列化成 String 的话,改一个 age 也得把整个 JSON 重写一遍,Hash 是点改。
四、List:有顺序的队伍
- 底层一句话:双向链表(新版 Redis 叫 quicklist,"一串链表串起来")。
- 典型场景:轻量消息队列(生产者
LPUSH塞左边,消费者BRPOP右边取);朋友圈最新 10 条。
List 做队列的坑(面试爱问):
BRPOP是阻塞读:队列空了它会一直等,连接被占住(实测空队列阻塞了约 2017ms 才返回 null);- 没有消息确认机制:消息一被弹出就没了,消费者拿到后崩了,这条消息就丢了;
- 所以只能当"轻量、可容忍丢失"的队列,正经消息队列用 RabbitMQ/Kafka。
实测:
LPUSH list:q "订单1" "订单2" -> 2
LRANGE list:q 0 -1
订单2
订单1
BRPOP list:q 2
list:q
订单1
注意顺序:LPUSH a b 后从队头看是 b a(最后塞的最靠前),想"先进先出"要配 BRPOP 从队尾取。
五、Set:不会重复的篮子
- 底层一句话:哈希表(元素唯一);全是小整数时会用更省内存的 intset。
- 典型场景:去重(点赞用户、签到);交并集——
SINTER求"共同好友"、SUNION求合集。
实测(共同好友):
SADD set:zhangsan "王五" "赵六" "钱七" -> 3
SADD set:lisi "王五" "孙八" "周九" -> 3
SINTER set:zhangsan set:lisi
王五 # 两人的共同好友
六、ZSet:带分数的排行榜
- 底层一句话:跳表(skiplist)+ 哈希表。
- 跳表类比 = 多层电梯:普通链表只有一层,找一个人得从一楼挨个走;跳表盖了"快速楼层",先坐高层电梯跳到大概位置,再走楼梯细找,所以按分数排序、按范围查都快。
- 典型场景:游戏积分榜、热搜榜。
- 关键命令:
ZADD加成员分数、ZINCRBY加分、ZRANGE升序、ZREVRANGE降序(榜一榜二)。
实测:
ZADD zset:rank 95 "语文" 88 "数学" 100 "英语" -> 3
ZINCRBY zset:rank 5 "数学" -> 93 (数学从 88 涨到 93)
ZREVRANGE zset:rank 0 2 WITHSCORES
英语 -> 100
语文 -> 95
数学 -> 93
思考题:为什么用跳表不用平衡树?面试答:实现简单、范围查询天然友好、单节点增删只影响局部,Redis 作者选它就是看中"够用且好写"。
七、选型速查表(面试直接背这个)
| 你想干嘛 | 选哪个 | 例子 |
|---|---|---|
| 缓存文本/数字、做计数器 | String | 缓存页面、点赞数 |
| 存对象、经常改其中几个字段 | Hash | 用户资料 |
| 简单排队、可容忍丢消息 | List | 站内消息队列 |
| 去重、求交集并集 | Set | 共同好友、抽奖去重 |
| 带分数排序、排行榜 | ZSet | 积分榜、热搜 |
八、挑战题
- ⭐ 用 String +
INCR做"网站今日访问量"计数器,连点 3 次看结果。 - ⭐⭐ 用 Set 实现"两个用户的共同关注",自己造两组关注列表,
SINTER求交集。 - ⭐⭐⭐ 用 List 实现"不能丢消息"的队列,怎么补救?(提示:消费者处理完再删,而不是弹出来就删——分布式锁那篇会再碰到类似问题)
九、面试回答模板
面试官:ZSet 底层是什么?
跳表 + 哈希表。跳表像多层电梯,按分数排序和范围查询都是 O(logN)。
追问:List 做消息队列有什么坑?
① BRPOP 阻塞占用连接;② 没有消息确认,弹出即丢,消费者崩了就丢消息;③ 生产用 MQ。
追问:INCR 为什么安全?
原子操作,一步完成不被插队,多并发不会数错。
追问:场景选型?
缓存/计数 String,对象 Hash,轻队列 List,去重 Set,排行榜 ZSet。
关于这个系列
本文是「Java 后端实战精通营」系列第 8 篇,原则:实战驱动、由浅到深、面试向。
👉 Redis 实战精通营(10 课):gitee.com/j67mk2/redi…
- 本文对应源码位置:
lesson-02/(五种类型一组 Jedis demo 全跑一遍)
下一篇预告:《Redis 持久化:断电了数据怎么不丢?RDB 和 AOF 怎么选》——快照 vs 记账本,混合持久化是啥,亲手触发一次 BGSAVE 看到 dump.rdb 从无到有。
跑完有任何报错,把终端输出发评论区,一起排查。