缓存穿透、缓存击穿和缓存雪崩详解
缓存在高并发系统中是保护数据库的重要防线但实践中常会遇到三个棘手的现象缓存穿透、缓存击穿和缓存雪崩。理解它们的区别和应对方案是后端工程师的必修课。下面我就以一个电商商品查询场景为例结合代码逐一拆解。1. 缓存穿透是什么查询一个数据库和缓存中都不存在的数据。由于缓存没有命中每次请求都会穿透到数据库恶意攻击者可通过构造大量不存在的 ID 瞬间压垮数据库。举例商品 ID 自增攻击者并发请求productId -1此 ID 不可能存在。每次请求都绕过缓存直接访问数据库造成数据库查询压力激增。解决方案方案一缓存空对象当数据库查询结果为 null 时也将一个空值标识缓存起来并设置较短的过期时间如30秒。这样后续攻击请求会直接命中缓存不会到达数据库。public Product getProductById(Long productId) { String cacheKey product: productId; // 1.查缓存 Product product (Product) redisTemplate.opsForValue().get(cacheKey); if (product ! null) { return product; // 命中直接返回 } // 2.查数据库假设这里调用 productMapper.selectById product productMapper.selectById(productId); if (product ! null) { // 数据库有写入缓存过期1小时 redisTemplate.opsForValue().set(cacheKey, product, 1, TimeUnit.HOURS); } else { // 数据库没有缓存空对象过期30秒 redisTemplate.opsForValue().set(cacheKey, new Product(), 30, TimeUnit.SECONDS); } return product; }注意点空对象占用内存空间有限但要控制过期时间避免长期存在。若空值也有业务含义可缓存一个特殊标记对象如NullProduct。方案二布隆过滤器维护一个存储所有合法 key 的布隆过滤器请求先经过布隆过滤器判定如果认为不存在则直接返回不再查询缓存和数据库。这种方式内存占用小但存在误判率判定不存在时一定不存在判定存在时可能不存在。适合数据量极大的场景。// 初始化布隆过滤器使用 Guava 库 BloomFilterLong bloomFilter BloomFilter.create( Funnels.longFunnel(), 1000000, // 预计数据量 0.01); // 误判率 // 查询时先判断 if (!bloomFilter.mightContain(productId)) { return null; // 直接返回避免穿透 } // 后续走缓存-数据库流程...生产上常用 Redisson 的布隆过滤器或 Redis 插件来实现分布式布隆过滤器。2. 缓存击穿是什么某个热点数据如秒杀商品缓存恰好过期此时大量并发请求同时查询该 key因缓存失效所有请求瞬间打到数据库导致数据库瞬时压力过大。举例爆款手机缓存 key 在 12:00:00 过期恰逢促销活动每秒上万请求涌入同一时刻发现缓存为空全部穿透到数据库数据库 CPU 直接 100%。解决方案核心思路保证同一个 key 同一时刻只有一个线程去加载数据其他线程等待或快速失败。方案一互斥锁分布式锁使用 Redis 的SETNX或 Redisson 的分布式锁实现互斥。public Product getProductWithLock(Long productId) { String cacheKey product: productId; Product product (Product) redisTemplate.opsForValue().get(cacheKey); if (product ! null) { return product; } // 获取分布式锁锁的key lock:product: productId RLock lock redissonClient.getLock(lock:product: productId); try { // 尝试加锁等待5秒锁有效期30秒 if (lock.tryLock(5, 30, TimeUnit.SECONDS)) { // 双重检查避免锁内再次查缓存 product (Product) redisTemplate.opsForValue().get(cacheKey); if (product ! null) { return product; } // 查数据库 product productMapper.selectById(productId); if (product ! null) { redisTemplate.opsForValue().set(cacheKey, product, 1, TimeUnit.HOURS); } else { // 缓存空对象防止穿透 redisTemplate.opsForValue().set(cacheKey, new Product(), 30, TimeUnit.SECONDS); } } else { // 获取锁失败可快速失败或自旋等待后重试 Thread.sleep(50); return getProductWithLock(productId); // 递归重试 } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { lock.unlock(); } return product; }方案二逻辑过期 异步刷新不设置物理过期时间TTL在 value 中存储一个逻辑过期时间字段。读取时若发现逻辑时间已过期返回旧值的同时异步更新缓存。这样永远不出现缓存失效瞬间的并发穿透。// 存储实体内部包含数据和过期时间戳 public class ProductWrapper { private Product product; private long expireTime; // 逻辑过期时间戳 } public Product getProductLogicalExpire(Long productId) { String cacheKey product: productId; ProductWrapper wrapper (ProductWrapper) redisTemplate.opsForValue().get(cacheKey); if (wrapper null) { // 缓存不存在直接查库并写入可能是冷数据并发不高 Product product productMapper.selectById(productId); wrapper new ProductWrapper(product, System.currentTimeMillis() 3600_000); redisTemplate.opsForValue().set(cacheKey, wrapper); return product; } if (wrapper.getExpireTime() System.currentTimeMillis()) { return wrapper.getProduct(); // 未过期直接返回 } // 逻辑过期尝试获取锁进行异步刷新 String lockKey lock:product: productId; boolean locked redisTemplate.opsForValue().setIfAbsent(lockKey, 1, 10, TimeUnit.SECONDS); if (locked) { // 异步更新缓存 executorService.submit(() - { Product product productMapper.selectById(productId); ProductWrapper newWrapper new ProductWrapper(product, System.currentTimeMillis() 3600_000); redisTemplate.opsForValue().set(cacheKey, newWrapper); redisTemplate.delete(lockKey); }); } // 返回旧数据即使过期也先用着 return wrapper.getProduct(); }3. 缓存雪崩是什么同一时刻大量缓存 key 同时过期或缓存服务器宕机导致海量请求直接访问数据库数据库不堪重负甚至引发连锁崩溃。举例某整点活动所有商品缓存同时设置了1小时过期。恰好在活动高峰时大量 key 集中失效数据库瞬间瘫痪。解决方案方案一过期时间加随机值在原有过期时间上追加一个随机秒数避免集中过期。// 设置缓存时基础过期时间随机偏移 int baseExpireSeconds 3600; // 1小时 int randomSeconds new Random().nextInt(600); // 0~600秒随机 redisTemplate.opsForValue().set(cacheKey, product, baseExpireSeconds randomSeconds, TimeUnit.SECONDS);方案二高可用架构对 Redis 使用主从哨兵或集群模式避免单点宕机。缓存层做好故障转移和自动恢复。方案三多级缓存 限流降级在应用本地增加一层 Caffeine 缓存或使用 Nginx 本地缓存。当 Redis 不可用时请求优先走本地缓存。同时引入 Hystrix / Sentinel 限流防止数据库被打死。// 使用 Caffeine 作为一级缓存Redis 为二级 CacheLong, Product localCache Caffeine.newBuilder() .expireAfterWrite(5, TimeUnit.MINUTES) .maximumSize(10000) .build(); public Product getProductMultiLevel(Long productId) { // 1. 查本地缓存 Product product localCache.getIfPresent(productId); if (product ! null) return product; // 2. 查 Redis product (Product) redisTemplate.opsForValue().get(product: productId); if (product ! null) { localCache.put(productId, product); return product; } // 3. 查数据库加锁防击穿此处省略 // ... return product; }三者对比总结现象原因核心解法穿透查询不存在的数据缓存空对象 / 布隆过滤器击穿热点 key 过期并发查库互斥锁 / 逻辑过期异步刷新雪崩大量 key 同时过期或服务宕机过期时间随机化 / 高可用 / 多级缓存实际开发中三种策略往往组合使用。例如商品查询接口同时采用布隆过滤器防穿透分布式锁防击穿过期时间随机化防雪崩。理解它们的本质才能在高并发场景下设计出稳定的缓存体系。

相关新闻

谷歌 AI 权力重组拆解:Hassabis 让位、Jeff Dean 创业,Gemini 4 谁来交卷

谷歌 AI 权力重组拆解:Hassabis 让位、Jeff Dean 创业,Gemini 4 谁来交卷

当地时间八月五日的深夜,谷歌投下了一枚让整个 AI 圈失眠的重磅消息。Google DeepMind 联合创始人兼 CEO Demis Hassabis 宣布卸任日常运营职务,转任 Google DeepMind 董事长,同时出任 Alphabet 首席科学家。同一天,在谷歌工作了整…

2026/10/20 23:45:12 阅读更多 →
具身智能协同机制研究:TVA与VLA的统一建模(2)

具身智能协同机制研究:TVA与VLA的统一建模(2)

前沿技术探索:TVA智能体(简称TVA)TVA智能体(亦称“TVA视觉智能体”或“AI智能体视觉”)是依托Transformer架构与“因式智能体”理论构建的通用视觉技术框架。它深度融合深度强化学习(DRL)、卷积…

2026/10/20 11:07:58 阅读更多 →
泛型编程的诞生(C++篇)

泛型编程的诞生(C++篇)

模板的深入理解 前言 模板非常的重要,它就是STL的根基。STL容器vector/list/set/map、算法sort全都是基于模板实现 我将从函数模板→类模板→模板实例化匹配→模板特化→分离编译,层层拆解,贯穿底层一、为什么需要模板?泛型编程的…

2026/10/19 17:58:37 阅读更多 →

最新新闻

Deepagents自动化测试实战:从断言设计到稳定性体系建设

Deepagents自动化测试实战:从断言设计到稳定性体系建设

做过AI Agent相关开发的朋友应该都有同感:写一个能跑通的代理不难,难的是让它稳定地跑完一百遍。LLM天然带有随机性,同样的输入,今天输出这个结果,明天就可能换个路径,哪怕最终答案都对,中间调用…

2026/10/20 23:44:36 阅读更多 →
AirMICROSD_1000直插式存储模块:嵌入式设备灵活扩容与量产可靠性实践

AirMICROSD_1000直插式存储模块:嵌入式设备灵活扩容与量产可靠性实践

去年秋天我在做一台边缘采集设备的量产改版,原方案用的是板载eMMC,结果代工厂那边封装炉温曲线一直调不理想,整板不良率居高不下。换方案的时候,朋友递过来一块AirMICROSD_1000直插式存储扩展模块,说你先试试这个。刚开…

2026/10/20 23:44:36 阅读更多 →
电热联合调度微电网运行优化模型:MATLAB+YALMIP实现解析

电热联合调度微电网运行优化模型:MATLAB+YALMIP实现解析

做微电网运行优化,绕不开一个朴素的问题:电负荷和热负荷明明共存于同一个园区,为什么大多数优化模型只盯着“电”在算?我最近在某园区微电网项目的经济调度分析里,把基于电热联合调度的微电网运行优化模型在 MATLAB 里…

2026/10/20 23:44:36 阅读更多 →
Java实现Word与PDF全文检索:Elasticsearch 7.x文档解析、IK分词与高亮查询实战

Java实现Word与PDF全文检索:Elasticsearch 7.x文档解析、IK分词与高亮查询实战

1. 从一份合同翻找说起:为什么Word和PDF的全文检索值得单独做前阵子帮一个做企业文档管理的朋友处理一个需求,他们内部有几千份Word合同和PDF技术方案,平时找一份文件只能靠文件名,可文件名往往起得极其随意,比如“最终…

2026/10/20 23:44:36 阅读更多 →
逆向工程Agent实战:从架构设计到落地优化的完整指南

逆向工程Agent实战:从架构设计到落地优化的完整指南

1. 逆向工程 Agent 到底在做什么1.1 从一个真实需求说起去年下半年开始,我陆续接触到几个做安全研究的朋友,他们不约而同地提到同一件事:手头需要分析的二进制样本越来越多,但人手根本没变。以前一个熟练的逆向工程师一天能啃完一…

2026/10/20 23:44:36 阅读更多 →
VS Code Jupyter内核崩溃排查:Kernel died with exit code 1全链路解析

VS Code Jupyter内核崩溃排查:Kernel died with exit code 1全链路解析

1. 从一个让人抓狂的报错说起如果你在 VS Code 里用 Jupyter 跑过代码,大概率见过这个弹窗:"Kernel died with exit code 1"。它不像语法错误那样给你一行红色波浪线,也不像缺包那样告诉你ModuleNotFoundError,它就是一…

2026/10/20 23:43:35 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →