得物二面:什么是缓存击穿、缓存穿透、缓存雪崩?(修订版)
在线 Java 面试刷题已更新239题https://www.quanxiaoha.com/java-interview面试考察点基础掌握度面试官不仅仅是想知道这三个概念的定义更是想知道你是否能清晰区分它们的触发场景查不到 vs 过期了 vs 集体过期以及各自对应的解决方案。方案设计能力考察你是否了解布隆过滤器、互斥锁、随机 TTL、熔断降级等生产级别的防护手段而不是只停留在 加缓存 这种表面回答。架构思维能否认识到这三类问题本质上都是 缓存失效 → 大量请求打到 DB 的不同变体需要从缓存层、应用层、数据库层多维度防御。核心答案问题触发场景核心特征首选方案缓存穿透查询根本不存在的数据缓存和 DB 都没有每次都穿透布隆过滤器 空值缓存缓存击穿热点 Key突然过期大量并发请求同时打到 DB互斥锁 逻辑过期缓存雪崩大量 Key同时过期或 Redis 宕机瞬间全部请求压向 DB随机 TTL Redis 高可用 熔断降级一句话结论穿透是 查的东西根本不存在击穿是 一个热点 Key 过期了雪崩是 一堆 Key 同时过期或 Redis 挂了。三者本质都是缓存挡不住请求DB 被压垮。深度解析一、缓存穿透查的东西根本不存在img上图展示了缓存穿透的核心问题请求的数据在缓存和数据库中都不存在比如查询id -1的用户缓存里没有数据库里也没有。因为没有数据所以也无法写入缓存下次同样的请求还是会穿透到数据库。恶意攻击者可以利用这一点大量发送查询不存在数据的请求直接把数据库打挂。解决方案一布隆过滤器推荐img上图展示了布隆过滤器的防护逻辑在请求到达缓存之前先经过布隆过滤器快速判断数据是否存在。布隆过滤器判断不存在则一定不存在可以直接拒绝请求不查缓存也不查 DB。判断存在则可能存在有误判率走正常的缓存 → DB 查询流程。适合数据相对固定、可预加载的场景如商品 ID、用户 ID。解决方案二缓存空值public Object getUserById(Long id) { String key user: id; // 1. 查缓存 String value redis.get(key); if (value ! null) { // 命中缓存包括空值缓存 returnNULL.equals(value) ? null : deserialize(value); } // 2. 查数据库 Object user userDao.findById(id); if (user ! null) { // 正常数据写入缓存TTL 30 分钟 redis.set(key, serialize(user), 30, TimeUnit.MINUTES); } else { // 空值也要缓存TTL 设短一些比如 5 分钟 redis.set(key, NULL, 5, TimeUnit.MINUTES); } return user; }关键点当数据库也查不到时往缓存中写入一个空值标记如NULL并设置一个较短的 TTL。下次同样的请求会命中这个空值缓存直接返回不再打到 DB。注意空值 TTL 不能设太长否则当数据真正被插入后缓存中的空值会阻止读取到新数据。方案对比方案优点缺点适用场景布隆过滤器内存占用极小判断高效有误判率不支持删除需预加载数据相对固定ID 类查询缓存空值实现简单通用浪费缓存空间可能数据不一致查询条件多样无法预加载二、缓存击穿一个热点 Key 突然过期img上图展示了缓存击穿的核心问题某个热点 Key如热门商品信息、首页推荐数据在缓存中过期了。过期的瞬间大量并发请求同时发现缓存 miss全部涌向数据库。典型场景秒杀商品详情、热门文章、微博热搜等高并发读的热点数据。解决方案一互斥锁推荐public Object getHotData(String key) { // 1. 查缓存 String value redis.get(key); if (value ! null) { return deserialize(value); } // 2. 缓存 miss尝试获取分布式锁只让一个请求去查 DB String lockKey lock: key; boolean locked redis.set(lockKey, 1, NX, EX, 10); if (locked) { try { // 3. 获取锁成功double-check 缓存可能被其他线程写入了 value redis.get(key); if (value ! null) { return deserialize(value); } // 4. 查数据库 Object data db.query(key); // 5. 写入缓存 redis.set(key, serialize(data), 30, TimeUnit.MINUTES); return data; } finally { // 6. 释放锁 redis.del(lockKey); } } else { // 7. 获取锁失败说明其他线程在查 DB稍等后重试 Thread.sleep(100); return getHotData(key); // 递归重试 } }关键点大量请求中只有获取到分布式锁的那个请求去查数据库其他请求等待后重试。其他请求重试时缓存可能已经被第一个请求写入了直接命中缓存返回。Double-check获取锁后再查一次缓存避免重复查 DB。解决方案二逻辑过期不设 TTLimg上图展示了逻辑过期的核心思路缓存数据中包含一个逻辑过期时间字段但 Redis 中不设物理 TTL数据永远不会被 Redis 主动删除。读取时判断逻辑过期时间未过期直接返回已过期则获取互斥锁获取成功后异步开启一个线程去更新缓存当前请求返回旧数据。其他请求发现过期但拿不到锁也直接返回旧数据。适合对一致性要求不高、但高可用要求高的场景如商品详情页旧数据短暂展示可以接受。方案对比方案一致性性能复杂度适用场景互斥锁强一致同一时刻只有一个数据源有等待时间低一致性要求高逻辑过期最终一致短暂返回旧数据无等待性能最佳中高可用优先允许短暂不一致三、缓存雪崩大量 Key 同时过期或 Redis 宕机img上图展示了缓存雪崩的两种触发场景场景一大量 Key 同时过期。如果一批 Key 的 TTL 设成一样的比如批量导入数据时都设了 30 分钟到期后全部失效瞬间大量请求全部打到 DB。场景二Redis 宕机。缓存层整体不可用所有请求直接打到数据库数据库扛不住就整体雪崩。与击穿的区别击穿是 一个热点 Key 过期雪崩是 大量 Key 集体过期范围更广危害更大。解决方案多维度防御img上图展示了缓存雪崩的四层防御体系第一层随机 TTL。给缓存过期时间加一个随机偏移量把集中过期打散成分散过期。这是最简单也最有效的手段。第二层Redis 高可用。用哨兵或集群模式保证 Redis 本身不会单点故障。还可以加一层本地缓存如 Caffeine作为二级缓存兜底。第三层熔断降级。当 DB 压力过大时通过 Sentinel 等熔断框架直接拒绝部分请求返回降级数据如默认值、温馨提示保护数据库不被打挂。第四层限流排队。用令牌桶或漏桶算法控制打到 DB 的请求速率避免瞬时流量压垮数据库。随机 TTL 代码示例// 批量设置缓存时给 TTL 加随机偏移量 public void batchSetCache(MapString, Object dataMap) { Random random new Random(); for (Map.EntryString, Object entry : dataMap.entrySet()) { // 基础 TTL 30 分钟 0~10 分钟随机偏移 long ttl 30 * 60 random.nextInt(10 * 60); redis.set(entry.getKey(), serialize(entry.getValue()), (int) ttl, TimeUnit.SECONDS); } }四、三者对比总结img面试高频追问追问一布隆过滤器有误判怎么办布隆过滤器判断 存在 时有一定误判率可能实际不存在但判断 不存在 时是 100% 准确的。可以通过增大位数组大小和增加哈希函数个数来降低误判率。生产环境通常设置误判率在 1% 以下即可。如果误判了最多也就是多查一次数据库不会造成严重后果。追问二互斥锁方案中如果查 DB 的线程挂了怎么办锁会不会死锁分布式锁必须设置过期时间如 10 秒即使持有锁的线程挂了锁也会自动释放。但要注意锁过期时间要大于 DB 查询时间否则锁提前释放会导致其他线程也去查 DB。如果担心可以在代码中加try-finally确保锁一定被释放。追问三如果 Redis 宕机了怎么办第一道防线是Redis 高可用哨兵/集群保证单节点故障不影响整体服务。第二道防线是本地缓存Caffeine、Guava CacheRedis 不可用时退而求其次用本地缓存。第三道防线是熔断降级Redis 和本地缓存都不行时直接返回降级数据保护数据库。常见面试变体变体一缓存穿透怎么解决变体二热点 Key 过期了怎么办变体三如何防止大量 Key 同时过期变体四Redis 宕机了怎么办变体五你项目中遇到过缓存相关的问题吗怎么解决的记忆口诀穿透查的东西不存在 —— 布隆过滤挡在前空值缓存兜在后。击穿一个热点突然过期 —— 互斥锁只放一个去查 DB逻辑过期异步更新不阻塞。雪崩大量 Key 集体过期 —— 随机 TTL 打散时间高可用防宕机熔断限流保命。核心区别穿透是 没有击穿是 一个没了雪崩是 一堆没了。总结缓存击穿、穿透、雪崩本质上都是 缓存失效 → 请求打到 DB → DB 被压垮 的不同变体。穿透是查询不存在的数据用布隆过滤器和空值缓存解决击穿是热点 Key 过期用互斥锁或逻辑过期解决雪崩是大量 Key 同时过期或 Redis 宕机用随机 TTL 高可用 熔断降级多维度防御。生产环境需要多层防护不能只依赖单一方案。 欢迎加入小哈的星球你将获得:专属的项目实战多个项目 / 1v1 提问 /Java 学习路线 /学习打卡 / 每月赠书 / 社群讨论新项目《Spring AI 项目实战》正在更新中..., 基于 Spring AI Spring Boot 3.x JDK 21;《从零手撸仿小红书微服务架构》 已完结基于 Spring Cloud Alibaba Spring Boot 3.x JDK 17..., 点击查看项目介绍演示地址http://116.62.199.48:7070/《从零手撸前后端分离博客项目全栈开发》2期已完结,演示链接http://116.62.199.48/;专栏阅读地址https://www.quanxiaoha.com/column截止目前累计输出 100w 字讲解图 4013 张还在持续爆肝中..后续还会上新更多项目目标是将 Java 领域典型的项目都整一波如秒杀系统, 在线商城, IM 即时通讯Spring Cloud Alibaba 等等戳我加入学习解锁全部项目已有4500小伙伴加入1. 我的私密学习小圈子从0到1手撸企业实战项目~ 2. 重磅JetBrains 正式发布全新的 AI 开发工具定名 AI IDE AIR 3. 华为二面Redis 分布式锁如何实现修订版 4. 用 SpringBoot Tess4j 优雅实现图像文字识别真香最近面试BAT整理一份面试资料《Java面试BATJ通关手册》覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。 获取方式点“在看”关注公众号并回复 Java 领取更多内容陆续奉上。PS因公众号平台更改了推送规则如果不想错过内容记得读完点一下“在看”加个“星标”这样每次新文章推送才会第一时间出现在你的订阅列表里。 点“在看”支持小哈呀谢谢啦