最近在刷 Java 后端秋招提前批的面试经验,发现 Redis 相关问题被问得相当频繁。不是那种「Redis 有哪些数据结构」的泛泛问题,而是直接结合项目切入:如果你的简历中写了缓存优化,面试官会追问:缓存穿透、击穿、雪崩分别如何应对?为什么采用这些方案?线上实际是否实施过?

这道题看似是八股文,但很容易被深入追问。很多人一上来就背「穿透用布隆过滤器,击穿用互斥锁,雪崩加随机过期时间」,一旦面试官继续追问,就容易卡壳。建议按照「一次请求的完整路径」来回答,效果会更好。
首先需要清晰区分这三类问题。缓存穿透,说的是请求的数据本身就不存在。例如,有人持续查询 ID 为负数的商品,缓存和数据库中均不存在,每次请求都直接穿透到数据库,这才叫穿透。缓存击穿,说的是一个热点 key 突然过期了。比如首页推荐配置、爆款商品库存、秒杀活动信息等,平时依赖缓存承担压力,当某个热点 key 突然过期,大量请求同时涌向数据库。缓存雪崩,则是指大量 key 在同一时刻失效,或者 Redis 集群短时间内不可用。它不是一个热点 key 的问题,而是缓存层大面积失守。面试中先明确这些区别,后续讨论才有基础。否则,如果混淆了击穿和穿透,面试官容易认为你只是机械背诵。
谈到穿透,处理方式不止布隆过滤器一种。第一层是参数校验。如果业务规定商品 ID 必须为正整数,那么负数、超长字符串、明显不合法的枚举值应在入口处直接拦截,避免进入缓存层,更不应到达数据库。第二层是缓存空值。当数据库查询结果为空时,可将空结果也缓存起来,设置较短的过期时间,例如几十秒到几分钟。这样同一个不存在的 key 不会反复打数据库。布隆过滤器适用于更严重的场景:数据集合相对稳定、非法 key 数量庞大、数据库压力已显著上升。其特点是可能误判某个 key「存在」,但绝不会将真实存在的 key 误判为不存在,因此可作为前置过滤,但不可依赖其作为最终判断。一个稳妥的回答思路是:先进行入参校验,过滤明显非法请求;数据库查询为空时缓存空值,防止同一不存在的 key 反复穿透;若非法 key 规模较大,则进一步将合法 ID 集合放入布隆过滤器进行前置过滤。如果面试官追问「缓存空值是否会污染 Redis」,可以回答:空值应设置较短的 TTL、较小的 value,并且仅对高频不存在的 key 进行缓存,不必对所有未命中都无脑写入。
针对热点 key 击穿,互斥锁是常用方案,但需注意一个陷阱:不要使用「SETNX 后再 EXPIRE」。这两个命令并非原子操作,若中间进程崩溃,可能导致死锁。Redis 中更推荐使用一条 SET 命令同时携带 NX 和 EX 参数,将「只在不存在时设置」与「过期时间」合并为一个原子操作。解锁时还需校验 value,通常使用 Lua 脚本确保只删除自己持有的锁。若业务允许短暂使用旧数据,逻辑过期方案更为优雅:缓存中不仅存储数据,还额外存储一个逻辑过期时间。当请求发现逻辑过期后,不会立即让所有请求查询数据库,而是由一个线程异步刷新缓存,其他请求继续使用旧数据。这在首页配置、排行榜、推荐位等场景中很常见,用户看到几十秒前的旧数据影响不大,但数据库被击穿则是严重事故。一个完整的回答可以是:对于强一致性要求高的热点 key,采用互斥锁重建缓存;对于读多写少、允许短暂旧值的场景,采用逻辑过期加异步刷新;锁必须设置过期时间,value 需唯一,删除锁时需校验 owner。这样的回答比单纯背诵「互斥锁」更有深度。
缓存雪崩的典型原因有两个。一是大量 key 同时过期,例如批量导入缓存时统一设置 30 分钟,半小时后集体失效。通过随机 TTL 即可缓解:在基础过期时间上增加随机偏移,使 key 的失效时间分散开。二是缓存服务本身不可用,这并非随机 TTL 能解决,需要评估系统是否具备本地缓存、限流、降级、熔断等机制,以及核心数据能否提前预热。面试时可按层次阐述:在过期时间层,TTL 增加随机值,防止同批 key 同时失效;在热点数据层,上线或活动前进行预热,避免用户请求触发重建;在系统保护层,采用本地缓存兜底、接口限流、数据库保护、非核心功能降级等措施。如果你的项目只是普通后台系统,没有高并发活动,不必强行编造秒杀经验,可以如实说:「我没有做过秒杀,但在后台字典、配置缓存中会使用随机 TTL,避免定时任务批量刷新导致同时过期。」这个回答反而更像真的。
面试官真正想了解的并非名词概念,而是你项目中的实际应用。如果简历中提到了 Redis,应提前思考清楚这些问题:哪些数据使用了缓存?为什么这些数据适合缓存?key 如何设计?是否包含前缀、版本号、业务 ID?TTL 设置多久?是固定值还是带随机?数据更新时,采取删除缓存、更新缓存还是延迟双删?如果缓存未命中,数据库能否承受压力?是否有限流措施?这比背诵十页 Redis 面试题更为重要。
