Redis 数据类型及应用场景:从基础到实战,一篇讲透
面试官:“Redis 有哪些数据类型?分别用在什么场景?”
你:“String、Hash、List、Set、ZSet。String 做缓存、计数器、分布式锁;Hash 存对象;List 做队列、栈;Set 做去重、交集并集;ZSet 做排行榜、延时队列。”
面试官:“那底层数据结构是什么?ZSet 实现排行榜的复杂度是多少?”
你:“……”
很多人能背出五种类型,但一追问底层结构、命令复杂度、实际选型就含糊了。本文从数据类型到底层实现,结合真实场景,彻底讲透 Redis 数据类型的选择和使用。
一、Redis 数据类型总览
Redis 不是单纯的键值存储,它的值支持多种数据结构,每种结构都有独特的操作命令和适用场景。下表快速对比五种基础类型:
| 类型 | 底层实现(可能) | 特点 | 典型应用 |
|---|---|---|---|
| String | SDS(简单动态字符串) | 二进制安全,可存任意数据 | 缓存、计数器、分布式锁、Session 存储 |
| Hash | 哈希表 + 压缩列表(listpack) | 适合存储对象,可单独操作字段 | 用户信息、商品详情、配置项 |
| List | 双向链表 + 压缩列表(quicklist) | 有序,可重复 | 消息队列、最新列表、栈 |
| Set | 哈希表 + 整数集合 | 无序,唯一 | 标签、共同好友、抽奖去重 |
| ZSet | 跳表 + 哈希表 | 有序,唯一,按分数排序 | 排行榜、延时队列、带权重的任务 |
此外还有 Bitmaps(位图)、HyperLogLog(基数统计)、Geo(地理位置)、Stream(消息流)等高级类型。
二、String:最基础,但不止是字符串
String 是 Redis 中最简单的类型,键值对结构,值可以是字符串、数字、二进制数据(如图片序列化)。底层使用 SDS,支持自动扩容、二进制安全。
常用命令
SET key value[EX seconds]# 设置,可带过期时间GET key INCR key# 原子自增DECR key MSET k1 v1 k2 v2# 批量设置应用场景
1. 缓存热点数据
最常见用法,将数据库查询结果序列化为 JSON 存入 Redis,设置合理过期时间。
// 伪代码Useruser=redis.get("user:123");if(user==null){user=db.query(...);redis.setex("user:123",3600,JSON.serialize(user));}2. 计数器
利用INCR原子操作实现文章阅读数、点赞数、限流计数。
# 文章 ID 1001 阅读数 +1INCR article:1001:views# 获取阅读数GET article:1001:views3. 分布式锁
SET NX EX实现简单的分布式锁(需注意原子性)。
SET lock:order:1001 uuid NX EX10# 获取锁,10秒过期# 释放锁时用 Lua 脚本判断 value 是否为自己,避免误删4. Session 存储
将用户 Session 序列化存入 String,设置过期时间,实现无状态服务的 Session 共享。
三、Hash:对象存储的最佳选择
Hash 是一个 field-value 映射表,适合存储对象,可以单独修改某个字段,避免整个对象序列化开销。
常用命令
HSET user:1001 name"张三"age25HGET user:1001 name HGETALL user:1001 HINCRBY user:1001 age1# 字段自增应用场景
1. 存储对象
比如用户信息、商品信息。相比 String 存 JSON,Hash 可以独立修改某个字段,且内存占用通常更小(尤其是使用压缩列表时)。
2. 购物车
以用户 ID 为 key,商品 ID 为 field,商品数量为 value。
HSET cart:100120012# 用户1001添加商品2001数量2HINCRBY cart:100120011# 增加数量3. 配置项
系统配置可存储为 Hash,动态修改单个配置无需重启。
四、List:有序可重复的队列
List 是双向链表,支持从两端推入弹出,按索引访问,但越靠近中间越慢(需遍历)。
常用命令
LPUSH queue task1 task2# 左侧推入RPUSH queue task3# 右侧推入LPOP queue# 左侧弹出RPOP queue LRANGE queue0-1# 获取全部应用场景
1. 消息队列(简单版)
生产者RPUSH,消费者LPOP(或BRPOP阻塞等待)。缺点是无确认机制,消费后即删除。Redis 5.0 推出的 Stream 更适合可靠消息队列。
2. 最新列表
比如最新评论、最新动态。LPUSH新内容,LTRIM截取固定长度,保留最近 N 条。
3. 栈
LPUSH+LPOP实现后进先出。
4. 异步日志收集
多个进程RPUSH日志,一个消费者LPOP写入文件。
五、Set:无序且唯一
Set 基于哈希表实现,支持集合运算(交集、并集、差集)。
常用命令
SADD tags:news:1001"技术""Redis"# 添加元素SREM tags:news:1001"技术"# 移除SMEMBERS tags:news:1001# 获取所有SINTER set1 set2# 交集SUNION set1 set2# 并集应用场景
1. 标签系统
为文章打标签,或为用户打兴趣标签。可以快速计算拥有某标签的文章数量,或推荐共同标签的文章。
2. 共同好友/关注
用户的好友集合,求交集得到共同好友。
3. 抽奖去重
将参与用户 ID 加入 Set,SRANDMEMBER随机抽取,保证一人一次中奖机会。
4. 唯一访问量(UV)
使用 Set 存储访问 IP,虽然比 HyperLogLog 耗内存,但精确且能查看具体 IP。
六、ZSet:有序且唯一
ZSet 每个元素关联一个 double 类型的分数,按分数排序。底层是跳表 + 哈希表,实现 O(logN) 的插入、查找、范围查询。
常用命令
ZADD rank100"playerA"# 添加/更新分数ZINCRBY rank10"playerA"# 增加分数ZREVRANGE rank09WITHSCORES# 获取分数前10名(降序)ZRANGEBYSCORE rank80100# 按分数范围取ZREM rank"playerA"应用场景
1. 排行榜
游戏积分榜、热搜榜。ZREVRANGE取 Top N,ZRANK获取用户排名。
2. 延时队列
将任务执行时间戳作为分数,消费者用ZRANGEBYSCORE轮询获取已到期的任务,删除并执行。相比 List 队列,支持按时间调度。
3. 带权重的任务调度
优先级高的任务分数小(或大),优先被处理。
4. 滑动窗口限流
将用户请求的时间戳作为分数存入 ZSet,统计指定时间窗口内的请求数量。
七、高级数据类型简介
| 类型 | 实现原理 | 应用场景 |
|---|---|---|
| Bitmaps | 位数组,基于 String 的位操作 | 签到统计、在线状态、布隆过滤器 |
| HyperLogLog | 概率算法,误差 0.81% | UV 统计(不要求精确时) |
| Geo | 基于 ZSet,存储地理位置 | 附近的人、距离计算 |
| Stream | 类似于消息队列的持久化结构 | 可靠消息队列、消费者组 |
八、底层数据结构与选择建议
了解底层实现有助于预估内存和性能:
- String:SDS,内存连续,简单高效。
- Hash:元素少时用压缩列表(ziplist),节省内存;元素多时转哈希表。Redis 7.0 后使用 listpack 替代 ziplist。
- List:3.2 版本前可用压缩列表或双向链表,3.2 后统一为 quicklist(压缩列表 + 链表)。
- Set:整数集合(intset)用于全整数且数量少,否则哈希表。
- ZSet:元素少时用压缩列表,多时用跳表(score->元素) + 哈希表(元素->score)。
选型指南
- 需要存储简单值、计数器、分布式锁 →String
- 需要频繁修改对象的部分字段 →Hash
- 需要按插入顺序或作为队列/栈 →List
- 需要去重、集合运算 →Set
- 需要排序、范围查询、权重 →ZSet
九、常见面试追问
Q1:ZSet 底层为什么用跳表而不是红黑树?
- 跳表实现简单,支持范围查询(
ZRANGEBYSCORE)效率高。 - 红黑树范围查询需中序遍历,跳表更容易找到区间起点然后顺序遍历。
- 跳表平均 O(logN),与红黑树相当,且并发控制更简单。
Q2:使用 String 存储对象和 Hash 存储对象哪个更好?
- 如果经常需要修改对象的部分字段,Hash 更优(无需整体序列化)。
- 如果总是整体存取且字段很少,String + JSON 更简单。
- Hash 在字段数量少且值小时内存占用更低(压缩列表)。
Q3:List 作为消息队列有哪些缺点?
- 不支持多消费者组,一个消息只能被一个消费者消费(除非用多个 List,复杂)。
- 无 ACK 机制,消费者宕机消息丢失。
- 建议使用 Stream 或专业消息队列(Kafka、RabbitMQ)。
Q4:Set 和 ZSet 的区别?
- Set 无序,ZSet 按分数有序。
- ZSet 每个元素有分数,Set 无。
- 两者都保证唯一性。
Q5:Redis 数据类型如何选择?
根据业务操作模式:是否需要排序、是否需要唯一、是否需要范围查询、是否需要单独修改字段。
十、总结
| 类型 | 一句话总结 | 常见陷阱 |
|---|---|---|
| String | 万金油,存啥都行 | 大 key 阻塞 |
| Hash | 对象存取神器 | 字段太多导致大 key |
| List | 简单队列 | 阻塞弹出可能导致空闲连接 |
| Set | 去重合集运算 | 大集合交集耗时 |
| ZSet | 排序王者 | 分数相同按字典序,易误解 |
一句话记住数据类型:String 缓存计数器,Hash 对象最适宜;List 队列最新榜,Set 去重交并集;ZSet 排行延时器,场景匹配快且利。
掌握 Redis 数据类型及其底层原理,是写出高性能缓存代码的关键。希望这篇文章能帮你彻底掌握这块高频考点,欢迎继续讨论。