Redis缓存穿透、击穿、雪崩区别和解决
时间:2026-10-9 07:47 作者:emer 分类: 无
前言
用Redis做缓存,经常听到三个词:缓存穿透、缓存击穿、缓存雪崩。很多人搞不清区别,本文一次讲明白,附解决方法。
一、三个概念对比
| 问题 | 是什么 | 谁在访问 | 影响 |
|---|---|---|---|
| 缓存穿透 | 查一个不存在的数据,缓存没有,每次都去查数据库 | 恶意攻击/大量无效请求 | 数据库压力大 |
| 缓存击穿 | 一个热点key突然过期,大量请求同时打过去 | 正常用户 | 数据库瞬间压力大 |
| 缓存雪崩 | 大量key同时过期,或者Redis挂了 | 正常用户 | 数据库直接崩 |
二、缓存穿透
是什么
黑客故意查一个数据库里根本不存在的数据,缓存里也没有,每次请求都打到数据库。
例子: 查用户ID=-1,数据库里没有,缓存里也没有,每次都查数据库。
危害
数据库被大量无效请求打挂。
解决方法
方法1:缓存空值
数据库查不到,也往缓存里写一个空值,设个短过期时间。
// 先查缓存
$user = $redis->get("user:$id");
if ($user === false) {
// 查数据库
$user = mysqli_query("SELECT * FROM users WHERE id = $id");
if (!$user) {
// 查不到也缓存,设60秒过期
$redis->setex("user:$id", 60, "null");
} else {
$redis->setex("user:$id", 3600, $user);
}
}
方法2:布隆过滤器
在缓存前面加一层布隆过滤器,快速判断这个ID存在不存在。
不存在的直接返回,不用查数据库。
方法3:接口校验
在接口层就做参数校验,比如用户ID必须大于0,不合法的直接拒绝。
三、缓存击穿
是什么
一个热点key(比如首页热点新闻),突然过期了,这时候刚好有大量用户同时访问,所有请求都打到数据库。
和穿透的区别: 这个key本来是存在的,只是过期了。
危害
数据库瞬间压力暴增。
解决方法
方法1:互斥锁
只让一个线程去查数据库,其他线程等一下。
$lockKey = "lock:user:$id";
// 尝试加锁
if ($redis->setnx($lockKey, 1)) {
// 加锁成功,查数据库
$user = mysqli_query("SELECT * FROM users WHERE id = $id");
$redis->setex("user:$id", 3600, $user);
$redis->del($lockKey);
} else {
// 加锁失败,等一下再读缓存
sleep(100);
$user = $redis->get("user:$id");
}
方法2:热点key不过期
热点数据不设过期时间,后台定时更新。
方法3:提前预热
在热点数据快过期前,主动后台更新缓存。
四、缓存雪崩
是什么
大量key在同一时间过期,或者Redis整个挂了,所有请求都打到数据库。
和击穿的区别:
- 击穿:一个key过期
- 雪崩:大量key同时过期
危害
数据库直接被打挂,整个服务不可用。
解决方法
方法1:过期时间加随机值
不要所有key都设3600秒过期,加个随机数错开。
// 基础3600秒,加0-600秒随机
$expire = 3600 + rand(0, 600);
$redis->setex("user:$id", $expire, $data);
方法2:Redis集群
Redis挂了还有备用的,不要单点。
方法3:服务降级
数据库扛不住的时候,直接返回默认数据或者报错,不要让请求都打过去。
方法4:热点数据永不过期
核心数据不设过期,后台异步更新。
五、总结对比
| 问题 | 原因 | 解决 |
|---|---|---|
| 缓存穿透 | 查不存在的数据 | 缓存空值 + 布隆过滤器 |
| 缓存击穿 | 热点key过期 | 互斥锁 + 热点永不过期 |
| 缓存雪崩 | 大量key同时过期 | 过期时间加随机 + Redis集群 |
六、常见坑
坑1:把穿透和击穿搞混
- 穿透:数据本来就不存在
- 击穿:数据存在,但缓存过期了
坑2:缓存空值不设过期时间
如果不设过期时间,以后这个数据真的有了,缓存还是空的。
解决: 空值也要设过期时间(比如60秒)。
坑3:互斥锁死锁
加了锁没释放,其他线程一直等。
解决: 锁也要设过期时间。
坑4:随机值加太小
所有key还是差不多同时过期。
建议: 随机范围至少几百秒。
总结
记住:
- 穿透:查不存在的数据 → 缓存空值 + 布隆过滤器
- 击穿:热点key过期 → 互斥锁 + 热点永不过期
- 雪崩:大量key同时过期 → 过期时间加随机 + Redis集群
最简单的防雪崩:
$expire = 3600 + rand(0, 600);
$redis->setex($key, $expire, $data);
遇到问题加QQ23979811 协助处理