缓存穿透治理:空值、布隆过滤器与回源限流组合
来源:17golang原创
时间:2026-10-08 12:14:22 105浏览 收藏
接口突然出现一批从未存在过的商品 ID,缓存查不到,数据库却被重复查询,这就是典型的缓存穿透。比较稳妥的组合是:先用 Bloom Filter 拦截“确定不存在”的键,再用短 TTL 空值缓存吸收重复 miss,最后把真正可能存在的请求放进原子限流器。三道防线各自负责不同阶段,任何一道失效时仍有下一层兜底。
Bloom Filter 只负责快速排除,空值缓存负责记住近期的不存在结果,回源限流负责保护数据库;只有查到真实数据后,才写入正常缓存。
- 过滤器返回不存在时直接结束,返回可能存在时不能跳过真实查询。
- 空值缓存要有短 TTL,并与正常数据使用清晰的值域区分。
- 限流计数和过期时间要原子化,指标要能区分三种拦截结果。
先把三道防线放在正确位置
以 product:{id} 作为统一键,读取顺序应是“请求规范化 → Bloom Filter → Redis 正向/空值缓存 → 回源限流 → 数据库”。Redis 官方文档说明 Bloom Filter 可以用很小空间判断元素是否可能存在:返回否定时可以确定不存在,返回肯定时仍可能是假阳性。因此它适合做前置筛选,不能替代商品表。
建议把值域分成真实 JSON 和固定空值标记,例如 __MISS__。空值 TTL 可以从 30~120 秒起步,正向缓存则按商品变更频率设置;TTL 不是越长越好,重点是避免一个短时不存在结果永久阻断后续新数据。

空值缓存与 Bloom Filter 要互相补位
Bloom Filter 适合挡住大量随机、长期无效的 ID;空值缓存适合吸收同一个无效 ID 被重复请求的情况。两者不能简单二选一:过滤器容量和误判率需要按有效 ID 数量估算,过滤器返回“可能存在”时仍要查 Redis 和数据库;数据库确认不存在后,再写入空值标记,让下一次请求快速结束。
写入新商品或恢复被删除商品时,要同步更新过滤器和缓存策略。删除商品时不要试图从普通 Bloom Filter 中强行删除,若业务需要频繁删除,可评估支持删除的 Cuckoo Filter,或者通过重建过滤器解决长期漂移。
回源限流用原子计数保护数据库
过滤器的“可能存在”结果仍可能把大量请求送到数据库,所以回源入口还需要按 IP、用户或资源维度做限制。下面的固定窗口示例用 Lua 把计数和首次过期放在一次执行中;生产环境也可以根据突发流量改成令牌桶。阈值只是示例,应结合数据库连接池、查询耗时和正常流量压测调整。
var allowSource = redis.NewScript(`
-- 计数与过期必须一次完成,避免并发下留下永不过期的键
local n = redis.call("INCR", KEYS[1])
if n == 1 then
redis.call("EXPIRE", KEYS[1], ARGV[1])
end
-- 返回 1 表示本次允许回源,0 表示超过窗口
if n
真正的读取函数还要区分三类结果:Bloom Filter 明确否定时返回 404;空值标记命中时返回 404;只有通过限流的“可能存在”请求才查库。查到实体写入正向缓存,查不到写入短 TTL 空值。若同一 Go 实例内仍有并发 miss,可再用 singleflight 合并一次查询,但它不能替代跨实例的 Redis 限流。
用指标确认组合没有变成新瓶颈
上线后不要只看整体缓存命中率,至少拆出 Bloom 拒绝数、空值命中数、正向命中数、回源放行数、限流拒绝数、数据库不存在数和限流器错误数。Redis 官方资料也强调,限流需要集中存储才能让多个服务实例共享同一配额;如果每个进程只维护本地计数,流量经过负载均衡后仍会绕过限制。
| 现象 | 优先检查 | 处理方向 |
|---|---|---|
| Bloom 拒绝很多 | 数据同步与过滤器容量 | 确认是否误把新数据漏写 |
| 空值命中很多 | 非法 ID 来源与 TTL | 治理调用方重试,避免 TTL 过长 |
| 回源限流很多 | 窗口、维度和突发形态 | 返回 429 或降级,不直接放开数据库 |
| Redis 出错 | 故障模式与连接池 | 限制回源并告警,避免“缓存故障变数据库故障” |

常见问题
Bloom Filter 返回存在就一定能查到数据吗?
不一定。Bloom Filter 允许假阳性,所以返回存在只能说明“可能存在”,最终结果仍由缓存和数据库确认。
空值缓存为什么不能设置很长时间?
因为暂时不存在的资源可能随后被创建,过长 TTL 会让新数据在空值过期前持续返回不存在。
固定窗口限流够不够用?
低复杂度接口通常够用;如果突发流量明显或窗口边界造成双倍突发,应改用滑动窗口或令牌桶,并继续保持原子执行。
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习