一、缓存击穿
(一)概念
某个热点 Key 过期的瞬间,大量并发请求同时打到数据库,导致数据库压力瞬间飙升,甚至被打崩。
大量并发请求 ---> 访问同一个热点 key
↓
这个 key 正好过期
↓
所有请求同时绕过 Redis 访问数据库
↓
DB 瞬间压力过大(被打爆)
(二)缓存击穿的后果
1. 数据库压力瞬间飙升
热点 Key 过期后,大量并发请求直接落到数据库
数据库瞬间承受超高并发请求,CPU、IO 占用飙升
2. 系统性能下降
数据库响应变慢 → 接口响应延迟增加
3. 请求可能超时或失败
高并发下,整体系统吞吐量下降
4. 可能触发雪崩效应
一个热点 Key 击穿导致数据库压力过大
可能影响其他业务请求
形成连锁反应,多个 Key 失效 → 系统整体性能下降
5. 运维风险增加
数据库连接耗尽、事务阻塞
CPU/内存占用过高 → 可能导致服务宕机
需要紧急干预,影响业务连续性
(三)触发条件
1. 热点 Key(高访问频率)
**概念:**热点 Key 是指在短时间内被大量请求访问的数据。
**特征:**访问量远高于其他普通 Key,可能占据系统绝大部分流量。
具体示例:
电商网站的某个秒杀商品库存信息
热门文章或新闻详情页
排行榜数据,如"本周最热商品 Top 10"
**为什么关键:**只有热点数据过期,才会有大量请求瞬间打到数据库,引发压力。
2. Key 过期或被淘汰
**概念:**缓存中存放的 Key 可能由于以下原因失效:
**(1)TTL 到期:**设置了过期时间,到时间就失效
**(2)手动删除:**开发或运维手动清理缓存
**(3)内存淘汰:**Redis 达到内存上限,根据 LRU/LFU 策略淘汰 Key
具体示例:
设置 SET product_123 100 EX 60,60 秒后过期
服务器更新产品信息,删除缓存强制刷新
Redis 内存满了,大对象或低频 Key 被淘汰
**为什么关键:**Key 过期或消失后,下一次请求会直接落到数据库,这才是击穿的直接触发点。
3. 高并发访问
**概念:**短时间内大量请求同时访问同一个 Key。
**特征:**瞬时并发量远高于数据库处理能力。
具体示例:
秒杀活动开始时,几千甚至上万用户同时访问同一商品库存
热点文章推送后,瞬间大量用户点击访问
**为什么关键:**如果并发量很小,即使 Key 过期,数据库也能承受压力;只有高并发访问,才会引发真正的缓存击穿。
4. 少了任意一个条件,就不会发生缓存击穿
解释:
热点 Key 不存在 → 就算并发很高,也不会击穿数据库(普通 Key 的请求量本来就小)
Key 没有过期或被淘汰 → 缓存命中,所有请求都打到 Redis,不会落到数据库
高并发访问 不存在 → 即使 Key 过期,也只有少量请求访问数据库,数据库能轻松承受
所以 三个条件必须同时满足,缓存击穿才会发生。
(四)典型场景
1. 热门商品详情
场景描述:
电商平台的秒杀商品或促销商品在短时间内被大量用户访问
每个用户都要查询库存、价格、折扣等信息
为什么容易击穿:
热点 Key:这个商品的缓存是热点,因为秒杀活动期间访问量极高
Key 过期或缓存未命中:TTL 到期,或者缓存被手动刷新
高并发请求:几千甚至上万用户同时访问数据库查询库存
技术后果:
数据库瞬时压力飙升
查询延迟增加 → 可能出现秒杀失败或页面崩溃
2. 排行榜或统计数据
场景描述:
热门文章阅读量、音乐/视频播放排行榜、实时交易额统计
大量用户同时查询"Top N"数据
为什么容易击穿:
热点 Key:排行榜数据被频繁访问
Key 过期:排行榜缓存每隔一段时间刷新一次,TTL 到期或逻辑过期
高并发访问:缓存过期瞬间,所有请求直接打数据库计算排行
技术后果:
数据库需要进行复杂聚合计算,CPU/IO 占用高
多个 Key 可能同时被访问 → 加剧系统压力
3. 系统配置或元数据
场景描述:
经常查询的基础信息,例如用户角色权限、地区列表、字典表数据
访问频率高,但数据量相对固定
为什么容易击穿:
热点 Key:这些 Key 被多次访问
Key 过期或被刷新:配置更新或 TTL 到期
高并发访问:短时间内多个服务/用户请求这些基础数据
技术后果:
一旦缓存失效,基础数据请求直接打数据库
影响整个业务链条的正常访问
(五)根本原因
1. Redis 只是缓存,并非数据库防护墙
Redis 的主要作用是加速数据访问,减少数据库压力
Redis 并不会阻止数据库本身被访问
一旦缓存未命中(Key 过期或被淘汰),请求就直接打数据库
**技术点:**缓存只是读写加速层,它不存储业务逻辑约束,也不限制请求流量
2. 高并发请求集中在失效 Key 上
当一个热点 Key 过期或被删除,瞬间所有访问这个 Key 的请求都会落到数据库
数据库承受能力有限,如果瞬时 QPS 超过数据库峰值 → 查询排队
技术点:
Redis hit rate 高 → 大部分请求在缓存层就被拦截
Key 失效瞬间 → 缓存失效窗口,hit rate 突然变低 → DB 瞬时压力飙升
3. 数据库无法承受大量并发请求
数据库在高并发下可能出现:
(1)CPU 饱和 → 查询速度下降
(2)IO 瓶颈 → 磁盘或网络访问慢
(3)连接耗尽 → 数据库拒绝新连接
(3)事务阻塞 → 并发写操作阻塞其他请求
**结果:**接口响应变慢、请求超时,甚至数据库宕机
**技术点:**缓存击穿不是 Redis 的问题,而是热点数据失效 + 数据库瞬时承载能力不足的系统问题
(六)解决策略
1. 热点 Key 永不过期 / 逻辑过期
思路:
对真正热点的数据,不设置 TTL,让缓存一直存在
或者使用"逻辑过期",即缓存中记录一个过期时间,但读取时仍然可以返回旧数据,同时后台异步刷新
技术实现:
永不过期:SET key value 不设置 EX 参数
逻辑过期:缓存结构如 {value: ..., expireTime: 1234567890}
读取时判断 expireTime 是否过期
如果过期,后台线程异步更新缓存,用户仍然能读取旧值
适用场景:
热门商品、排行榜、系统配置、常用字典数据
2. 互斥锁 / 单线程加载
思路:
当缓存失效时,只有一个线程去加载数据库,其它线程等待缓存更新,避免高并发同时打数据库
技术实现(Java 示例):
java
String cache = redis.get(key);
if (cache == null) {
if (tryLock(key + "_lock")) { // 尝试获取锁
String dbData = queryFromDB(); // 查询数据库
redis.set(key, dbData, 60); // 回写缓存
unlock(key + "_lock"); // 释放锁
return dbData;
} else {
Thread.sleep(50); // 等待一段时间
return redis.get(key); // 重试读取缓存
}
}
return cache;
适用场景:
秒杀活动、热点 Key 高并发访问场景
3. 缓存预热 + 定时刷新
思路:
系统启动或缓存即将过期时提前加载热点数据到缓存
避免缓存失效瞬间出现大量请求落到数据库
技术实现:
缓存预热:系统启动时读取数据库,将热点 Key 加入 Redis
定时刷新:使用定时任务或后台线程,提前更新即将过期的 Key
适用场景:
系统启动后的热门数据
高频访问的排行榜或统计数据
4. 降级处理 / 限流
思路:
当缓存击穿瞬间,可以对部分请求做降级或限流,保护数据库
部分请求直接返回默认值或提示稍后再试
技术实现:
使用限流器(如令牌桶、漏桶算法)限制数据库访问频率
对热点查询返回缓存的旧值或默认值
结合互斥锁或逻辑过期,提高系统容错能力
适用场景:
秒杀活动、热门接口请求暴增场景
保护数据库稳定,保证系统可用性
(七)优化角度
1. 热点识别
**概念:**先识别哪些 Key 是真正的热点数据(访问频率高、压力大的 Key),针对这些 Key 才做特殊处理。
具体做法:
(1)在 Redis 中记录访问频率或使用监控统计访问量
(2)根据访问量排序,识别访问量大的 Key 为热点
(3)对热点 Key 可采取"永不过期"或"逻辑过期 + 异步刷新"等策略
作用:
只针对真正的热点做优化,避免资源浪费
降低缓存击穿风险
2. TTL 差异化
**概念:**给不同 Key 设置不同过期时间,避免大量热点 Key 同时过期。
具体做法:
对普通 Key 设置短 TTL
对热点 Key 设置长 TTL 或逻辑过期
可以给相似热点 Key 设置错开 TTL,避免同时失效
作用:
避免多个热点 Key 同时过期 → 大量请求同时打数据库
平滑系统压力
3. 分布式锁 / 单点加载
**概念:**缓存失效时,保证只有一个请求去访问数据库加载数据,其他请求等待或重试缓存。
具体做法:
使用 Redis 的 SETNX 或 Redisson 提供的分布式锁
第一个请求获取锁去加载数据库
其他请求等待锁释放或读取缓存
作用:
避免高并发请求同时击穿数据库
控制数据库瞬时压力
示例流程:
请求1 → 获取锁 → 查询 DB → 回写缓存 → 释放锁
请求2 → 获取锁失败 → 等待 / 重试缓存
请求3 → 同上
4. 逻辑过期 + 异步刷新
**概念:**缓存中存储数据的逻辑过期时间,而不是直接让 Key 过期。
具体做法:
(1)缓存结构:{value: ..., expireTime: timestamp}
(2)读取时判断 expireTime 是否过期
未过期 → 直接返回缓存
已过期 → 后台线程异步刷新缓存,前端仍返回旧值
(3)异步刷新完成后更新缓存
作用:
用户请求不会直接落到数据库
避免缓存失效瞬间击穿数据库
保证系统高并发下可用性
(八)注意事项
1. 分布式系统注意点
在多节点或微服务环境下,如果采用 分布式锁 或 逻辑过期刷新,需要保证跨节点的一致性。
可使用成熟的工具或框架实现分布式锁,例如 Redisson、ZooKeeper 或 Etcd。
这样可以避免多个节点同时刷新缓存,防止数据库压力再次集中。
2. 数据一致性问题
使用 逻辑过期 + 异步刷新 时,可能会在短时间内返回过期的旧数据(脏数据)。
需要根据业务可容忍度判断是否可以接受这种短暂的不一致,例如:排行榜、商品浏览量等统计类数据通常允许短暂延迟。
对于对实时性要求高的关键业务,需额外设计一致性策略。
3. 限流/降级策略的组合使用
在高并发场景下,单独的策略可能不足以完全保护数据库。
推荐组合方案:逻辑过期 + 分布式锁 + 限流/降级
这样可以确保:
请求不会直接击穿数据库
缓存刷新有序
系统在瞬时高并发下仍保持可用性
二、缓存穿透
(一)概念
缓存穿透指的是 查询一个根本不存在的数据时,请求会绕过缓存直接打到数据库,如果这种请求量很大,会导致数据库压力急剧增加。
核心:请求的数据在缓存和数据库中都不存在
表现:缓存永远无法命中,数据库承受全部请求
请求数据A(不存在)
↓
Redis查询,未命中
↓
直接访问数据库
↓
数据库也没有该数据
↓
返回结果给用户
特点:
数据不存在 → 每次请求都要查询数据库
缓存没有命中 → 每次都落到数据库
高并发下可能造成数据库压力过大
(二)触发原因
1. 用户恶意请求
恶意刷接口,随机生成不存在的 ID 请求
例如:/product/999999999,数据库中根本没有
2. 程序缺陷或参数错误
前端或接口调用传错参数,导致请求不存在的数据
例如:用户传了非法商品 ID 或拼写错误的关键字
3. 缓存未处理空结果
查询不存在的数据,缓存层没有保存空对象
每次请求都走数据库 → 穿透
(三)典型场景
1. 商品查询接口
用户请求不存在的商品 ID
没有缓存空对象 → 每次都打数据库
2. 用户登录或注册校验接口
查询不存在的用户名或手机号
高并发时可能形成数据库压力
3. 通用搜索或统计接口
查询历史不存在的记录或随机关键字
数据库承载能力下降
(四)可能后果
1. 数据库压力增加
大量不存在的数据请求直接落到数据库
CPU、IO 占用增加
2. 接口响应变慢 / 超时
高并发下数据库响应慢,接口可能返回超时
3. 系统可用性降低
数据库过载 → 其他正常请求也受影响
4. 潜在安全风险
恶意请求可能导致拒绝服务(DoS)攻击
(五)解决策略
1. 缓存空对象(Null Cache)
将数据库查询不存在的数据也缓存起来(空对象或特殊标记)
设置较短 TTL 避免缓存无限膨胀
示例:
java
String cache = redis.get(key);
if (cache != null) {
return cache.equals("NULL") ? null : cache;
} else {
String dbData = queryFromDB();
if (dbData == null) {
redis.set(key, "NULL", 60); // 缓存空对象
return null;
} else {
redis.set(key, dbData, 3600);
return dbData;
}
}
2. 布隆过滤器(Bloom Filter)
在请求到达缓存/数据库之前,用布隆过滤器快速判断 key 是否存在
不存在的请求直接拦截,避免落到数据库
特点:
支持海量数据
允许少量误判(存在概率误判为存在,但不会漏判)
场景:热门商品 ID、用户 ID 等高频接口
3. 接口层校验 / 参数校验
对传入请求参数做合法性校验
非法 ID、空 ID、格式错误的请求直接拒绝
4. 限流和防刷策略
对接口增加限流、频率限制
防止恶意请求或高并发重复查询不存在数据
(六)进一步优化与注意事项
1. 分布式环境注意点
如果系统是多节点或微服务架构,需要保证布隆过滤器或缓存空对象在各节点的一致性。
可以使用 集中式布隆过滤器 或分布式缓存同步策略,避免不同节点出现数据不一致。
2. 空对象缓存策略优化
缓存空对象时需要控制 TTL,避免占用过多缓存资源。
对于热门但不存在的数据,可以适当加长 TTL;对于随机或恶意请求的数据,TTL 设置短一些即可。
结合日志监控,发现频繁穿透的 Key,可进一步处理或封禁请求源。
3. 结合限流与防刷策略
在高并发或恶意请求场景,单靠缓存空对象或布隆过滤器可能不足。
可以使用 限流 + 防刷,例如:
限制接口每秒请求次数
对异常频繁请求 IP 或用户进行封禁或验证码验证
结合缓存策略可以更有效保护数据库稳定性。
4. 监控与报警
对缓存命中率、数据库查询量进行监控
当发现缓存命中率下降、数据库异常增多时,触发告警
帮助运维及时发现缓存穿透攻击或异常请求
三、缓存雪崩
(一)概念
缓存雪崩指的是大量缓存同时失效,导致大量请求直接打到数据库,使数据库瞬间承受超高压力,可能导致系统不可用。
核心:大量缓存同时过期或失效
表现:数据库压力骤增,接口响应延迟或失败
流程示意:
java
大量热点Key同时过期
↓
缓存全部未命中
↓
请求全部打到数据库
↓
数据库承受高压
↓
接口响应变慢或宕机
特点:
大量 Key 同时失效 → 大量请求集中落到数据库
高并发下数据库无法承受 → 系统可能整体不可用
不同于缓存击穿:缓存雪崩可能涉及 多个 Key,而击穿通常是 单个热点 Key
(二)触发原因
1. 大量 Key TTL 相同
系统初始化时给缓存设置了统一过期时间,例如都 1 小时
到期瞬间,大量请求同时访问数据库
2. Redis 宕机或重启
Redis 服务不可用 → 缓存全部失效
所有请求直接落到数据库
3. 缓存淘汰策略触发
当 Redis 内存不足,根据 LRU/LFU 策略淘汰大量 Key
瞬时失效 → 请求直接访问数据库
(三)典型场景
1. 秒杀活动或促销活动
活动前预热缓存,设置统一过期时间
活动开始或 TTL 到期 → 大量商品缓存同时过期
大量请求直接打数据库 → 宕机风险
2. 系统启动或 Redis 重启
系统重启或 Redis 重启 → 缓存清空
热门 Key 未预热 → 请求直接落数据库
3. 大规模缓存失效
一些批量刷新或缓存清理操作
例如定时清理排行榜、统计数据
多个 Key 同时失效 → 请求瞬间集中
(四)可能后果
1. 数据库瞬时压力飙升
CPU、IO、连接数暴涨
2. 接口响应延迟或失败
请求排队 → 用户体验差
3. 系统可用性下降
多个业务模块同时受影响 → 链式反应
4. 触发缓存击穿或雪崩放大
一个 Key 的高并发击穿 + 大量 Key 同时过期 → 系统雪崩
(五)解决策略
1. 缓存过期时间错开 / 随机化 TTL
给 Key TTL 增加随机偏移量,避免大量 Key 同时过期
例如:设置 TTL = 3600 ± random(0~300) 秒
2. 缓存预热(Cache Preload)
系统启动或缓存即将失效前,提前加载热点数据到缓存
避免大量请求落到数据库
3. 互斥锁或单线程加载
当缓存失效时,控制只有一个请求访问数据库,其它请求等待
避免高并发直接打数据库
4. 降级与限流
对部分请求返回默认值或提示稍后重试
使用限流器控制访问数据库的速率
5. 多级缓存 / 本地缓存 + Redis
本地缓存存热点数据 → Redis 失效前可以命中本地缓存
降低对 Redis 和数据库瞬时压力