Redis 缓存实战:雪崩、穿透、击穿一次讲透 📅 发布时间:2026/7/14 21:15:53 👁️ 浏览次数: 缓存用好了是性能翻倍用不好凌晨三点把你叫醒。Redis 在大多数系统里扮演的角色是挡在数据库前面的一堵墙。正常情况下请求打到 Redis命中就走数据库压力不大。但这堵墙有三个洞它可能整堵墙同时塌可能有人绕过墙直接锤数据库也可能墙上最忙的那块砖碎了。对应过来就是雪崩、穿透、击穿。一个一个说。一、缓存雪崩整堵墙同时塌了什么是雪崩大量缓存在同一个时间点集中过期所有请求瞬间穿透到数据库数据库扛不住直接挂。典型的触发场景你批量导入了 10 万条商品数据所有缓存都设了 24 小时过期。24 小时后10 万个 key 在同一秒集体死亡。下一秒打进来的请求全部查库数据库连接池瞬间打满。怎么解决三道防线// 防线一过期时间加随机值错开集体死亡 // 不要这样 redisTemplate.opsForValue().set(productKey, value, 24, TimeUnit.HOURS); // 改成这样随机加 1-6 小时 int randomHours ThreadLocalRandom.current().nextInt(1, 7); redisTemplate.opsForValue().set(productKey, value, 24 randomHours, TimeUnit.HOURS); // 防线二永不过期 异步更新 // 缓存本身不设过期由定时任务定期刷新 Scheduled(fixedDelay 300000) // 每5分钟 public void refreshHotProducts() { ListProduct hotProducts productService.getHotProducts(); for (Product p : hotProducts) { String key product: p.getId(); redisTemplate.opsForValue().set(key, p, Duration.ofDays(7)); } } // 注意这里设了7天兜底防止定时任务挂了缓存永远不更新 // 防线三多级缓存 限流兜底 // 如果 Redis 也挂了本地缓存拦截一波 Cacheable(value product, key #id, unless #result null) public Product getProduct(Long id) { return productMapper.selectById(id); } // 数据库层加限流即使被打穿也不至于雪崩到宕机 // Sentinel / RateLimiter 随便选一个核心是保住数据库不挂雪崩的核心矛盾不是数据过期是所有东西同时过期。错开时间就消解了 80% 的风险。二、缓存穿透有人绕过墙直接锤数据库什么是穿透查询一个不存在的数据——缓存里没有数据库里也没有。正常逻辑会直接查库然后发现是空的不写缓存。下次同样的非法请求再来继续查库。恶意攻击者可以构造一大堆不存在的 ID把你的数据库活活打死。// 漏洞代码 public Product getProduct(Long id) { String key product: id; Product p (Product) redisTemplate.opsForValue().get(key); if (p ! null) { return p; } p productMapper.selectById(id); if (p ! null) { redisTemplate.opsForValue().set(key, p, 24, TimeUnit.HOURS); } // BUGp 是 null 的时候什么都不做下次还查库 return p; }怎么修// 方案一缓存空值把「没有」也存起来 public Product getProduct(Long id) { String key product: id; Product p (Product) redisTemplate.opsForValue().get(key); if (p ! null) { // 判断是不是空值标记 if (p.getId() -1L) return null; return p; } p productMapper.selectById(id); if (p null) { // 缓存一个空对象短过期防止被恶意刷 Product empty new Product(); empty.setId(-1L); redisTemplate.opsForValue().set(key, empty, 5, TimeUnit.MINUTES); return null; } redisTemplate.opsForValue().set(key, p, 24, TimeUnit.HOURS); return p; }空值缓存的问题是如果有人刷几百万个不同的非法 IDRedis 内存会被空对象占满。所以// 方案二布隆过滤器事前拦截不存在的 key // 用 Guava 或 Redisson 的布隆过滤器 RBloomFilterString bloomFilter redissonClient.getBloomFilter(product_filter); bloomFilter.tryInit(1000000L, 0.01); // 100万容量1%误判率 // 数据写入时同步加入过滤器 bloomFilter.add(product: id); // 查询前先过过滤器 public Product getProduct(Long id) { String key product: id; if (!bloomFilter.contains(key)) { return null; // 一定不存在直接返回不查库 } // 剩下走正常缓存逻辑 // ... }布隆过滤器的代价有 1% 误判率说存在但实际不存在。多查一次数据库是能接受的。它的价值是拦住 99% 的恶意穿透请求。推荐组合布隆过滤器前置拦截 空值缓存兜底。布隆过滤器拦不住的那 1% 由空值缓存接住。三、缓存击穿墙上最忙的那块砖碎了什么是击穿一个热点 key 过期了这个 key 平时扛几万 QPS。过期瞬间几万请求同时打向数据库。和雪崩的区别是雪崩是一大片 key 同时过期击穿是单个爆款 key 过期。典型场景秒杀商品的库存缓存、首页推荐位的配置缓存、大 V 的粉丝数缓存。// 漏洞代码 public Product getHotProduct(Long id) { String key product: id; Product p (Product) redisTemplate.opsForValue().get(key); if (p null) { // 缓存过期了直接查库 // 几千个线程同时走到这里全部查库 p productMapper.selectById(id); redisTemplate.opsForValue().set(key, p, 10, TimeUnit.MINUTES); } return p; }怎么修// 方案一互斥锁只让一个人去查库 public Product getHotProduct(Long id) { String key product: id; String lockKey lock:product: id; Product p (Product) redisTemplate.opsForValue().get(key); if (p ! null) return p; // SET NX EX抢锁超时 10 秒防止死锁 Boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, 10, TimeUnit.SECONDS); if (Boolean.TRUE.equals(locked)) { try { // 抢到锁二次检查 p (Product) redisTemplate.opsForValue().get(key); if (p ! null) return p; p productMapper.selectById(id); redisTemplate.opsForValue().set(key, p, 10, TimeUnit.MINUTES); return p; } finally { redisTemplate.delete(lockKey); } } else { // 没抢到锁等 50ms 重试 try { Thread.sleep(50); } catch (InterruptedException ignored) {} return getHotProduct(id); // 递归重试 } } // 方案二逻辑过期不删 key只更新 value // 适合容忍短期旧数据的场景 public Product getHotProductV2(Long id) { String key product: id; Product p (Product) redisTemplate.opsForValue().get(key); if (p null) { // 首次加载 p productMapper.selectById(id); redisTemplate.opsForValue().set(key, p, 10, TimeUnit.MINUTES); return p; } // 判断是否逻辑过期在 value 里塞一个过期时间戳 if (p.getExpireTime() System.currentTimeMillis()) { // 异步更新当前请求直接返回旧数据 threadPool.submit(() - refreshCache(id)); } return p; // 返回可能过期的旧数据但不会击穿 }逻辑过期方案的关键取舍宁愿用户看到 30 秒前的旧数据也别让他等 5 秒白屏。区别速查表类型现象本质核心解法雪崩大量 key 同时过期过期时间一致随机 TTL 永不过期异步刷新穿透查不存在的 key无缓存屏障布隆过滤器 缓存空值击穿单个热点 key 过期高并发查同一个互斥锁 逻辑过期最后不管你用不用 Redis这三类问题本质上都是同一件事当缓存和数据库之间的假设破裂时你的系统有没有兜底。一个缓存 key 过期了你没有互斥锁说明你假设了缓存永远能命中。缓存不存在你没有布隆过滤器说明你假设了所有请求都是合法的。大量 key 同时过期你没有随机 TTL说明你假设了过期时间是均匀分布的。假设被打破的那一刻就是你的系统开始雪崩的那一刻。
C28x DSP平台三相电机SVPWM实时控制代码包(含扇区判断、死区补偿与ePWM适配) 本文还有配套的精品资源,点击获取 简介:直接用于TI C28x系列DSP(如TMS320F2812、F28335)的三相电机SVPWM生成代码,核心为SVPWM.c文件,纯软件实现空间矢量脉宽调制算法,不依赖外部专用芯片。支… 2026/7/14 21:15:52
ai生成开题报告完整指南(2026版):从零到通过导师审核的5步法 一句话答案:用 ai生成开题报告 已经能把一份规范的开题报告在十几分钟内产出初稿,推荐用毕业之家ai(www.biye.com) 这类懂学术规范的工具,再结合人工修改,通过率更高。开题报告为什么总让人头秃开题报告是论文的「门面… 2026/7/14 21:13:50
MATLAB一键生成涡旋光束:调节拓扑荷数l实现高斯光束相位螺旋化与OAM可视化 本文还有配套的精品资源,点击获取 简介:一套开箱即用的MATLAB光学仿真工具,直接加载高斯光束后叠加exp(ilφ)螺旋相位因子,快速生成携带轨道角动量(OAM)的涡旋光束。支持实时调整关键参数:拓… 2026/7/14 21:13:50
《Java 100 天进阶之路》并发编程与JUC详解(第51~60篇)核心知识点汇总 并发编程与JUC详解(第51~60篇)核心知识点汇总 本阶段共10篇文章,覆盖Java并发编程从基础到精通的完整链路,是面试权重最高、区分度最大的模块之一。 第51篇:线程生命周期与创建方式 📖 查看完整文章 6种线… 2026/7/14 23:22:46
C++宏定义:从基础语法到高级应用与最佳实践 1. 项目概述:为什么宏定义是C程序员绕不开的坎?如果你写过C,尤其是接触过一些大型项目或者开源库的源码,比如STL、Boost或者一些游戏引擎的底层代码,那你一定对宏定义不陌生。那些以#define开头的、看起来像魔法一样的… 2026/7/14 23:22:46
遗传算法实战进阶:动态算子、自适应参数与工业级调试 1. 项目概述:为什么遗传算法的“第二课”比第一课更关键你翻过不少资料,也看过“遗传算法入门”的标题党文章——讲完生物类比、种群、染色体、适应度,就戛然而止,留下一堆没落地的公式和一句“交叉变异后迭代即可”。但真正动手写… 2026/7/14 23:20:45
【需求工程】统一建模语言UML类图中关联、多样性和角色 UML类图:关联、多样性、角色 完整解读 这是UML类图的基础知识点,分为图示样例 文字定义两部分,我拆开逐点讲清楚。 一、先看懂上面的建模样例(人 ↔ 车辆) 1. 两个「类」 人:属性 姓名、出生日期车辆&… 2026/7/14 23:16:44
遗传算法工业级调参与可解释性实践指南 1. 项目概述:从“会跑”到“跑得明白”的遗传算法进阶实践你手头刚跑通一个遗传算法示例,种群迭代了200代,目标函数值从1500掉到了87,屏幕上跳着一串数字——但你心里没底:这结果靠谱吗?为什么交叉概率设0.… 2026/7/14 23:16:44
Python使用SMTP发送电子邮件 1 文章背景 在 Web 系统或桌面工具中,经常会遇到发送邮件的需求,例如发送注册验证码、密码找回验证码、系统通知、任务提醒等。 Python 标准库中提供了 smtplib 和 email 相关模块,可以比较方便地完成邮件发送。本文主要整理如何开启邮箱 SMT… 2026/7/14 23:16:44
XUnity.AutoTranslator 游戏实时翻译插件:从原理到实战的完整指南 1. 项目概述:当游戏语言成为一堵墙作为一名玩了十几年日系、欧美独立游戏的“老油条”,我太懂那种面对一款心仪已久、画风玩法都戳中G点的游戏,却因为语言不通而望而却步的痛了。尤其是那些基于Unity引擎开发的、体量不大但内容精良的作品&am… 2026/7/14 0:05:14
2026普通文员学数据分析的价值 一、2026年普通文员学习数据分析的必要性随着数字化转型加速,数据分析技能正逐渐成为职场基础能力。普通文员学习数据分析可以提升工作效率、增强竞争力,并为职业转型提供更多可能性。二、数据分析对文员的价值自动化办公:通过数据分析工具&a… 2026/7/14 0:05:14
2026从计划员到主管,生产管理者学数据分析有用吗? 一、生产管理领域的职业发展路径 从计划员到主管的角色转变,是生产管理者职业发展的典型路径。计划员主要负责生产排程、库存管理和资源协调等基础工作,而主管则需要承担团队管理、决策支持和效率优化等更高级别的职责。这种转变不仅仅是职位的提升&… 2026/7/14 0:05:14
Git reset 与 revert 深度对比:5个关键差异与 3 种典型应用场景 Git Reset 与 Revert 深度对比:5个关键差异与3种典型应用场景在团队协作开发中,代码版本管理如同行走钢丝——一步失误可能导致整个项目陷入混乱。作为Git进阶用户,你是否曾在深夜面对错误的提交束手无策?是否在强制推送后收到同事… 2026/7/13 8:31:55
GitHub 学生包申请避坑:5个常见失败原因与开发者工具调试方案 GitHub 学生包申请技术排障指南:5个高频失败场景与开发者工具实战方案第一次尝试申请GitHub学生包时,我盯着屏幕上那个不断转圈的加载动画整整15分钟,最终只等来了一行冰冷的错误提示。这可能是许多开发者共同的经历——明明按照教程操作&… 2026/7/14 18:25:04
冒烟测试用例设计规范:5%-10%覆盖率下的3类核心场景与执行标准 冒烟测试用例设计的黄金法则:5%-10%覆盖率下的精准筛选策略在快节奏的敏捷开发环境中,冒烟测试作为质量保障的第一道防线,其重要性不言而喻。当测试资源有限而时间紧迫时,如何从海量测试用例中精准筛选出那关键的5%-10%࿰… 2026/7/14 5:09:41