Redis 常见面试题:缓存、一致性与分布式锁

从数据结构与线程模型,讲到过期淘汰、缓存故障、持久化和锁,准备Redis的常见问题。

10 道题目来自 69 篇面经 · 4 份题集
本页目录

基础结构与运行机制

3 道题目
查看参考回答收起参考回答

Redis 常见的五种基础数据类型是 String、Hash、List、Set 和 Sorted Set(Zset)。选择时看需要怎样访问和修改数据:

  • String:保存一个字节串,适合缓存整份 JSON、保存令牌或使用原子自增计数。
  • Hash:在一个键下组织多组字段和值,适合用户信息、商品属性、购物车等需要按字段读写的对象。
  • List:有顺序、允许重复的字符串序列,支持两端插入和弹出,适合近期记录、简单待处理列表等。
  • Set:无序且成员唯一,支持成员判断和交并差,适合去重、标签集合、共同关注。
  • Zset:成员唯一,每个成员关联一个分数,适合按分数排序、取排名或查范围,例如积分榜和按时间排序的任务索引。

这里说的是对外的数据模型,不等于底层编码。Redis 也有 Bitmap、HyperLogLog、GEO、Stream 等其他能力,不能把“五种基础类型”理解成所有版本只有五种类型。

完整解析与参考资料
来源 · 11 篇面经 · 2 份题集收起来源
查看参考回答收起参考回答

Redis 的快主要来自数据访问路径短、适合键值操作的数据结构,以及非阻塞网络处理,不能只归因于“单线程”。

  • 热点数据在内存中访问。 常见读写不用每次从磁盘读数据页,也不用像关系查询那样完成连接、执行计划等处理;但持久化和内存压力仍会影响延迟。
  • 按操作选择数据结构与编码。 按键查询、集合成员判断、排名等有对应实现,小集合还可以采用紧凑编码。不同命令的复杂度不同,并非所有操作都是 O(1)。
  • I/O 多路复用配合事件循环。 可以管理大量连接,按就绪事件处理,避免每个连接独占一个等待线程。常见数据命令主要串行执行,减少了多个执行线程同时修改核心数据结构所需的协调。
  • 通信和批处理减少额外开销。 RESP 协议、连接复用、适当的批量命令与 Pipeline,有助于降低解析、建连、网络往返及系统调用的成本。

所以,Redis 适合大量短小的键值请求;“连接多、请求多”不意味着同一实例的命令都在多核上并行执行。实际速度仍取决于命令、数据量、网络和部署条件。

完整解析与参考资料
来源 · 5 篇面经 · 2 份题集收起来源
查看参考回答收起参考回答

要先说明“单线程”指哪一部分。对 Redis Open Source 常见的键值读写命令,一个实例主要由主线程顺序执行;整个 Redis 进程并不只有一个线程。集群则由多个独立实例分片,各节点可以并行工作。

  • 命令执行:主线程负责操作键空间和数据结构,普通命令不会因为开启 I/O 线程,就变成多条命令同时修改同一个实例的数据。
  • 网络 I/O:Redis 6.0 开始引入多线程 I/O,把部分收发工作交给 I/O 线程;具体分工和配置随版本变化。例如 Redis 8.4 的配置说明包含读、写和协议解析,不能直接套用旧版本“只负责发送响应”的说法。
  • 后台任务:AOF 的部分刷盘工作、异步释放对象等可以交给后台线程;后台生成 RDB、重写 AOF 通常还涉及 fork 出的子进程,线程和进程要分开。
  • Redis Cluster:不同主节点分别负责一部分哈希槽,各实例仍采用自己的线程模型。集群提升的是多个实例处理不同分片的能力,并没有把一个节点的命令执行自动改成多线程。

所以,“单节点命令主要串行”和“整个集群能并行处理请求”可以同时成立。

完整解析与参考资料
来源 · 7 篇面经 · 1 份题集收起来源

缓存使用与一致性

3 道题目
查看参考回答收起参考回答

下面以电商商品目录服务为参考示例,具体业务和参数可按项目调整。

Redis 主要缓存商品展示详情和分类字典。这类数据会被重复读取,更新相对少,适合减少数据库查询;MySQL 保存权威数据,Redis 中的内容可以从数据库重建。

  • 缓存内容:商品详情按商品 ID 存,value 是页面需要的名称、图片、分类 ID、展示价格等字段;分类字典单独缓存。使用业务前缀和结构版本区分键,避免不同数据混用。
  • 读取流程:先查 Redis,命中直接返回;未命中再查 MySQL,查询成功后把结果和 TTL 一起写入缓存。数据库确认不存在时,可短期缓存空值标记。
  • 更新流程:后台修改商品并提交数据库事务后,删除对应缓存,下次读取时重建;删除失败进入可靠重试。TTL 负责兜底,并加随机偏移,分散集中过期。
  • 异常与效果:热点重建合并请求,Redis 故障时限时、限量回源或降级,避免压垮数据库。效果看业务缓存命中率、回源量、接口延迟与内存使用,同时检查数据新鲜度。

商品展示允许的陈旧程度,与下单校验不同;实际成交价、库存是否可扣,要在权威业务链路中确认。

完整解析与参考资料
来源 · 12 篇面经收起来源
查看参考回答收起参考回答

三者都会让过多请求绕过缓存、增加数据库压力,区别在于数据是否存在,以及失效集中在一个热点还是大片缓存。

  • 缓存穿透:请求的数据在缓存和数据库中都不存在,反复查询仍无法填充正常缓存。先校验参数、限制异常请求,再对查无结果设置短期空值缓存;不存在的 ID 很分散时,可用布隆过滤器提前过滤。
  • 缓存击穿:数据库中有数据,但某个热点 key 过期、被淘汰或尚未建立,大量请求同时回源重建。可以提前预热、合并同一 key 的重建请求,或用互斥锁限制重建;允许短暂旧数据时,可采用逻辑过期、异步刷新。
  • 缓存雪崩:大量 key 集中失效,或者缓存服务整体不可用,导致大范围回源。集中过期用随机 TTL、分批预热来打散;服务故障则依靠高可用、快速超时、限流和降级。无论哪种情况,都要限制回源总量,避免把缓存故障扩大成数据库故障。

下面用取货场景对比三种故障:货架表示缓存,仓库表示数据库。看请求的编号、两处是否有对应数据,以及回源范围,就能区分穿透、击穿和雪崩;每幅场景下方列出常见处理方法。

缓存取货场景:两处都无数据的穿透、单个热点缓存缺失的击穿、大片缓存失效的雪崩,以及对应处理方法

完整解析与参考资料
来源 · 19 篇面经 · 2 份题集收起来源
查看参考回答收起参考回答

常见做法是以 MySQL 为权威数据源,采用 Cache Aside:读时先查 Redis,未命中再查数据库并回填;写时先提交数据库事务,再删除对应缓存。 配合可靠失效重试和 TTL,目标是最终一致,不能保证所有时刻都没有旧读。

需要分别处理三个问题:

  1. 提交后、删除前的窗口:这段时间仍可能命中旧缓存。把删除放在事务提交之后,避免因提前失效而让并发请求在提交前回源旧数据;业务必须明确能否接受这个窗口。
  2. 删除失败或进程退出:不能只在内存里重试。可以把失效事件与业务更新写入同一个数据库事务的 Outbox 表,再异步投递和重试;也可以通过 CDC 订阅已提交的变更触发删除。
  3. 并发旧值回填:读请求可能先读到旧值,等写请求提交并删完缓存后,才把旧值写回。删除重试和 TTL 只能缓解,要求更高时还需设计覆盖读写路径的版本校验或互斥协议。

对于余额、库存校验等不能依赖陈旧值的关键判断,应走权威数据库的事务读写路径,明确隔离与读写语义,不能仅靠缓存双删来保证。

完整解析与参考资料
来源 · 17 篇面经 · 1 份题集收起来源

过期、淘汰与持久化

3 道题目
查看参考回答收起参考回答

Redis 将被动过期(惰性删除)和主动过期(定期删除)结合,在清理过期数据与控制 CPU 开销之间取平衡。

  • 被动过期:访问某个键时检查过期时间。普通读取发现它已过期,就按不存在处理,并在相应路径触发删除。这样不必不停检查所有键,但一直不被访问的过期键可能继续占内存。
  • 主动过期:周期性地从设置了过期时间的键中增量抽查,删除发现的过期键,并根据清理情况和时间预算决定是否继续。它负责清理那些没人再访问的键,不是每轮全库扫描。

因此,TTL 到期、键从键空间移除、对象内存释放,不一定发生在同一瞬间。主动和被动过期解决“什么时候发现并移除过期键”;同步删除或 lazy free 解决“对象内存由谁、何时释放”,这是两个不同维度。

完整解析与参考资料
来源 · 7 篇面经收起来源
查看参考回答收起参考回答

Redis 内存淘汰是在配置了 maxmemory 上限,且用于淘汰判断的内存超过这个上限时,按 maxmemory-policy 尝试移除键,为后续操作腾出空间。没有可淘汰的键,或选择不淘汰策略时,相应写入命令可能返回 OOM 错误。

它与过期删除是两回事:过期删除处理已经到达 TTL 的键;内存淘汰处理容量压力,可能删除尚未过期、甚至没有 TTL 的键,取决于策略。

常见的 8 种策略可以按“候选范围”和“选择方法”记忆:

  • allkeys-lru、allkeys-lfu、allkeys-random:从所有键中,分别按最近最少使用、较低访问频率、随机方式选择。
  • volatile-lru、volatile-lfu、volatile-random:使用同样的选择方法,但候选范围仅限设置了 TTL 的键。
  • volatile-ttl:从设置了 TTL 的键中,优先选择剩余存活时间较短的候选键,不要求它已经过期。
  • noeviction:不因内存压力主动淘汰键;超限且无法满足相应命令的内存要求时拒绝该类写入,普通读取、删除等仍可执行。它是 Redis Open Source 的默认策略。

版本要补充:Redis 8.6 新增 allkeys-lrm 和 volatile-lrm,因此 8.6 的配置支持 10 种。 LRM 是最近最少修改,只用写操作更新相应时间,不会因为键被频繁读取就把它视为“刚修改过”。

完整解析与参考资料
来源 · 3 篇面经 · 1 份题集收起来源
查看参考回答收起参考回答

Redis 主要有 RDB 快照和 AOF 日志两种持久化机制,可以同时使用;常说的混合持久化,是在 AOF 的基础数据部分采用 RDB 格式,再记录后续增量。选择时主要比较三点:能接受丢多少数据、希望多快恢复,以及愿意承担多少内存和磁盘 IO 开销。

  • RDB 保存某个时间点的数据集快照,文件通常较紧凑,适合保存历史备份和较快恢复。缺点是两次快照之间的修改没有全部落入这个快照,异常宕机时可能丢失最近一次成功快照之后的数据;后台生成也有 fork、写时复制和 IO 成本。
  • AOF 追加能够恢复数据变化的写操作记录,重启时加载并重放,数据丢失窗口取决于同步策略。它可以比低频快照更及时地保存修改,但持续写日志和刷盘会产生开销,文件会增长,需要重写;纯命令日志通常也比对应快照加载更慢。
  • 混合持久化 先加载 RDB 格式的基础数据,再重放 AOF 增量,兼顾基础数据加载与后续修改恢复。它没有消除 AOF 的刷盘窗口,也不等于同时存在一个独立 dump.rdb 文件就自动完成了混合持久化。

如果数据可以从主数据库重建,可以根据冷启动和回源压力选择较轻的持久化策略,必要时也能关闭持久化。如果恢复要求更高,常见做法是配置 AOF,并保留定期快照及独立备份;具体同步策略和快照频率应由业务恢复目标决定。

完整解析与参考资料
来源 · 6 篇面经 · 2 份题集收起来源

分布式锁

1 道题目
查看参考回答收起参考回答

Redis 分布式锁把锁状态放在各个进程都能访问的 Redis 中,利用原子条件写入决定谁获得锁,再通过租约和持有者标识管理锁的有效期与释放。

基本流程是:

  1. 加锁:为业务资源确定同一个锁键,用一次带 NX 和 EX/PX 的 SET 写入本次获取独有的随机标识及过期时间。键不存在才成功;只有确认成功后,才执行临界区。
  2. 持有:租约限制锁的有效时间,避免客户端崩溃后永久占锁。业务可能超过租期时,需要缩短临界区或在确认仍是持有者后原子续约。
  3. 释放:比较锁值是否仍等于本次标识,再删除,比较和删除要原子完成。通常用 Lua 脚本;Redis 8.4 起也可用 DELEX 的 IFEQ 条件删除。
  4. 竞争失败:按业务选择立即失败、限时等待或带退避的重试,不要无限空转。

这套机制提供的是有期限、受故障条件约束的互斥协调。锁过期不会停止原业务线程,异步主从切换也可能丢失锁记录;重要业务约束仍需数据库条件更新、唯一约束、幂等或下游版本校验配合。

完整解析与参考资料
来源 · 10 篇面经 · 2 份题集收起来源