Redis缓存穿透、击穿、雪崩区别和解决

前言

用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还是差不多同时过期。

建议: 随机范围至少几百秒。

总结

记住:

  1. 穿透:查不存在的数据 → 缓存空值 + 布隆过滤器
  2. 击穿:热点key过期 → 互斥锁 + 热点永不过期
  3. 雪崩:大量key同时过期 → 过期时间加随机 + Redis集群

最简单的防雪崩:

$expire = 3600 + rand(0, 600);
$redis->setex($key, $expire, $data);

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:47