helloGPT缓存穿透指南
缓存穿透指攻击者或异常请求绕过缓存直接查询后端,导致数据库压力激增。常用防护包括:严格参数校验、布隆过滤器或黑白名单拦截非法Key、缓存空结果(短期)、请求频率限制与防刷、后端分布式锁或singleflight合并同类请求,以及对热点Key做预热与TTL抖动。合理组合可极大降低穿透风险。实践中有效。

先把概念讲清楚:缓存穿透到底是什么
想像你家门口有个快递柜(缓存),平时有人凭单号从柜子里取件,柜子没,就去楼下的仓库(数据库)取。如果有人不断提交不存在的单号,每次都把请求送到仓库,仓库就被压垮——这就是缓存穿透。它的核心在于:请求携带的Key在缓存中不存在且直接落到后端。
常见成因(简单归类)
- 无效或畸形参数:接口没校验,用户或爬虫传入随机ID。
- 恶意攻击:批量请求不存在的Key,做DOS或探测。
- 业务逻辑问题:一些Key本来不应该缓存、缓存策略设置不当。
- 缓存空值未处理:空结果每次都不入缓存,导致重复穿透。
为什么要重视(不只是“卡一下”)
数据库被大量并发穿透时会产生短时间的连接暴涨、慢查询、锁竞争,甚至服务熔断。更糟的是,问题难以定位:缓存命中率掉了,但日志里看到的是大量后端查询而非缓存异常。
防护手段一览(先看框架,再深入)
先给你一个清单,再逐项解释:
- 参数校验与黑白名单
- 布隆过滤器(Bloom Filter)
- 缓存空值(negative caching)
- 请求限流与防刷
- 请求合并(singleflight / 去重)
- 分布式锁与原子操作
- 热点Key预热与TTL抖动
- 监控与告警、主动演练
参数校验与黑白名单(第一道防线)
最简单也最重要:在进入缓存层之前把明显非法的Key拦掉。比如ID应为正整数、UUID格式、长度限制、范围限制等。对已知可疑来源或IP做黑名单;对可信调用方做白名单授权。成本低,收益高。
布隆过滤器:高效拦截“根本不存在”的Key
布隆过滤器是一种空间高效的概率集合结构。把所有合法或已有的Key放进去,查询时先问布隆过滤器:“这个Key可能存在吗?”如果答案是“否”,就直接返回,不访问数据库;如果是“可能”,才去缓存/数据库。
优点:内存小、速度快,能拦掉绝大多数恶意探测。缺点:存在一定误报(将不存在的Key判断为可能存在),需要更新维护过滤器(比如新增或删除Key时)。
缓存空值(negative caching):缓存“没有”的事实
当数据库返回空(比如用户ID不存在),可以把这个空结果缓存一段短时间(例如几分钟),避免短时间内重复落到数据库。但需要注意TTL不要太长,否则真被创建的数据会被延迟可见。
- 适用场景:真实不存在的数据、探测请求等。
- 注意点:设置短TTL并加上随机抖动,避免集中失效造成雪崩。
请求合并 / singleflight:把并发的相同请求合并成一次后端查询
当大量并发请求同时命中某个未命中的Key时,可以用singleflight(或类似机制)把这些请求合并,只有第一个去后端查询,其他等待结果并共享返回。Go的singleflight、Java的异步Future合并模式都常用。
分布式锁与原子操作:保护写入与缓存重建
在缓存穿透导致的并发回源场景下,可以使用分布式锁(如Redis的SETNX+过期)确保只有一个线程去查询并回填缓存,其他线程等待或直接返回。要避免死锁、超时设置合理,并结合singleflight以减少复杂度。
请求限流与防刷:从流量端做止损
结合IP限流、用户限流、接口熔断,在短时间内检测到同一来源频繁请求异常Key时,直接降级或拦截。限流策略可基于滑动窗口、漏桶或令牌桶实现。
热点Key预热与TTL抖动:降低突发失效风险
热点Key(比如首页配置、热销商品)应该预先加载到缓存,避免因缓存失效导致瞬时大量回源。同时给TTL加上随机抖动,避免大量Key在同一时刻过期,引发雪崩。
不同方法的对比(便于选择)
| 策略 | 效果 | 复杂度 | 副作用/注意 |
| 参数校验 | 拦截明显无效请求,高效 | 低 | 误判风险低,需覆盖面广 |
| 布隆过滤器 | 拦截绝大多数探测,有概率误报 | 中 | 需维护集合、误报导致少量回源 |
| 缓存空值 | 减少重复回源 | 低 | 可能延迟新数据可见,TTL要短 |
| 请求合并/singleflight | 避免短时间并发回源 | 中 | 实现较复杂,适合热点场景 |
| 限流/防刷 | 有效止损,保护后端 | 中 | 用户体验可能受影响,需策略微调 |
实践建议:一步步来,不要一次性全抛向技术
- 先做参数校验和简单白名单/黑名单,这是最低成本、收益最大的改动。
- 对已知Key建立布隆过滤器:把业务上所有“已存在”的主键集合导入,作为第一道过滤。
- 在数据库返回空时,缓存空结果并设置短TTL(比如几分钟),同时加入TTL抖动。
- 对热点API引入singleflight或请求合并,避免瞬时并发回源。
- 对关键路径做限流,设置合理阈值和降级策略,结合IP与用户维度。
- 对热点做预热与监控:监控缓存命中率、数据库QPS、异常Key分布并配置告警。
- 定期演练:模拟穿透攻击场景,测试限流和降级效果,调整阈值。
监控与告警的重要性
任何防护都有失败的可能,及时发现是关键。常见的监控指标包括:
- 缓存命中率(整体与按Key分类)
- 数据库QPS与慢查询数
- 异常Key访问分布(TopN)
- 请求来源IP分布
当监控显示短时间内某些不存在的Key访问激增时,应触发告警并自动限制来源。
常见误区与陷阱(别踩坑)
- 把空值缓存TTL设置太长,导致数据创建后长时间不可见。
- 盲目依赖布隆过滤器却不更新集合,导致大量合法新Key被拦截或误判。
- 单靠某一种手段(比如只限流)来应对所有穿透场景,忽视组合策略。
- 分布式锁实现不当引发死锁或性能下降,注意超时与幂等性。
一个小例子(思路,不是完整代码)
请求流程可以这么写成步骤:1)参数校验;2)问布隆过滤器,否->直接返回空或404;3)查缓存,有则返回;4)未命中,进入singleflight或加锁;5)查询数据库并回填缓存(或缓存空值);6)释放并返回。
选取优先级(在资源有限的情况下)
如果你现在不能一次性实现所有方法,优先级建议:
- 参数校验与限流
- 缓存空值(短TTL)
- 布隆过滤器
- 请求合并(singleflight)与分布式锁
- 预热与监控告警完善
结语(嗯,就到这里)
缓存穿透的防护不是靠某一条神奇规则解决的,而是组合拳:从源头校验、到内存结构优化、到流控与合并,再到监控和演练。把这些方法按优先级逐步落地,会比一开始想做“完美”方案更实用。好吧,说到这儿我得去改下生产环境的布隆过滤器参数,先这样……