Redis 缓存击穿、穿透、雪崩:三种失效场景的识别与兜底

举报
yd_290186593 发表于 2026/11/06 12:35:05 2026/11/06
【摘要】 击穿是热点 key 失效、穿透是不存在的 key、雪崩是批量同时失效。三种场景给判断依据、代码级兜底方案与压测验证方法。

Redis 缓存击穿、穿透、雪崩:三种失效场景的识别与兜底

击穿是热点 key 失效、穿透是不存在的 key、雪崩是批量同时失效。三种场景给判断依据、代码级兜底方案与压测验证方法。

三种场景的本质区别

| 场景 | 触发条件 | 数据库压力 | 典型现象 |

|---|---|---|---|

| 击穿 | 单个热点 key 过期 | 瞬时高并发打到同一行 | QPS 突增到数千,全是同一条 SQL |

| 穿透 | 查询不存在的数据 | 每次请求都穿透到 DB | 缓存命中率骤降,DB 出现大量空查询 |

| 雪崩 | 大量 key 同时过期 | 整体流量压到 DB | DB 连接数瞬间打满,响应时间飙升 |

一句话区分:击穿是「一个 key」,穿透是「没有 key」,雪崩是「一批 key」。

一、缓存击穿

现象

某个热点 key(如首页配置、爆款商品)在过期瞬间,所有请求同时打到数据库。

判断

redis-cli --stat
redis-cli info stats | grep keyspace_misses
mysql -e "SHOW PROCESSLIST" | grep -c "SELECT.*hot_key_table"

兜底方案

方案 A:互斥锁(推荐)。只让一个线程去加载,其余等待。

import redis, time, json

r = redis.Redis()

def get_with_mutex(key, loader, ttl=300):
    val = r.get(key)
    if val:
        return json.loads(val)

    lock_key = f"lock:{key}"
    if r.set(lock_key, "1", nx=True, ex=10):
        try:
            val = loader()
            r.setex(key, ttl, json.dumps(val))
            return val
        finally:
            r.delete(lock_key)
    else:
        time.sleep(0.05)
        return get_with_mutex(key, loader, ttl)

关键点:锁必须设过期时间(ex=10),否则持锁线程崩溃会导致死锁。

方案 B:逻辑过期(不设 TTL,异步刷新)。数据里自带过期时间,过期后异步刷新,当前请求先返回旧值。

适用场景:允许短暂返回旧数据(如商品详情、排行榜)。不允许用于余额、库存等强一致场景。

二、缓存穿透

现象

请求查询一个根本不存在的数据,缓存永远不命中,每次都打到 DB。典型攻击是遍历 ID。

判断

redis-cli info stats | grep -E "keyspace_hits|keyspace_misses"
mysql -e "SHOW GLOBAL STATUS LIKE 'Com_select'"

兜底方案

方案 A:缓存空值。用特殊标记表示「确认不存在」,TTL 设短(60 秒),避免大量无效 key 占满内存。

方案 B:布隆过滤器。启动时把所有存在的 ID 灌进去,查询前先判断。1000 万 ID 只需约 18 MB,误判率可配置。

布隆过滤器的权衡:空间效率高,但误判率无法降到 0,且不支持删除。

方案 C:参数校验。非法 ID 直接拒绝,不进缓存也不进 DB。

三、缓存雪崩

现象

大量 key 在同一时间过期,或 Redis 整体不可用。

判断

redis-cli --scan --pattern "user:*" | head -1000 | while read k; do redis-cli ttl "$k"; done | sort -n | uniq -c

如果大量 key 的 TTL 集中在某个区间,说明是批量设置的。

兜底方案

方案 A:TTL 加随机偏移(最简单有效)。在基础 TTL 上叠加正负 10% 的随机值,原本同时过期的 10000 个 key 会分散到 60 秒窗口内。

方案 B:多级缓存。请求 → 本地缓存(Caffeine/LRU,TTL 10s)→ Redis(TTL 300s)→ DB。Redis 挂了,本地缓存还能挡住大部分请求。

方案 C:熔断降级。Redis 不可用时直接降级到 DB 或返回默认值。

方案 D:Redis 高可用。主从 + Sentinel 切换 10-30 秒,Cluster 秒级。

压测验证

改完代码必须验证,否则只是「看起来修好了」。

wrk -t 10 -c 500 -d 60s --latency "http://localhost:8000/api/hot"
watch -n 1 "mysql -e 'SHOW STATUS LIKE \"Threads_connected\"'"
watch -n 1 "redis-cli info stats | grep keyspace"

验证标准:击穿修复后 DB 连接数从 200+ 稳定在 20 以内;穿透修复后命中率从低于 50% 提升到 95% 以上;雪崩修复后 DB 负载平稳。

一张对照表

| 场景 | 根因 | 首选方案 | 备选 |

|---|---|---|---|

| 击穿 | 热点 key 过期 | 互斥锁 | 逻辑过期 |

| 穿透 | 查不存在的数据 | 缓存空值 | 布隆过滤器 |

| 雪崩 | 批量同时过期 | TTL 随机偏移 | 多级缓存 + 熔断 |

小结

三种场景的兜底逻辑完全不同,用错方案等于没修:给穿透加互斥锁无效,因为每个 key 都不同锁不住;给击穿加布隆过滤器无效,因为 key 是存在的;给雪崩加空值缓存无效,因为问题在批量过期不在单 key。

真正有效的做法是先看监控定位是哪一种,再选对应方案。判断依据:看 Redis 命中率骤降是穿透;看 DB 是否有大量同一条 SQL 是击穿;看 key 的 TTL 是否集中在同一时刻是雪崩。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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