Redis 11 · 缓存问题:穿透、击穿与雪崩

缓存的难点不是 GET,而是缓存失效、异常流量和回源压力叠加时,系统还能不能维持服务。穿透、击穿、雪崩看起来都是"缓存没命中",根因却不同,治理手段也不能混用。

目录

  1. 先定义缓存命中链路
  2. 缓存穿透
  3. 缓存击穿
  4. 缓存雪崩
  5. 限流、降级与恢复
  6. 监控与演练

一、先定义缓存命中链路

典型 Cache Aside 链路是:查缓存 → 未命中查数据库 → 回填缓存 → 返回。任何一步超时都要有边界,不能让缓存层把数据库连接池拖垮。

二、缓存穿透

穿透是大量请求查询根本不存在的 Key,每次都绕过缓存打到数据库。攻击者可以随机生成商品 ID,普通业务流量也可能因参数校验缺失造成穿透。

第一道防线是参数校验;第二道是空值缓存,给不存在结果设置较短 TTL;第三道是布隆过滤器等存在性过滤。空值缓存要防止把后来创建的商品长期挡住,布隆过滤器的误判只能让请求继续查库,不能用于肯定存在。

三、缓存击穿

击穿是某个极热 Key 恰好过期,瞬间大量请求同时回源。互斥锁可以让一个请求重建,其余请求短暂等待;逻辑过期则允许返回旧值,同时异步刷新。

锁的等待时间、重建超时和失败兜底必须明确。不能只写 SET NX,却没有锁拥有者校验和异常释放。

四、缓存雪崩

雪崩是大量 Key 同时失效,或 Redis 集群整体不可用,造成回源洪峰。TTL 加随机抖动、分批预热、多级缓存和限流可以降低峰值,但不能替代容量评估。

五、限流、降级与恢复

当数据库连接池、线程池或下游接口到达阈值时,应优先返回静态兜底、旧缓存或明确的"稍后重试",而不是无限排队。对重建任务使用单飞(single flight)和任务队列,避免每个请求都启动刷新。

六、监控与演练

关注命中率、回源 QPS、空值比例、热点 Key、重建耗时、Redis 延迟和数据库连接池。演练单 Key 过期、批量过期、缓存集群不可用三类场景,分别验证击穿和雪崩策略。

6.1 场景:不存在的商品 ID 把数据库打满

营销活动的落地页接受 productId 参数。攻击流量不断请求随机的超大 ID,Redis 中没有缓存,MySQL 索引查询虽然单次很快,却以数万 QPS 持续消耗连接池。此时命中率下降只是表象,真正的信号是"缓存未命中率高且数据库结果为空的比例异常"。

第一步在网关和接口层校验 ID 格式、范围和签名,明显非法的请求不应进入缓存。对合法格式但不存在的 ID,缓存一个显式空对象,例如 {"found":false},TTL 可以比正常商品详情短并加入随机抖动。不要用空字符串和"缓存不存在"混淆,否则排查时无法判断是缓存 miss 还是负缓存命中。

商品后台新建了原先不存在的 ID 时,要删除这条空缓存,或者用消息事件驱动失效。布隆过滤器适合商品主数据量大且变更相对少的场景:它能快速否定"不存在",但有误判,误判时仍应让请求继续经过正常的查库和回填链路。

6.2 场景:明星商品过期,一秒钟打出几万次回源

product:detail:1001 在直播间是绝对热点。它到期的瞬间,20 000 个请求发现 miss,同时去查库并回填。只加一个互斥锁还不够:如果持锁线程的数据库查询超时,等待线程全部堆积,锁释放后又会形成第二波回源。

适合详情展示类数据的方案是逻辑过期:缓存内容带 expireAt,过期后第一个请求提交异步刷新任务,其余请求返回可接受的旧值。适合必须看到新库存的场景则用 single-flight,同一 Key 只有一个重建任务,其余请求等待一个很短的上限,超过上限返回降级页或库存繁忙。

无论哪种方案,都要限制重建任务队列。刷新失败时保留旧值并做退避,不能立即把 Key 删除后让所有请求回源。热点 Key 的 TTL 也不宜在固定整点统一失效。

6.3 场景:发布后 300 万 Key 同时失效

一次预热脚本把所有详情 Key 都设成固定一小时 TTL,整点同时过期。Redis 并没有故障,但数据库 QPS 暴涨、线程池排队,最终引发级联超时。这是典型雪崩,不是 Redis "扛不住"。

修复包括 TTL 基值加随机窗口、按批次预热、给回源接口做并发阈值与熔断、在 CDN/本地缓存放静态兜底。恢复时不要一次性清缓存或全量预热,应按热度分批回填,并用数据库连接池水位决定下一批速度。

6.4 降级不是返回错误对象那么简单

库存接口不能因为 Redis 超时就默认"库存充足",否则会把风险转成超卖。可以按业务重要性分层:商品描述返回最近一次成功缓存,推荐列表返回空列表,库存和支付直接失败并引导稍后重试。降级响应要带上来源和时间,便于客户端避免把旧数据当成实时数据。

恢复阶段先恢复 Redis 可用性,再逐步放开回源流量。设置一个"回源闸门":只有当数据库连接池、线程池和下游 P99 都低于阈值时,才提高每秒允许的重建任务数。否则缓存刚恢复,预热任务又会把数据库打垮。


记住:穿透治理"无效 Key",击穿治理"单个热点同时回源",雪崩治理"很多 Key 或整层同时失效"。

相关推荐
陕西企来客3 小时前
豆包检索层 9 月迭代后信源陈旧的核心 —— 缓存策略的可用性优先转向
缓存
imDwAaY7 小时前
Redis String 为什么不直接用 C 字符串?从 SDS 到三种编码
redis
imDwAaY7 小时前
Redis 的数据到底怎么存?一篇理清数据类型与底层结构
redis
谢亮_vipxieliang8 小时前
一套 Compose 搭起完整开发环境:MySQL + Redis + Nginx
redis·mysql·nginx·docker
三掌柜6668 小时前
ArkWeb 手记 07|缓存和离线页怎么做
缓存·harmonyos
wefg11 天前
【Redis】初识 Redis
数据库·redis·缓存
周之瑞1 天前
技术手记|Redis 与数据库事务不在一个世界:afterCommit、显式补偿还是 Canal?
redis
hacker_LeeFei1 天前
Windows 下配置 Redis 8.10.2 开机自启:一次踩坑全记录
数据库·windows·redis
zl_dfq1 天前
Redis 之 【持久化 与 事务】
数据库·redis
白露与泡影1 天前
Redis 的“单线程”与“多线程”:一场关于性能与简洁的权衡
数据库·redis·php