Redis 缓存穿透、击穿与雪崩排查实战:从命中率判断到互斥重建与过期抖动

举报
yd_229207802 发表于 2026/11/06 09:51:27 2026/11/06
【摘要】 背景线上用 Redis 做缓存的服务,最怕三种情况:缓存穿透、缓存击穿和缓存雪崩。它们的共同点是请求绕过缓存直接打到数据库,区别在于触发原因不同。本文按“现象、原因、处理”的顺序整理一套我自己排查时用的做法,示例以 Java 风格的伪代码和 redis-cli 命令为主,思路适用于任何语言。一、先判断是哪一种先看监控,不要急着改代码。可以用下面的命令观察命中率:redis-cli info ...

背景

线上用 Redis 做缓存的服务,最怕三种情况:缓存穿透、缓存击穿和缓存雪崩。它们的共同点是请求绕过缓存直接打到数据库,区别在于触发原因不同。本文按“现象、原因、处理”的顺序整理一套我自己排查时用的做法,示例以 Java 风格的伪代码和 redis-cli 命令为主,思路适用于任何语言。

一、先判断是哪一种

先看监控,不要急着改代码。可以用下面的命令观察命中率:

redis-cli info stats | grep -E "keyspace_hits|keyspace_misses"

命中率 = hits / (hits + misses)。三种问题的特征不同:

  • 穿透:命中率长期偏低,数据库收到大量“查不到结果”的请求,参数多半是不存在的 ID。
  • 击穿:命中率整体正常,但某一个热点 key 过期的瞬间,数据库并发查询突然飙高,随后恢复。
  • 雪崩:大量 key 在同一时间段集中过期,或者 Redis 实例不可用,数据库压力持续升高。

二、缓存穿透:查询的数据根本不存在

典型场景是接口按 ID 查询,攻击者或爬虫不断传入不存在的 ID,缓存永远不命中,请求每次都落库。常用的处理方式有三个。

1. 参数校验。在接口层先拦掉明显非法的参数,比如负数 ID、超长字符串,这是成本最低的一层。

2. 缓存空值。查询数据库没有结果时,也写一个空值进缓存,并设置较短的过期时间:

value = redis.get(key)
if value == NULL_MARK: return None
if value is None:
    row = db.query(id)
    if row is None:
        redis.setex(key, 60, NULL_MARK)   # 空值只缓存 60 秒
        return None
    redis.setex(key, 3600 + random(0, 300), row)
    return row
return value

空值的过期时间不要太长,否则数据后来真的新增了,用户会在一段时间内一直查不到。

3. 布隆过滤器。如果合法 ID 的集合比较固定,可以在应用启动时把全部 ID 加载进布隆过滤器,查询前先判断。它的特点是“说不存在就一定不存在”,存在少量误判但不会漏判。

三、缓存击穿:热点 key 过期的一瞬间

一个访问量很高的 key 到期后,在它被重新写入之前,所有并发请求都会发现缓存为空并同时去查数据库。解决思路是让同一时刻只有一个请求去重建缓存:

value = redis.get(key)
if value is None:
    if redis.set(lock_key, token, nx=True, ex=10):
        try:
            value = db.query(id)
            redis.setex(key, 3600, value)
        finally:
            if redis.get(lock_key) == token:
                redis.delete(lock_key)
    else:
        sleep(0.05)
        return get(id)   # 稍后重试,读取别人写好的缓存

注意三点:锁一定要设过期时间,防止持有者异常退出后死锁;释放锁前要确认还是自己持有,否则会误删别人的锁,严格做法是用 Lua 脚本把判断和删除合成一步;重试要有次数上限,不要无限递归。对于极热的数据,也可以不设过期时间,改成后台定时任务主动刷新。

四、缓存雪崩:同时过期或实例故障

如果批量导入缓存时都设成了“3600 秒”,一小时后它们会同时失效。最简单的办法是在基础过期时间上加随机值,前面示例里的 random(0, 300) 就是这个作用。

实例故障引起的雪崩则要靠架构兜底:

  • 使用主备或集群版本的 Redis 实例,开启自动故障切换,避免单点。
  • 应用侧做限流和降级,数据库承受不了时直接返回兜底数据或提示稍后重试,而不是让请求排队把数据库压垮。
  • 给数据库连接池设置上限和超时,宁可部分请求失败,也不要全部超时。

五、上线前的检查清单

  • 所有写缓存的地方都带过期时间,并且带随机抖动。
  • 查询不存在数据的路径有空值缓存或布隆过滤器。
  • 热点 key 有互斥重建或后台刷新机制。
  • 监控里配置了命中率、慢查询和数据库 QPS 的告警,告警阈值要结合业务平时的基线来定。
  • 压测时专门模拟一次“清空缓存后立刻放量”的场景,看数据库和连接池的表现。

总结

穿透看参数、击穿看热点、雪崩看时间分布和高可用。排查时先用命中率和数据库 QPS 判断类型,再对症处理;日常开发时把过期抖动、空值缓存和互斥重建当成默认写法,大部分缓存事故都能提前避免。以上做法都是我在日常开发里踩坑后总结的,参数需要按自己业务的数据量和并发情况调整。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。