当前位置: 首页 > news >正文

学习日报|梳理三类典型缓存问题:缓存穿透、缓存击穿、缓存雪崩

一、缓存穿透(Cache Penetration)

What:是什么

请求的 key 在缓存与数据库都不存在,导致每次都 绕过缓存直打数据库;在高并发或恶意流量下,DB 被打穿。

Why:为什么会发生

  • 恶意构造随机/不存在的 ID。

  • 业务参数校验缺失(负数/超范围 ID)。

  • 刚上线/冷数据天然不存在。

When:典型触发时机

  • 新功能放量、促销活动、黑产扫描接口。

  • 分布式爬虫对详情页/查询接口“打点式”扫描。

Scenario:表现/定位线索

  • 缓存命中率下降,但 不存在的 key 占比高

  • DB QPS、慢查询上升;热点并不集中,而是“均匀稀疏”的随机 key。

  • 应用日志大量 404/空结果。

How:治理与防护

1)快速止血

  • 接口参数校验前置:ID 边界/白名单校验,不合法直接拦截。

  • 缓存空值(短 TTL + 随机抖动):不存在也写缓存

    • TTL:30s~2min(按业务容忍度);并加 0~10s 随机抖动防止齐过期。

    • 缺点:占用少量缓存空间;可配“空值计数”+LRU 限制。

  • 网关限速/验证码:对异常维度限流(例如 UA/IP/Referer)。

2)中长期治理

  • 布隆过滤器(Bloom Filter):挡掉“必然不存在”的 key

    • 适合“ID 是否存在”判定;误判率可控(如 1e-4)。

    • 维护方式:启动全量构建 + 增量同步(binlog/MQ)。

    • RedisBloom 用法(示例):

      BF.RESERVE item_bf 0.0001 50000000
      BF.ADD item_bf 123
      BF.EXISTS item_bf 456  // false -> 直接返回空或拦截
      
  • 多级缓存:本地(Caffeine/Guava)+ 分布式(Redis),先查本地能进一步抵御穿透。

  • 安全策略:WAF、机器人规则、黑名单、频次惩罚。

示例伪代码(Java/Caffeine + Redis + 空值缓存)

String cacheKey = "item:" + id;
String local = localCache.getIfPresent(cacheKey);
if (local != null) return decode(local);if (!bloom.exists(id)) return Result.empty(); // 快速挡掉String redisVal = redis.get(cacheKey);
if (redisVal != null) return decode(redisVal);// DB 查询
Item item = repo.findById(id);
if (item == null) {redis.setex(cacheKey, randTtl(30, 10), NULL_MARKER);return Result.empty();
}
redis.setex(cacheKey, randTtl(300, 60), encode(item));
localCache.put(cacheKey, encode(item));
return item;

二、缓存击穿(Cache Breakdown / Stampede)

What:是什么

单个热点 key 过期或被动失效 的瞬间,大量并发 同时未命中,全部回源 DB,造成 尖刺式 流量冲击。

Why:为什么会发生

  • 热点数据 TTL 到点统一过期。

  • 大 key 被误删/手动失效。

  • 热点在短时暴涨(突发热点)。

When:典型触发时机

  • 秒杀/大促、热门内容突然爆火。

  • 定点 00:00/整点批量过期。

Scenario:表现/定位线索

  • 少量 key 的 miss 突增;DB 后端出现短时高峰。

  • 接口 P99 延迟峰值在热点 key 上聚集。

  • Redis QPS 正常但应用到 DB 的回源强烈“尖峰”。

How:治理与防护

1)请求合并/互斥(SingleFlight)

  • 同一 key 的并发 miss,只放行 一个请求回源,其它等待,回源结果落缓存后“羊群分食”。

  • 可用分布式锁(SETNX + EX)或进程内 singleflight(如 Go sync/singleflight、Java 本地锁)。

2)逻辑过期 + 异步重建

  • 缓存值内带 expireAt(逻辑过期);过期后仍返回旧值,同时异步触发刷新,避免“黑洞期”。

  • 适合读多写少且可接受短时旧数据的场景。

3)永不过期(物理)+ 后台定时刷新

  • Redis 层不设 TTL 或较长 TTL;定时器/消息驱动刷新热点 key。

  • 配合“版本号/etag”防脏读。

4)多级缓存 + 热 key 保护

  • 本地缓存兜底;Nginx/Sidecar 层可做 microcache(1~3s)抗瞬时击穿。

  • 一些网关/代理支持热 key 合并(request collapsing)。

5)提前续租(Expire-After-Access + 提前刷新)

  • 在 TTL 进入“刷新窗口”时(例如剩余 <20%),后台线程提前刷新。

示例伪代码(分布式锁 + 双层缓存)

String cacheKey = "hot:" + id;
String v = redis.get(cacheKey);
if (v != null) return decode(v);// 只允许一个线程回源
String lockKey = "lock:" + cacheKey;
if (redis.set(lockKey, "1", "NX", "EX", 5)) {try {Item x = repo.findById(id);if (x == null) {redis.setex(cacheKey, randTtl(30, 10), NULL_MARKER);return Result.empty();}redis.setex(cacheKey, randTtl(300, 60), encode(x));localCache.put(cacheKey, encode(x));return x;} finally {redis.del(lockKey);}
} else {// 失败则小睡 + 读本地缓存兜底sleep(30);String local = localCache.getIfPresent(cacheKey);if (local != null) return decode(local);return Result.tryLater(); // 或短路降级
}

三、缓存雪崩(Cache Avalanche)

What:是什么

大量 key 在同一时刻集中失效(或缓存集群整体不可用),导致 大面积回源,DB 被洪峰压垮。

Why:为什么会发生

  • 批量设置了相同 TTL(齐过期)。

  • 集群整体故障/网络隔离/主从同时抖动。

  • 集中式预热后未做随机抖动。

When:典型触发时机

  • 整点统一过期策略、批量回灌的固定时间点。

  • 容量变更/切集群/故障恢复后的首次大流量。

Scenario:表现/定位线索

  • 缓存命中率“悬崖式”下跌;DB/下游服务同时暴涨。

  • Redis 集群节点抖动、连接暴增、超时上升。

  • 网关层出现全链路拥塞(队列积压、限流触发)。

How:治理与防护

1)TTL 随机抖动(核心)

  • 为每个 key 的 TTL 加 ±(0~10%) 随机,避免同刻过期。

2)分层缓存 & 微缓存

  • 本地缓存(Caffeine)兜底 1~5s;Nginx/Sidecar 微缓存 1~3s 抗瞬时洪峰。

3)流量治理

  • 限流:令牌桶/漏桶,优先保护下游(DB/SaaS)。

  • 熔断/降级:不可用时快速失败或返回兜底数据(骨架屏、上次成功快照)。

  • 舱壁隔离:线程池/连接池隔离不同接口,避免一处雪崩拖垮全站。

4)高可用架构

  • Redis 集群 + 副本 + 多 AZ/多机房;哨兵/Proxy 自动切换。

  • 双集群/双活 或“读多活写单元化”;关键热点维持 只读副本缓存

5)预热与分批回灌

  • 上线/扩容后按 批次 预热,错峰写入缓存。

  • 热点清单(TopN)优先级回灌;异步后台校准。


四、三者差异一图懂

维度穿透击穿雪崩
命中对象大量不存在的 key单个/少量热点 key 失效大量 key 同时失效/集群故障
流量形态稀疏、随机尖刺集中洪峰、面状
快速止血参数校验/空值缓存/布隆/限速请求合并/逻辑过期/本地兜底TTL 抖动/限流熔断/多级缓存
中长期布隆+增量维护永不过期+后台刷新高可用 + 错峰回灌

五、落地清单(Checklist)

通用

  • TTL 统一加随机抖动(±10% 参考)。

  • 本地缓存 + 分布式缓存 二级架构。

  • 接口参数严格校验;网关维度限流。

  • 关键接口舱壁隔离 + 熔断降级策略。

  • 关键路径埋点与追踪(命中/回源/DB)。

穿透专属

  • RedisBloom/Guava Bloom:全量构建 + binlog/MQ 增量。

  • 空值缓存(短 TTL);黑白名单 & WAF。

击穿专属

  • SingleFlight/SETNX 互斥锁。

  • 逻辑过期 + 异步重建;提前刷新窗口。

  • 热点 Key 清单与保护策略(永不过期/后台刷新)。

雪崩专属

  • 错峰预热与分批回灌。

  • 多活/跨 AZ;微缓存兜底。

  • 全链路限流 → 熔断 → 降级的三级闸门。


六、监控与排查(SRE 视角)

核心指标

  • 命中率(总体 & 接口 & key 维度)。

  • 回源率(Miss→DB 比例)、DB QPS/延迟/慢查询。

  • 热点分布(Top N key、热点迁移)。

  • Redis:连接数、ops、内存、淘汰、阻塞、复制延迟。

  • 应用:线程池队列长度、P95/P99、错误率、超时率。

  • 网关:限流命中、熔断次数、微缓存命中。

告警规则

  • 命中率骤降阈值(如 15%+ 在 1 分钟内)。

  • 单 key 回源速率/请求并发异常突增。

  • DB 连接池耗尽/慢查数激增。

  • Redis 主从复制落后/不可达。


七、写策略与一致性(触类旁通)

  • Cache Aside(旁路缓存):读先查缓存,miss 再 DB → 回写缓存;写:先 DB 再删缓存(延迟双删 10~500ms,抗读写并发脏读)。

  • Write-Through:写 DB 同时写缓存(代理层实现)。

  • Write-Behind:写入先落缓存/队列,异步冲刷 DB(需幂等/丢失可接受)。

  • 消息驱动失效:binlog/MQ 广播精确失效,降低 TTL 依赖。

延迟双删示例

updateDB(id, data);
redis.del(key(id));
sleep(100);           // 视业务与链路时延调整
redis.del(key(id));   // 再删一次,降低并发脏读窗口

八、方案选型建议(速查)

  • 读多写少 & 可容忍轻微旧数据:逻辑过期 + 异步刷新 + 本地缓存。

  • 强一致读:singleflight + 短 TTL + 精确失效(MQ/binlog),必要时读 DB 直出。

  • 超大流量 & 热点不可丢:永不过期 + 后台刷新 + Nginx 微缓存 + 限流熔断。

  • 安全风险高:布隆 + 空值缓存 + 严限流 + 验证码/WAF。


如果你愿意,我可以把这份指南整理成可复用的“团队维基页面”或加上你的技术栈(比如 Spring + Redisson、Go、Node)给出更贴近生产的示例代码与配置。


文章转载自:

http://HwuRocGy.cpctr.cn
http://2AtAAsod.cpctr.cn
http://pVRXk0o9.cpctr.cn
http://ClkMJ7CY.cpctr.cn
http://vCyuk1bB.cpctr.cn
http://cKuKxTQs.cpctr.cn
http://Q8KZnAMK.cpctr.cn
http://Pq9Uk25c.cpctr.cn
http://PHbBhm7c.cpctr.cn
http://dzD4OKMY.cpctr.cn
http://HtJ52W2n.cpctr.cn
http://acPwFg3F.cpctr.cn
http://A8GsrtHG.cpctr.cn
http://qtGGOv6l.cpctr.cn
http://n093kSMq.cpctr.cn
http://hoJPTxQP.cpctr.cn
http://v1D8LmuY.cpctr.cn
http://xQQs2Z90.cpctr.cn
http://Y43dISi4.cpctr.cn
http://QPzdBBBV.cpctr.cn
http://ctqBXwef.cpctr.cn
http://bGyKteAX.cpctr.cn
http://wvbSdRXd.cpctr.cn
http://Z99p2yN8.cpctr.cn
http://NlU5of4K.cpctr.cn
http://2zg4IeV8.cpctr.cn
http://tKcljsH3.cpctr.cn
http://IJ6VCpcs.cpctr.cn
http://BMqi1jYJ.cpctr.cn
http://NLB9k7JD.cpctr.cn
http://www.dtcms.com/a/384171.html

相关文章:

  • 【JavaEE】线程安全-内存可见性、指令全排序
  • MCP传输机制完全指南:Stdio、SSE、Streamable HTTP详解-实践案例-整体对比
  • 基于C#的快递打单系统源码+数据库+使用教程
  • RabbitMQ 高可用实战篇(Mirrored Queue + Cluster + 持久化整合)
  • RabbitMQ 命令执行流程与内核数据结构
  • Dify:Step1 本地化安装部署on MACOS
  • 有鹿机器人:以智能清洁 redefine 服务,以灵活租赁开启可能
  • 9.5 机器翻译与数据集
  • 苹果MAC、MacBook air和pro安装windows双系统与iOS分发
  • 跨数据中心的 Kafka 架构与落地实战
  • Kafka架构:构建高吞吐量分布式消息系统的艺术——进阶优化与行业实践
  • 如何在企业微信上以 HTTPS 方式访问内网 OA/ERP 等系统?
  • iOS 上架全流程指南 iOS 应用发布步骤、App Store 上架流程、uni-app 打包上传 ipa 与审核实战经验分享
  • 细粒度文本分类
  • Go 并发模型学习:从 goroutine 到 channel 的最佳实践
  • 高效解决多语言视频分发难题:Amazon MediaConvert 多语言输入配置 + CMAF 通用容器输出优化实战
  • 摆脱劳心,奔向劳体
  • pcl案例五 求类平面点云孔区面积
  • 第6.2节 Android Agent开发<三>
  • 利用kimi k2编写postgresql协议服务端的尝试
  • 深入理解 Java 集合框架
  • 第十届99全球链商节重点项目“全球纸基生态战略联盟”正式签约
  • 系统服务包括1-4章
  • 自动化C到Rust翻译工具探索:工具实操、不足与挑战解析
  • RabbitMQ 事件驱动与多进程架构
  • 飞书视频,设计测试case
  • python 自动化从入门到实战-开发一个文件自动备份工具(7)
  • 量子能量泵:一种基于并联电池与电容阵的动态直接升压架构
  • 从 WPF 到 Avalonia 的迁移系列实战篇7:EventTrigger 的迁移
  • pgNow:一款免费的PostgreSQL监控与性能诊断工具