Redis面试要点:从数据结构、持久化到缓存架构的系统梳理
前言
Redis 面试题看起来很散:有人问数据结构,有人问缓存一致性,有人问持久化、主从复制、哨兵、Cluster,也有人会继续追问热点 Key、大 Key、缓存雪崩、分布式锁和慢查询排查。
如果只背结论,很容易在追问中断掉。更好的方式是把 Redis 理解成一条完整链路:
1 | 请求进入 Redis -> 事件循环处理命令 -> 读写内存数据结构 -> 根据过期和淘汰策略管理 Key -> 通过 RDB/AOF 做持久化 -> 通过复制、哨兵、集群提升可用性和扩展性 |
本文按面试中最常见的模块梳理 Redis 要点,适合作为面试前的复习清单。
Redis 整体架构
Redis 是一个以内存为主要存储介质的高性能 Key-Value 数据库。它常被用作缓存、分布式锁、计数器、排行榜、消息队列、会话存储和实时统计系统。
flowchart TD
A[客户端请求] --> B[网络 IO]
B --> C[事件循环]
C --> D[命令解析与执行]
D --> E[内存数据结构]
D --> F[RDB 快照]
D --> G[AOF 日志]
E --> H[过期删除与内存淘汰]
E --> I[主从复制]
I --> J[哨兵或 Cluster]
面试可以这样概括:
Redis 的核心优势来自内存读写、高效的数据结构、事件驱动模型和单线程命令执行带来的低并发控制成本。它不是只靠“内存快”,还依赖合理的数据结构、网络模型、持久化机制和高可用架构。
为什么 Redis 快
Redis 快通常可以从五个角度回答:
| 原因 | 说明 |
|---|---|
| 基于内存 | 大多数读写直接访问内存,避免频繁磁盘 IO |
| 数据结构高效 | String、Hash、List、Set、ZSet 等结构针对常见场景做了优化 |
| 单线程执行命令 | 避免多线程锁竞争和上下文切换,命令执行具备原子性 |
| IO 多路复用 | 一个线程可以同时处理大量网络连接 |
| 简洁协议与实现 | RESP 协议简单,命令路径短,整体开销低 |
需要注意:Redis 的命令执行主流程通常是单线程,但 Redis 并不是所有工作都只有一个线程。后台持久化、异步删除、网络 IO 线程等场景可能会使用额外线程或子进程。
面试表达:
Redis 快不是单一原因,而是内存存储、高效数据结构、事件驱动 IO、多路复用和单线程命令执行共同作用的结果。单线程减少了锁竞争,但也意味着不能执行耗时命令,否则会阻塞其他请求。
单线程模型怎么理解
Redis 的核心命令执行通常在一个线程中完成。这样做有几个好处:
- 不需要为每个数据结构加复杂的并发锁。
- 命令执行天然具备原子性。
- 减少线程切换成本。
- 简化实现,提高稳定性。
但单线程也有明显限制:如果某个命令执行时间太长,例如对超大 Key 执行 hgetall、smembers、keys *,就会阻塞后续请求。
面试中要避免把 Redis 简单说成“完全单线程”。更准确的说法是:
Redis 的网络请求解析和命令执行主流程长期以单线程为核心,因此单个命令执行期间不会被其他命令打断。但 Redis 也会在持久化、异步删除、网络 IO 优化等方面使用子进程或辅助线程。
常见数据类型
Redis 常见数据类型包括 String、Hash、List、Set、ZSet、Bitmap、HyperLogLog、GEO、Stream。
| 类型 | 常见场景 |
|---|---|
| String | 缓存对象、计数器、分布式锁、限流 |
| Hash | 存储对象字段,适合局部字段读写 |
| List | 简单队列、时间线、最新消息列表 |
| Set | 去重、共同好友、标签集合、抽奖 |
| ZSet | 排行榜、延迟队列、带权重排序 |
| Bitmap | 签到、活跃状态、布尔统计 |
| HyperLogLog | UV 估算,允许少量误差 |
| GEO | 附近的人、门店距离计算 |
| Stream | 消息流、消费组、可靠消息队列 |
面试回答时不要只列类型,最好顺带说明适用场景和限制。例如排行榜优先想到 ZSet,签到优先想到 Bitmap,大规模 UV 估算可以用 HyperLogLog。
底层数据结构
Redis 的上层数据类型并不等于底层实现。Redis 会根据元素数量、元素大小和配置选择更合适的内部编码。
常见底层结构包括:
| 底层结构 | 作用 |
|---|---|
| SDS | Redis 自定义字符串结构,用于保存字符串 |
| dict | 哈希表,用于数据库 Key 空间、Hash、Set 等 |
| ziplist/listpack | 紧凑列表结构,节省小对象内存 |
| quicklist | List 的主要底层结构,结合链表和紧凑列表 |
| skiplist | ZSet 的排序结构,支持范围查询 |
| intset | 整数集合,适合小规模整数 Set |
SDS
Redis 没有直接使用 C 字符串作为核心字符串结构,而是使用 SDS。SDS 会记录字符串长度、剩余空间和实际内容。
它的好处是:
- 获取长度是
O(1)。 - 二进制安全,可以存储任意字节。
- 追加字符串时可以减少频繁内存分配。
- 避免普通 C 字符串的一些缓冲区风险。
面试表达:
SDS 比 C 字符串更适合数据库场景,因为它记录长度、支持二进制安全,并通过预分配和惰性空间释放减少内存重分配。
Hash 表
Redis 的数据库本身就是一个大的字典,Key 到 Value 的映射依赖哈希表。Hash、Set 等类型也大量使用哈希表。
哈希表查询平均复杂度是 O(1),但需要处理哈希冲突和扩容问题。Redis 扩容时通常采用渐进式 rehash,不会一次性把所有数据迁移完,避免长时间阻塞。
面试表达:
Redis 哈希表扩容不是一次性完成,而是渐进式 rehash。每次访问字典时顺带迁移一部分数据,从而把扩容成本摊开,减少阻塞风险。
跳表
ZSet 的排序能力通常依赖跳表。跳表通过多层索引加快查找,平均查询、插入、删除复杂度是 O(log n)。
Redis 选择跳表而不是红黑树,常见原因包括:
- 实现相对简单。
- 范围查询很方便。
- 插入删除只需要调整局部指针。
- 性能足够稳定。
面试表达:
ZSet 需要同时支持按分数排序、范围查询和动态更新。跳表实现简单,范围遍历方便,平均复杂度也是
O(log n),很适合 Redis 的有序集合场景。
过期删除策略
Redis 支持给 Key 设置过期时间,例如 expire、setex、set key value ex seconds。
过期 Key 的删除主要有三种思路:
| 策略 | 说明 |
|---|---|
| 定时删除 | 到期立刻删除,及时但消耗 CPU |
| 惰性删除 | 访问 Key 时发现过期再删除,节省 CPU 但可能占内存 |
| 定期删除 | 周期性抽样检查过期 Key,折中方案 |
Redis 主要使用惰性删除加定期删除。
面试表达:
Redis 不会为每个 Key 都创建一个定时器。它主要通过惰性删除和定期删除处理过期 Key:访问时发现过期就删除,同时后台周期性抽样清理,兼顾 CPU 和内存。
内存淘汰策略
当内存达到 maxmemory 限制时,Redis 会根据配置的淘汰策略选择 Key 删除。
常见策略包括:
| 策略 | 含义 |
|---|---|
| noeviction | 不淘汰,写入报错 |
| allkeys-lru | 在所有 Key 中淘汰最近最少使用的 Key |
| volatile-lru | 在设置过期时间的 Key 中淘汰最近最少使用的 Key |
| allkeys-random | 在所有 Key 中随机淘汰 |
| volatile-random | 在设置过期时间的 Key 中随机淘汰 |
| volatile-ttl | 优先淘汰 TTL 更短的 Key |
| allkeys-lfu | 在所有 Key 中淘汰最不常用的 Key |
| volatile-lfu | 在设置过期时间的 Key 中淘汰最不常用的 Key |
LRU 关注最近是否被访问,LFU 关注访问频率。生产中如果 Redis 主要作为缓存,常见选择是 allkeys-lru 或 allkeys-lfu。
持久化机制
Redis 是内存数据库,但可以通过 RDB 和 AOF 做持久化。
RDB
RDB 是快照持久化,会在某个时间点生成数据快照文件。
优点:
- 文件紧凑,适合备份和全量恢复。
- 加载速度通常较快。
- 对主线程影响较小,通常由子进程完成。
缺点:
- 两次快照之间的数据可能丢失。
fork子进程时可能带来额外内存压力。
AOF
AOF 会记录写命令,重启时通过重放命令恢复数据。
常见刷盘策略:
| 策略 | 说明 |
|---|---|
| always | 每次写命令都刷盘,最安全但最慢 |
| everysec | 每秒刷盘一次,性能和可靠性折中 |
| no | 交给操作系统决定,性能好但风险更高 |
AOF 文件会越来越大,因此 Redis 支持 AOF rewrite,把历史命令压缩成恢复当前数据集所需的最少命令。
面试表达:
RDB 更像定时快照,适合备份和快速恢复;AOF 更像写操作日志,数据丢失风险更小。生产中常见做法是两者结合,兼顾恢复速度和数据安全。
主从复制
Redis 主从复制用于读写分离、数据冗余和高可用基础。
flowchart LR
A[Master] --> B[Replica 1]
A --> C[Replica 2]
B --> D[读请求]
C --> E[读请求]
复制大致分为全量复制和增量复制:
- 从节点连接主节点。
- 主节点生成 RDB 快照并发送给从节点。
- 从节点加载快照。
- 主节点把复制期间新增的写命令继续同步给从节点。
- 后续正常情况下通过复制偏移量和复制积压缓冲区做增量同步。
面试追问常见点:
| 问题 | 回答重点 |
|---|---|
| 主从复制是强一致吗 | 不是,通常是异步复制,可能有延迟 |
| 从库能写吗 | 通常不建议写,从库写入不会同步回主库,容易造成数据不一致 |
| 主从延迟怎么办 | 监控延迟、减少大 Key、优化网络和主库压力,关键读走主库 |
哨兵机制
Sentinel 用于监控主从节点,并在主节点故障时自动选举新的主节点。
flowchart TD
A[Sentinel 集群] --> B[监控 Master]
A --> C[监控 Replica]
B --> D{Master 故障?}
D -- 是 --> E[选举新 Master]
E --> F[通知客户端]
E --> G[其他 Replica 重新复制]
哨兵主要负责:
- 监控主从节点是否存活。
- 主观下线和客观下线判断。
- 故障转移。
- 通知客户端新的主节点地址。
面试表达:
哨兵解决的是主从架构下的自动故障转移问题。它会监控节点状态,当多数哨兵认为主节点不可用时,选举一个从节点提升为新的主节点,并让其他从节点重新复制它。
Redis Cluster
Redis Cluster 用于数据分片和高可用。它把整个 Key 空间划分为 16384 个 hash slot,每个主节点负责一部分槽。
flowchart LR
A[客户端] --> B[节点 A: slot 0-5000]
A --> C[节点 B: slot 5001-10000]
A --> D[节点 C: slot 10001-16383]
B --> E[Replica A]
C --> F[Replica B]
D --> G[Replica C]
Cluster 的核心点:
- Key 通过 CRC16 计算后映射到 16384 个槽。
- 每个主节点负责一部分槽。
- 主节点可以有从节点,用于故障转移。
- 客户端访问错误节点时,会收到
MOVED或ASK重定向。 - 多 Key 操作要求 Key 在同一个槽中,可以通过 hash tag 控制。
面试表达:
哨兵解决高可用但不解决水平扩容,Cluster 通过 hash slot 做数据分片,同时每个主节点可以配置从节点来实现故障转移。
缓存穿透、击穿、雪崩
这是 Redis 面试最高频的一组问题。
缓存穿透
缓存穿透指查询一个缓存和数据库都不存在的数据,请求每次都会打到数据库。
常见解决方案:
- 缓存空值,并设置较短 TTL。
- 使用布隆过滤器提前判断 Key 是否可能存在。
- 对非法参数做校验和限流。
面试表达:
缓存穿透的核心是请求绕过缓存直接打到数据库。解决思路是让不存在的数据也能在缓存层被拦截,或者用布隆过滤器提前过滤明显不存在的 Key。
缓存击穿
缓存击穿指某个热点 Key 过期的瞬间,大量请求同时打到数据库。
常见解决方案:
- 热点 Key 不设置过期时间,后台异步刷新。
- 使用互斥锁,只允许一个线程回源重建缓存。
- 逻辑过期:缓存中保存过期时间,过期后先返回旧值,再异步刷新。
面试表达:
缓存击穿关注的是单个热点 Key 失效。解决重点是避免大量请求同时回源,通常用互斥锁、逻辑过期或热点 Key 自动续期。
缓存雪崩
缓存雪崩指大量 Key 在同一时间失效,或者 Redis 整体不可用,导致请求集中打到数据库。
常见解决方案:
- 给 TTL 增加随机值,避免同一时间过期。
- 多级缓存,本地缓存加 Redis。
- Redis 高可用部署。
- 限流、降级、熔断保护数据库。
- 核心数据预热。
面试表达:
缓存雪崩是大面积缓存失效或缓存服务不可用,解决思路不仅是随机 TTL,还包括高可用、多级缓存、限流降级和预热。
缓存一致性
缓存一致性通常围绕数据库和 Redis 的更新顺序展开。
常见方案是:
1 | 先更新数据库,再删除缓存 |
原因是缓存本质上是数据库的派生数据。更新数据库成功后删除缓存,下一次读取再从数据库加载新值写回缓存。
为什么不是先删缓存再更新数据库?
1 | 线程 A 删除缓存 |
如果对一致性要求更高,可以进一步使用:
- 延迟双删。
- 消息队列异步删除缓存。
- 订阅数据库 binlog 删除缓存。
- 设置合理 TTL 作为最终兜底。
面试表达:
缓存和数据库很难做到绝对强一致,常见目标是最终一致。多数业务可以采用先更新数据库再删除缓存,并通过消息补偿、binlog 订阅或 TTL 兜底降低不一致窗口。
分布式锁
Redis 分布式锁常用命令:
1 | SET lock_key request_id NX PX 30000 |
含义:
NX:Key 不存在时才设置成功。PX:设置过期时间,避免死锁。request_id:唯一标识锁持有者,防止误删别人的锁。
释放锁时要用 Lua 脚本保证判断和删除的原子性:
1 | if redis.call("get", KEYS[1]) == ARGV[1] then |
面试追问:
| 问题 | 回答重点 |
|---|---|
| 为什么要设置过期时间 | 防止持锁客户端宕机后锁永远不释放 |
| 为什么 value 要唯一 | 防止锁过期后被别人拿到,旧客户端误删新锁 |
| 为什么释放锁用 Lua | 保证比较 value 和删除 Key 是原子操作 |
| 锁过期但业务没执行完怎么办 | watchdog 自动续期,或合理设置超时时间并控制业务耗时 |
需要注意:如果是非常高一致性、高可靠性的分布式锁场景,Redis 锁未必是唯一选择,也可以考虑数据库、ZooKeeper、etcd 等方案。
大 Key 和热 Key
大 Key
大 Key 指 Value 很大,或者集合类型元素数量特别多的 Key。
风险包括:
- 删除阻塞。
- 网络传输慢。
- 持久化和复制压力大。
- Cluster 中数据倾斜。
- 执行
hgetall、smembers等命令时阻塞主线程。
解决方案:
- 拆分大 Key。
- 使用
scan、hscan、sscan、zscan分批处理。 - 删除大 Key 时使用
unlink异步删除。 - 限制单个 Value 大小和集合元素数量。
- 通过监控工具定期发现大 Key。
热 Key
热 Key 指某个 Key 被极高频访问,可能打满单个 Redis 节点。
解决方案:
- 本地缓存热点数据。
- 热点 Key 拆分成多个副本 Key,分散读压力。
- 读写分离。
- 提前识别热点并做限流降级。
面试表达:
大 Key 关注的是单个 Key 太大带来的阻塞和内存倾斜;热 Key 关注的是单个 Key 访问太集中带来的节点压力。两者都可能导致 Redis 延迟上升,但治理手段不同。
慢查询和性能排查
Redis 性能问题通常从命令、Key、网络、内存、持久化和系统资源几个方向排查。
常用命令:
| 命令 | 用途 |
|---|---|
slowlog get |
查看慢查询 |
info |
查看内存、连接、复制、持久化、命中率等信息 |
monitor |
实时观察命令流,生产慎用 |
scan |
渐进式遍历 Key |
bigkeys |
查找大 Key |
hotkeys |
查找热 Key,需要 LFU 相关配置支持 |
latency doctor |
查看延迟诊断信息 |
常见排查思路:
- 看是否有慢命令,例如
keys *、hgetall、smembers、大范围zrange。 - 看是否存在大 Key 或热 Key。
- 看内存是否接近上限,是否频繁淘汰。
- 看持久化是否带来 fork、AOF rewrite、磁盘 IO 压力。
- 看主从复制是否延迟。
- 看网络连接数、客户端输出缓冲区是否异常。
- 看业务是否存在缓存穿透或短时间突增流量。
面试表达:
Redis 延迟升高不能只看 Redis 本身。要从慢命令、大 Key、热 Key、内存淘汰、持久化 fork、主从复制、网络连接和业务流量一起排查。
常见命令复杂度
面试中经常会追问“这个命令会不会阻塞”。
| 命令 | 复杂度和注意点 |
|---|---|
get / set |
通常 O(1) |
hget / hset |
平均 O(1) |
lpush / rpop |
通常 O(1) |
zadd |
通常 O(log n) |
zrange |
O(log n + m),m 是返回元素数量 |
keys * |
O(n),生产慎用 |
scan |
渐进式遍历,单次阻塞风险较低 |
del 大 Key |
可能阻塞,可考虑 unlink |
hgetall / smembers |
返回数据量大时可能阻塞 |
回答时要强调:复杂度只是一个方面,返回数据量也很关键。一个 O(n) 命令如果 n 很大,就可能造成明显阻塞。
Redis 和数据库的关系
Redis 常用于缓存数据库查询结果,但它不能完全替代关系型数据库。
| 对比项 | Redis | MySQL/PostgreSQL |
|---|---|---|
| 存储介质 | 主要内存 | 主要磁盘加缓存 |
| 查询能力 | Key-Value 和特定数据结构 | SQL、事务、复杂查询 |
| 一致性 | 更偏性能和最终一致 | 更强调事务一致性 |
| 容量成本 | 内存成本高 | 磁盘容量成本低 |
| 典型场景 | 缓存、计数、排行榜、锁 | 核心业务数据、复杂查询 |
面试表达:
Redis 更适合作为高性能缓存和特殊数据结构服务,关系型数据库更适合保存核心业务事实。架构设计时通常让数据库作为数据源,Redis 作为加速层。
高频面试题速记
| 问题 | 回答要点 |
|---|---|
| Redis 为什么快 | 内存、高效数据结构、单线程命令执行、IO 多路复用、协议简单 |
| Redis 真的是单线程吗 | 命令执行主流程通常单线程,后台任务和部分 IO 可使用线程或子进程 |
| 过期 Key 怎么删除 | 惰性删除加定期删除 |
| 内存满了怎么办 | 根据 maxmemory-policy 淘汰,或写入报错 |
| RDB 和 AOF 区别 | RDB 是快照,AOF 是写命令日志 |
| 主从复制强一致吗 | 不是,通常异步复制,可能有延迟 |
| 哨兵解决什么问题 | 监控、故障转移、通知新主节点 |
| Cluster 怎么分片 | 通过 16384 个 hash slot 分配 Key |
| 缓存穿透怎么办 | 缓存空值、布隆过滤器、参数校验 |
| 缓存击穿怎么办 | 互斥锁、逻辑过期、热点 Key 续期 |
| 缓存雪崩怎么办 | 随机 TTL、高可用、多级缓存、限流降级 |
| 分布式锁怎么写 | SET key value NX PX,Lua 脚本校验 value 后删除 |
| 大 Key 怎么处理 | 拆分、scan 分批、unlink 删除、限制集合大小 |
| 热 Key 怎么处理 | 本地缓存、副本 Key、读写分离、限流 |
总结
Redis 面试的主线可以概括为:
1 | 为什么快 -> 数据怎么存 -> Key 怎么过期和淘汰 -> 数据怎么持久化 -> 服务怎么高可用 -> 缓存问题怎么治理 -> 线上慢了怎么排查 |
回答 Redis 问题时,最好不要只背概念,而是结合场景说明取舍。例如 RDB 和 AOF 要联系恢复速度与数据丢失风险,缓存一致性要联系数据库更新链路,大 Key 和热 Key 要联系阻塞、网络、复制和集群倾斜。
真正能打动面试官的回答,通常不是“Redis 很快”,而是能说清楚 Redis 为什么快、什么时候会慢、出了问题如何定位,以及在业务架构里应该把 Redis 放在什么位置。


