一、缓存击穿
(一)概念
某个热点 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 示例):
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 避免缓存无限膨胀
示例:
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. 监控与报警
- 对缓存命中率、数据库查询量进行监控
- 当发现缓存命中率下降、数据库异常增多时,触发告警
- 帮助运维及时发现缓存穿透攻击或异常请求
三、缓存雪崩
(一)概念
缓存雪崩指的是大量缓存同时失效,导致大量请求直接打到数据库,使数据库瞬间承受超高压力,可能导致系统不可用。
- 核心:大量缓存同时过期或失效
- 表现:数据库压力骤增,接口响应延迟或失败
流程示意:
大量热点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 和数据库瞬时压力
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论