☰
Redis面试核心考点系统梳理:缓存穿透、持久化、分布式锁与高可用架构
2026/10/13 4:54:15 网站建设 项目流程

最近后台收到不少准备面试的朋友留言,问 Redis 到底该怎么复习。说实话,Redis 这块在面试里几乎是必考项,从缓存三大问题到分布式锁,再到底层数据结构,每一层都能问出花来。网上也能搜到不少“50 道高频真题+答案”之类的合集,但大部分都只是把题和答案贴出来,背完第二天就忘。

我的建议是:别急着背题,先理解 Redis 的设计逻辑。面试官问来问去,核心其实就那几个点——缓存一致性、持久化可靠性、高可用架构、性能与资源权衡。只要把这几个主线打通,题目怎么变你都能接住。这篇文章我按自己的复习路径,把这些考点系统地拆了一遍,每个问题不光给答案,还把背后的原理和面试官的意图讲清楚,适合正在准备面试的同学,也适合想系统梳理 Redis 知识体系的开发者。

1. 缓存三大经典问题:穿透、击穿、雪崩

1.1 缓存穿透:查询一个不存在的数据

这个问题十个面试有八个会问,而且通常会连环追问。缓存穿透指的是请求的数据在缓存和数据库里都不存在,每次请求都直接打到数据库上。如果有人恶意构造大量不存在的 key,数据库压力会瞬间被打满。

我见过很多人的答案只停留在“缓存空值”这层,其实面试官想听到的是完整的思路。空值缓存很好理解:某个 key 查出来是空,也把它写进缓存,TTL 设短一点,比如 60 秒。但这个方案有个缺陷——如果恶意请求的 key 是随机生成的,你根本没法穷举,缓存空值也就失去了意义。

所以还要配合布隆过滤器。布隆过滤器的思路是用 N 个哈希函数把 key 映射到位数组上,判断“一定不存在”和“可能存在”。把数据库里已有的 key 都提前存进过滤器,请求进来先过过滤器,如果判断 key 不存在,直接返回,压根不会经过数据库。布隆过滤器有误判率,但不存在漏判——它说“不存在”就一定不存在,这一点正好用在穿透场景。

我还被追问过一个细节:误判率怎么控制?这是位数组长度和哈希函数数量共同决定的,位数组越长、哈希函数越多,误判率越低,但占用的内存也越大。实际项目里通常把误判率控制在 1% 左右,bit 数组长度大概是数据量的 10 到 20 倍。另外,布隆过滤器不支持删除操作,所以维护成本也在那里,面试时可以主动提一句,显得你考虑过落地问题。

1.2 缓存击穿:热点 key 突然失效

注意别把穿透和击穿搞混,这是我面试时犯过的错。击穿指的是某个热点 key 在过期的一瞬间,大量请求同时打到数据库。这和穿透的本质区别是:key 是真实存在的,只是刚好在那一刻缓存失效了。

解决方案最经典的是互斥锁。简单说,当缓存没有命中时,不是所有请求都去查数据库,而是先尝试加锁。拿到锁的请求去查数据库、回填缓存,其他请求要么等一会再查缓存,要么直接返回默认值。实现上可以用 Redis 的SET key value NX EX,保证同一时刻只有一个线程能拿到锁。

另一个思路是逻辑过期。不放 TTL,而是在 value 里塞一个逻辑过期时间。每次读取时判断逻辑时间是否过期,如果没过期直接返回;如果过期了,就尝试获取互斥锁,拿到锁的线程去重建缓存,其他线程先拿到旧值。这个方案的好处是服务不会因为缓存过期出现空窗期,坏处是实现复杂度高,而且旧值会被多返回一段时间。

还有一个大家常说的“永不过期”,其实严格说不是真的不设 TTL,而是不给 key 设过期时间,后台起一个异步任务定期刷新热点 key。这种方案适合极端热点,但对数据一致性要求高的场景不适用。

1.3 缓存雪崩:大面积 key 同时失效

雪崩和击穿的区别在于范围。击穿是单个热点 key 失效,雪崩是大量 key 在同一时间段失效,数据库瞬间被流量冲垮。造成雪崩的原因通常是两个:一是设置了相同的过期时间,大批 key 同时到期;二是 Redis 实例本身宕机了。

针对第一种情况,解决办法是给 TTL 加随机扰动。比如基础过期时间是 1 小时,实际设置的时候加上一个 1 到 300 秒的随机值,把过期时间分散开。我见过一些不够谨慎的项目,所有缓存 key 都用固定的 TTL,上线一段时间后定时出现一波慢查询,多半就是这个原因。

针对 Redis 宕机的情况,要靠高可用架构兜底,比如哨兵模式做主从切换,或者 Cluster 集群分片。但架构切换需要时间,这期间可以用本地缓存做降级——在应用层加一层 Guava Cache 或者 Caffeine,数据库压力就能被挡住一部分。面试时可以补充一句:降级方案的取舍是能接受一定程度的脏数据,换稳定性。

2. 持久化机制:RDB 与 AOF 怎么选

2.1 RDB:快照的优缺点

RDB 就是把内存里的全量数据以二进制形式写入磁盘,核心命令是SAVE和BGSAVE。SAVE是同步的,会阻塞 Redis 主线程,生产环境基本不用;BGSAVE是异步的,由 fork 出的子进程负责落盘。

面试常问的是 fork 过程为什么不会阻塞太久。这里涉及 Linux 的写时复制技术——子进程刚 fork 出来时,父子进程共享同一份物理内存,并不需要真的把内存复制一份。只有当父进程要修改某个内存页时,才复制这一页给子进程。所以 fork 本身很快,阻塞时间主要取决于内存页表的规模。

但要注意,如果写入量非常大,父进程为了复制被修改的内存页,会产生额外的内存开销,极端情况下可能触发内存不足。我在一个写密集型的项目里就遇到过,为了做 RDB 快照,内存峰值涨了将近一倍。所以做 RDB 持久化时,服务器内存要留足余量,比如 4G 的内存至少得留 1G 的缓冲空间。

RDB 最大的问题是数据丢失窗口。默认配置下,BGSAVE的触发条件可能是 900 秒内有一次写就执行,或者 300 秒内 100 次写、60 秒内 10000 次写。这意味着如果 Redis 在这之间突然宕机,这几分钟内的写入数据全丢。对数据零容忍的场景,单靠 RDB 不够。

2.2 AOF:日志追加与重写机制

AOF 记录的是每次写命令本身,相当于把执行过的命令按顺序存下来,恢复时重放一遍。它有三个写回策略:

  • always:每条写命令都同步 fsync 到磁盘,最安全,但性能开销最大。
  • everysec:每秒 fsync 一次,兼顾性能和数据安全,最多丢一秒数据,也是 Redis 默认配置。
  • no:交给操作系统决定什么时候刷盘,性能最好,但数据丢失窗口不可控。

生产环境我通常建议用everysec,性能可以接受,也能把数据丢失控制在可接受范围内。

AOF 最需要理解的是重写机制。随着运行时间变长,AOF 文件会越来越大,恢复时重放耗时也越久。重写就是根据当前内存里的数据状态,生成最精简的写入命令集合。比如某个 key 被 set 了一百次,重写后只保留最后一次 set 的命令。

重写过程同样依赖 fork 子进程,而且重写期间的增量写命令会先缓存到重写缓冲区里,等子进程把新 AOF 文件写完后,再把增量命令追加进去,最后原子替换旧文件。这里有个细节,如果重写期间写入量特别大,重写缓冲区占用的内存也要纳入容量规划。

2.3 混合持久化:多数人的最终选择

两个方案都有短板,RDB 丢数据,AOF 恢复慢。所以 Redis 4.0 之后提供了混合持久化:以 RDB 快照作为基础,在快照之后增加的写操作以 AOF 命令的方式追加在文件后面。这样既保证了重启后恢复速度快,又缩小了数据丢失窗口。

面试时如果被问到“生产环境怎么配置持久化”,我一般这么答:默认的 RDB 配置保留,AOF 设置everysec,开启aof-use-rdb-preamble混合持久化。核心数据可以搭配主从节点做多副本,即使单机文件损坏,也能从从节点补数据。这套组合可以覆盖绝大多数业务场景。

3. 过期策略与内存淘汰机制

3.1 三种过期删除策略

Redis 的 key 设置了 TTL,但到底什么时候删?这背后有三套策略。

定时删除:key 过期时立刻删除。优点是内存释放及时;缺点是过期 key 一多,删除动作会占大量 CPU 时间,阻塞主线程,生产环境不会直接用。

惰性删除:每次读取 key 时才检查是否过期,过期就删除并返回空。好处是 CPU 开销小,坏处是一批过期 key 如果没有被访问,就一直占着内存不放,会造成一定程度的内存浪费。

定期删除:每隔一段时间随机抽取一批 key 检查,把过期的删掉。这是前两者的折中方案,也是 Redis 实际采用的策略。注意“随机抽取”这个词——每次只抽一部分,目的是把删除动作的耗时控制在可接受范围内。

3.2 从缓存淘汰到置换算法

过期删除解决的是“到了时间怎么删”,八种淘汰策略解决的是“内存满了怎么办”。高频考的是noeviction、allkeys-lru、allkeys-lfu以及两种 volatile 版本。

noeviction是默认策略,内存满了直接拒绝写请求并报错。很多面试的人不知道这一点,下意识以为 Redis 满了会自动淘汰——实际上默认是真报错。

allkeys-lru是从所有 key 中,按最近最少使用原则淘汰。这里我踩过坑:如果业务里存在一次性的大批数据导入,LRU 容易把真正热点的 key 挤掉。用allkeys-lfu能好一些,LFU 按访问频率淘汰,还能区分“最近被访问过但频率很低”的冷数据。Redis 的 LFU 实现还带一个对数衰减机制,避免长期未访问的 key 频率一直不减。

面试官如果问到底层实现,可以答:LRU/LFU 的近似实现是基于抽样 + 维护一个淘汰池,而不是对全量 key 精确排序。这样设计是为了避免精确排序带来的内存和 CPU 开销。能答到这里,说明你确实研究过源码。

4. 缓存与数据库的一致性

4.1 先删缓存还是先更新数据库

这个问题的标准答案是 Cache Aside 模式:读的时候先读缓存,没命中就读数据库,然后回填缓存;写的时候先更新数据库,再删缓存。注意,是“删缓存”,不是“更新缓存”。

为什么不更新缓存而要删除?因为更新缓存的操作容易产生并发问题。比如两个线程同时写,一个把 cache 里的值改成 A,另一个改成 B,最后缓存里留下的是后写的那个,但数据库里的值可能对应的是先写的那个,两边就对不上了。删除缓存的话,下次读取时缓存缺失,会从数据库重新拉取,相当于强制“自愈”。

那先更新数据库再删缓存,就没有问题吗?有一个经典的并发场景:线程 A 更新完数据库,还没有删缓存,线程 B 读取到了旧缓存,然后缓存被 A 删除。表面看没问题,但极端情况下,线程 B 读到的旧值被回填到了缓存,而数据库已经是新值,就出现不一致。解决思路是延迟双删——删完缓存之后,隔几百毫秒再删一次,把中间窗口期可能回填的脏数据清掉。

4.2 订阅 binlog 与最终一致性

延迟双删只是一个弥补措施,更好的思路是让数据同步变成一个自动化的异步过程。比如使用 Canal 订阅 MySQL 的 binlog,当数据库发生写操作时,同步删除或者说更新对应的缓存。

这套方案的核心优势是业务代码里不需要再关注缓存更新逻辑。缺点也很明显——引入中间件,链路变复杂,而且 binlog 同步有延迟,做不到强一致,只能做到最终一致。

面试时如果被问到“能不能做到强一致”,我的回答是:缓存和数据库是两个独立存储,强一致必须付出事务和锁的代价,性能会大打折扣。大多数互联网场景接受最终一致性,关键是把不一致的时间窗口压缩到足够小。这个回答既诚实,又能体现架构取舍能力。

5. 分布式锁与原子性保障

5.1 SET NX EX 与可重入问题

Redis 实现分布式锁最常用的就是一条命令:SET lock_key value NX EX 30000。NX 保证只有 key 不存在时才能设置成功,EX 设置过期时间,防的是拿到锁的进程崩溃后锁永不释放。

这个方案的要点是命令的原子性。我见过有人把这一步拆成两条命令,先SETNX后EXPIRE,一旦两步之间进程崩溃,锁就变成死锁,这是最常见的 bug。所以面试时一定要强调用一条原子命令,或者用 Lua 脚本把两步包起来。

进一步会被问到的问题是:锁过期了,但业务还没执行完怎么办?这就涉及“续期”问题。通常可以在客户端实现一个看门狗线程,在锁快过期时自动续期。一些开源客户端库自带这个能力,原理不复杂:定期检查锁的剩余 TTL,如果业务还在运行,就用 Lua 脚本把 TTL 重置。

还有一个容易被问到的点:锁的 value 到底该放什么?我习惯放一个全局唯一的 requestId,释放锁时先比对 value,是自己的锁才删除。如果不做这一步,有可能自己持有的锁已经被过期自动释放了,然后被别人抢到,你自己把别人刚设置的锁删了。

5.2 RedLock 的真实争议

Redis 作者提出的 RedLock 算法,思路是向多个独立的 Redis 节点申请锁,超过半数成功才认为获得锁。这个方案的目的是解决单点故障问题和避免锁丢失。

但 RedLock 在业界是有争议的,而且很多面试官自己也知道这点。它依赖系统时钟,如果某个节点的时钟发生跳变,锁的 TTL 判断会出问题。分布式系统领域甚至有专门的论文分析过 RedLock 的安全性。面试时如果遇到这个话题,我建议先讲清楚 RedLock 的原理,再补一句:现在更主流的做法是直接把锁的基础依赖在 Redis 主节点上,配合高可用架构,同时业务侧做好锁超时的兜底逻辑。别把 RedLock 吹成灵丹妙药,这反而能让面试官看到你的判断力。

6. 底层数据结构与使用场景

6.1 String、Hash、List、Set、ZSet 不只是 API

面试里对数据结构的考察通常分为两层:第一层是你知不知道有哪些类型,第二层是懂不懂每种类型底层用的什么编码。第二层才是拉差距的地方。

String 的底层编码可能是 int 或者 embstr、raw。整数值会用 int 编码,短字符串用 embstr,长度超阈值后转成 raw。Hash 在元素少时用 ziplist 紧凑存储,元素多后升级成 hashtable。List 的底层是 quicklist,本质是多个 ziplist 组成的链表。Set 的底层要么是 intset,要么是 hashtable。ZSet 的底层最复杂,元素少时用 ziplist,规模上来之后用 skiplist + dict 的组合。

面试高频问题是:ZSet 为什么要用跳表而不是平衡树?我的理解是:跳表实现简单,代码容易写对,支持范围查询的能力也不差。平衡树实现复杂,红黑树的操作细节很容易出 bug。跳表还支持通过调整层高生成概率来节省内存。Redis 的排序集合同时用 dict 存成员到分值的映射,所以按成员查分值的时间复杂度是 O(1),按排名或者分值范围查用跳表,两者结合,互为补充。

6.2 位图与 HyperLogLog 的低内存技巧

除了五种基本类型,Redis 还提供了位图、HyperLogLog 和地理坐标这类高级数据结构。面试时主动提这些是加分项。

位图本质是 String 类型上的位级操作。用户签到、在线状态判断这类场景,用位图可以把存储压缩到极致——一亿个用户只需要 12.5MB 左右就能记录所有人的签到状态。这种“数据压缩”的思维方式,面试官很看重。

HyperLogLog 则是用固定大小的内存做基数统计。比如统计页面的 UV,10 亿级别的去重计数,误差在 1% 以内,内存只需要 12KB。面试时被问到“怎么统计本周活跃用户”,先想到 HyperLogLog,再补一句“误差可接受性”,就是一次很加分的回答。现实场景里,UV 这种统计本来就不需要精确值,1% 以内的误差完全够用。

7. 高可用架构:主从、哨兵与集群

7.1 主从复制与读写分离的代价

主从复制的基本流程是:从节点启动时发 PSYNC 命令,主节点 fork 子进程生成 RDB 快照发给从节点,后续写命令继续以命令流的形式同步给从节点。这套机制解决了单点容灾问题,也让读写分离成为可能。

但读写分离有代价。主从之间的同步存在延迟,如果从节点刚写完主节点的数据就立刻去从节点读,很可能读不到最新数据。对有强一致需求的场景,读操作不能分流到从节点。我见过因为读写分离导致用户下单后查不到订单的事故,只能通过强制主库读来解决。

7.2 哨兵与脑裂

哨兵的作用不只是主节点挂了自动切换,它还承担了监控、通知、配置下发的职责。三个哨兵节点组成一个团体,通过投票机制决定是否真的执行故障转移——这是为了避免一个哨兵误判就切换主节点,造成不必要的抖动。

哨兵模式下最容易被问的是脑裂问题:主节点网络被隔离,但进程没挂,哨兵把从节点提升为新主节点,等原主节点恢复后,它就成了一个孤立的“假主节点”。这段时间内写入原主节点的数据,会因为新主节点上线而丢失。Redis 的min-slaves-to-write和min-slaves-max-lag参数可以一定程度规避这种问题:当主节点发现从节点连接数异常时,主动拒绝写入请求,宁可用短暂不可用换数据安全。这个参数配置在面试里提一下,会非常亮眼。

7.3 Cluster 集群与分片原理

Cluster 模式把数据分布在 16384 个哈希槽里,每个节点负责一部分槽。写入某个 key 时,先计算哈希槽,再路由到对应节点。客户端如果连到了错误的节点,节点会返回 MOVED 错误,告诉客户端应该去哪个节点。

这里有个容易混淆的点:Cluster 的哈希槽和一致性哈希不是一回事。一致性哈希通过哈希环和虚拟节点解决数据分布与节点变更问题;Cluster 用的是固定数量的槽位方案,每个节点负责一段槽。槽位方案的优势是数据分布足够均匀,不需要处理哈希环上的偏斜问题;缺点是集群节点规模有上限,因为槽位个数是固定的。

面试时如果被问到某 key 到底落在哪个节点,可以用cluster countkeysinslot这类命令实测,也可以直接解释哈希槽路由过程。能聊到“根据 key 的 CRC16 结果对 16384 取模”,就已经比大多数候选人深了一层。

8. 面试实战问答技巧

8.1 用“追问链”复习比死记硬背更有效

我自己的经验是,复习 Redis 不要按题号背,要按追问链走。比如从“缓存穿透怎么解决”开始,答完布隆过滤器之后,面试官大概率继续问“布隆过滤器误判率怎么算”“能不能删除”“本地版本和 Redis 版本选哪个”。你要模拟这个追问链往下准备,而不是只准备原始问题。

以下是我根据自己面过的几十场技术面整理的高频追问链:

  • 缓存击穿 → 互斥锁如何避免死锁 → 锁过期业务没执行完怎么办 → 看门狗续期实现
  • AOF 重写 → 重写期间写命令怎么处理 → fork 子进程内存开销 → 写时复制原理
  • Redis 是单线程为什么还快 → IO 多路复用 → 单线程的好处和局限
  • 缓存淘汰策略 → LRU 和 LFU 区别 → 业务里选哪种、为什么
  • 分布式锁 → 锁过期问题 → 锁粒度怎么控制 → 是否可重入

8.2 答题节奏:结论先行,再补细节

面试答题有节奏问题,很多候选人一上来就倒豆子一样把底层原理全说了,反而没突出重点。好的节奏应该是结论先行:先给一个简明结论,再说关键依据,最后补充扩展细节。比如问到 Redis 为什么快,先抛“内存存储 + 单线程避免竞争 + IO 多路复用”,然后展开每个点,再补一句“单线程局限性在大 key 场景会被放大”。

最后可以准备一句“你对 Redis 未来的发展方向有什么理解”之类的开放题。这个问题没有标准答案,但考察的是你有没有持续关注社区动态。我会提到 Redis 对多线程 IO 的逐步引入、持久化性能持续优化,以及对向量检索等新数据结构的探索。能从一个“工具使用者”的角度跳到“生态观察者”的角度,这道题基本就稳了。

9. 一些写在最后的话

翻来覆去说这么多,最后分享一个自己的复习心得:准备面试题不是目的,借这个机会把 Redis 的工作机制系统过一遍,收益是长期的。我当年就是借着面试复习,把 RDB 和 AOF 的源码相关文档啃了一遍,后来线上排查问题的时候,正是靠着对写时复制和 AOF 重写缓冲区的理解,才没有在半夜抓瞎。

如果你时间有限,优先把缓存三大问题、持久化、过期淘汰、分布式锁这四个板块吃透,它们占据了 Redis 面试题的七成份额。剩下的数据类型和集群架构,做到能讲清楚原理、能画出示意图就行。另外强烈建议自己动手在本地搭一个 Redis 实例,把INFO、MEMORY DOCTOR、SLOWLOG GET这些命令玩熟,面试时提到“我在排查某次延迟时用过 SLOWLOG”,比背一百道题都管用。

技术面试说到底考察的是“理解的深度”和“表达的清晰度”,题库只是载体。希望这篇梳理能让你少走一些弯路,把这门必修课一次拿下。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询