最近梳理了Java后端秋招提前批的面试经验,发现Redis几乎是必问的考点。不同于“Redis有哪些数据结构”这类基础题,面试官往往会从项目场景切入:你的简历上写到了缓存优化,那么缓存穿透、缓存击穿、缓存雪崩分别如何处理?为什么这样处理?线上是否真正实施过?
这道题看似是经典的八股文,但实际上很容易被追问到具体细节。许多求职者一上来就背诵“穿透用布隆过滤器,击穿用互斥锁,雪崩加随机过期时间”,面试官再深入问几句,往往就答不上来了。
更推荐的做法是,按照“一次请求的完整流程”来组织回答思路。
首先明确三个概念的区别
首先,缓存穿透是指请求的数据在数据库中根本不存在。例如,有人持续使用一个不存在的商品ID发起查询,缓存中无数据,数据库中也没有,每次请求都直接穿透到数据库。这才是真正的穿透场景。
缓存击穿则不同,它指的是一个热点key突然过期。例如首页推荐配置、爆款商品库存、秒杀活动信息等,平时这些数据都由缓存扛住,但一旦某个key失效,大量请求瞬间同时涌向数据库。
而缓存雪崩,是指一批key在同一时间点集体失效,或者Redis集群短时间内不可用。这不是单个热点key的问题,而是缓存层大面积失效。
面试时,首先清晰阐述这三个概念的区别,后续才有深入讨论的空间。否则,如果混淆了击穿和穿透,面试官通常会认为你只是背过题目。
缓存穿透:不要只提布隆过滤器
处理缓存穿透的第一层防线,其实是参数校验。如果业务上商品ID必须是正整数,那么负数、超长字符串、明显不合法的枚举值,都应该在入口处直接拦截。不要让这类请求进入缓存层,更不要让它打到数据库。
第二层才是缓存空值。当数据库查询不到结果时,可以将空结果也写入缓存,并将过期时间设置得短一些,比如几十秒到几分钟。这样,同一个不存在的key就不会反复穿透数据库。
布隆过滤器更适合更重的场景:数据集合相对稳定、非法key数量很多、数据库压力已经明显上升。它的特点是可能误判“存在”,但不会将真实存在的数据误判为不存在。因此,可以用它做前置过滤,但不能将其作为最终判断依据。
一个比较稳妥的回答可以这样组织:
- 先做入参校验,拦截明显非法的请求;
- 查库为空时缓存空值,避免同一个不存在的key反复击穿数据库;
- 如果非法key规模很大,再将合法id集合放入布隆过滤器进行前置判断。
如果面试官进一步追问“缓存空值会不会污染Redis”,可以这样回应:空值的TTL要短、value要小,并且只对高频不存在的key做缓存,而不是所有未命中都无脑写入。
缓存击穿:互斥锁不仅仅是SETNX
热点key过期时,最怕的就是大量线程同时查询数据库并同时回写缓存。
常见做法是加互斥锁:只有抢到锁的线程去查询数据库并重建缓存,其他线程稍等后重试,或直接返回旧值。
这里有一个常见陷阱:不要使用“SETNX后再EXPIRE”。这两个命令分开执行不是原子操作,中间如果进程崩溃,就可能留下死锁。Redis中更常见的做法是使用一条SET命令,带上NX和EX参数,将“只在不存在时设置”和“过期时间”放在同一个原子命令里。解锁时还要校验value,通常用Lua脚本保证只删除自己持有的锁。
如果业务允许短暂使用旧数据,逻辑过期方案会更合适:缓存中不仅存放数据,还存放一个逻辑过期时间。请求到来时发现逻辑过期,不是立即让所有请求去查库,而是由一个线程异步刷新,其他请求先使用旧数据顶住。
这个方案在首页配置、排行榜、推荐位上很常见。用户看到几十秒前的旧数据,问题不大,但数据库被打爆可是真正的故障。
面试时,可以这样组织回答:
- 强一致要求高的热点key,使用互斥锁重建缓存;
- 读多写少、允许短暂旧值的热点key,使用逻辑过期加异步刷新;
- 锁要有过期时间,value要唯一,删除锁时需校验owner。
这几句话,比单纯背诵一个“互斥锁”要有分量得多。
缓存雪崩:随机TTL只是基础配置
造成雪崩的典型原因有两个。一是大量key同时过期。例如,批量导入缓存时全部设置为30分钟,半小时后它们一起失效。这种情况可以用随机TTL缓解:在基础过期时间上加上一段随机抖动,让key的失效时间分散开。二是缓存服务本身不可用。这就不是随机TTL能解决的了。需要看系统是否有本地缓存、限流、降级、熔断,以及核心数据是否能提前预热。
面试时可以按层次来回答:
- 过期时间层:TTL加随机值,避免同一批key同时失效。
- 热点数据层:上线或活动开始前做好预热,不等用户请求来触发重建。
- 系统保护层:本地缓存兜底、接口限流、数据库保护、非核心功能降级。
如果你的项目只是普通后台系统,没有高并发活动,也不要硬编秒杀场景。可以说“我没做过秒杀,但在后台字典、配置缓存里会做随机TTL,避免定时任务批量刷新导致同时过期”。这个回答反而更像真实的经验。
面试官真正想听的,不是名词术语
这道题到最后,通常会落到项目上。
如果你的简历里写了Redis,就提前把这几个问题想清楚:
- 哪些数据用了缓存?为什么这些数据适合用缓存?
- key怎么设计的?是否有前缀、版本号、业务id?
- TTL设多久?是固定值还是带随机值?
- 数据更新时,是删除缓存、更新缓存,还是延迟双删?
- 如果缓存未命中,数据库能否扛住?是否有限流?
这些问题的价值,比背诵十页Redis面试题更重要。
