前言

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 的核心命令执行通常在一个线程中完成。这样做有几个好处:

  1. 不需要为每个数据结构加复杂的并发锁。
  2. 命令执行天然具备原子性。
  3. 减少线程切换成本。
  4. 简化实现,提高稳定性。

但单线程也有明显限制:如果某个命令执行时间太长,例如对超大 Key 执行 hgetallsmemberskeys *,就会阻塞后续请求。

面试中要避免把 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 设置过期时间,例如 expiresetexset 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-lruallkeys-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[读请求]

复制大致分为全量复制和增量复制:

  1. 从节点连接主节点。
  2. 主节点生成 RDB 快照并发送给从节点。
  3. 从节点加载快照。
  4. 主节点把复制期间新增的写命令继续同步给从节点。
  5. 后续正常情况下通过复制偏移量和复制积压缓冲区做增量同步。

面试追问常见点:

问题 回答重点
主从复制是强一致吗 不是,通常是异步复制,可能有延迟
从库能写吗 通常不建议写,从库写入不会同步回主库,容易造成数据不一致
主从延迟怎么办 监控延迟、减少大 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 个槽。
  • 每个主节点负责一部分槽。
  • 主节点可以有从节点,用于故障转移。
  • 客户端访问错误节点时,会收到 MOVEDASK 重定向。
  • 多 Key 操作要求 Key 在同一个槽中,可以通过 hash tag 控制。

面试表达:

哨兵解决高可用但不解决水平扩容,Cluster 通过 hash slot 做数据分片,同时每个主节点可以配置从节点来实现故障转移。

缓存穿透、击穿、雪崩

这是 Redis 面试最高频的一组问题。

缓存穿透

缓存穿透指查询一个缓存和数据库都不存在的数据,请求每次都会打到数据库。

常见解决方案:

  • 缓存空值,并设置较短 TTL。
  • 使用布隆过滤器提前判断 Key 是否可能存在。
  • 对非法参数做校验和限流。

面试表达:

缓存穿透的核心是请求绕过缓存直接打到数据库。解决思路是让不存在的数据也能在缓存层被拦截,或者用布隆过滤器提前过滤明显不存在的 Key。

缓存击穿

缓存击穿指某个热点 Key 过期的瞬间,大量请求同时打到数据库。

常见解决方案:

  • 热点 Key 不设置过期时间,后台异步刷新。
  • 使用互斥锁,只允许一个线程回源重建缓存。
  • 逻辑过期:缓存中保存过期时间,过期后先返回旧值,再异步刷新。

面试表达:

缓存击穿关注的是单个热点 Key 失效。解决重点是避免大量请求同时回源,通常用互斥锁、逻辑过期或热点 Key 自动续期。

缓存雪崩

缓存雪崩指大量 Key 在同一时间失效,或者 Redis 整体不可用,导致请求集中打到数据库。

常见解决方案:

  • 给 TTL 增加随机值,避免同一时间过期。
  • 多级缓存,本地缓存加 Redis。
  • Redis 高可用部署。
  • 限流、降级、熔断保护数据库。
  • 核心数据预热。

面试表达:

缓存雪崩是大面积缓存失效或缓存服务不可用,解决思路不仅是随机 TTL,还包括高可用、多级缓存、限流降级和预热。

缓存一致性

缓存一致性通常围绕数据库和 Redis 的更新顺序展开。

常见方案是:

1
先更新数据库,再删除缓存

原因是缓存本质上是数据库的派生数据。更新数据库成功后删除缓存,下一次读取再从数据库加载新值写回缓存。

为什么不是先删缓存再更新数据库?

1
2
3
4
线程 A 删除缓存
线程 B 读取缓存未命中,查询旧数据库值并写回缓存
线程 A 更新数据库
最终缓存中可能长期保留旧值

如果对一致性要求更高,可以进一步使用:

  • 延迟双删。
  • 消息队列异步删除缓存。
  • 订阅数据库 binlog 删除缓存。
  • 设置合理 TTL 作为最终兜底。

面试表达:

缓存和数据库很难做到绝对强一致,常见目标是最终一致。多数业务可以采用先更新数据库再删除缓存,并通过消息补偿、binlog 订阅或 TTL 兜底降低不一致窗口。

分布式锁

Redis 分布式锁常用命令:

1
SET lock_key request_id NX PX 30000

含义:

  • NX:Key 不存在时才设置成功。
  • PX:设置过期时间,避免死锁。
  • request_id:唯一标识锁持有者,防止误删别人的锁。

释放锁时要用 Lua 脚本保证判断和删除的原子性:

1
2
3
4
5
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end

面试追问:

问题 回答重点
为什么要设置过期时间 防止持锁客户端宕机后锁永远不释放
为什么 value 要唯一 防止锁过期后被别人拿到,旧客户端误删新锁
为什么释放锁用 Lua 保证比较 value 和删除 Key 是原子操作
锁过期但业务没执行完怎么办 watchdog 自动续期,或合理设置超时时间并控制业务耗时

需要注意:如果是非常高一致性、高可靠性的分布式锁场景,Redis 锁未必是唯一选择,也可以考虑数据库、ZooKeeper、etcd 等方案。

大 Key 和热 Key

大 Key

大 Key 指 Value 很大,或者集合类型元素数量特别多的 Key。

风险包括:

  • 删除阻塞。
  • 网络传输慢。
  • 持久化和复制压力大。
  • Cluster 中数据倾斜。
  • 执行 hgetallsmembers 等命令时阻塞主线程。

解决方案:

  • 拆分大 Key。
  • 使用 scanhscansscanzscan 分批处理。
  • 删除大 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 查看延迟诊断信息

常见排查思路:

  1. 看是否有慢命令,例如 keys *hgetallsmembers、大范围 zrange
  2. 看是否存在大 Key 或热 Key。
  3. 看内存是否接近上限,是否频繁淘汰。
  4. 看持久化是否带来 fork、AOF rewrite、磁盘 IO 压力。
  5. 看主从复制是否延迟。
  6. 看网络连接数、客户端输出缓冲区是否异常。
  7. 看业务是否存在缓存穿透或短时间突增流量。

面试表达:

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 放在什么位置。