登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  Redis

缓存穿透治理:空值、布隆过滤器与回源限流组合

来源: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 不是越长越好,重点是避免一个短时不存在结果永久阻断后续新数据。

Redis 缓存穿透治理结构说明图,展示 Bloom Filter、正向缓存、空值缓存、限流器和数据库的请求边界
图1:缓存穿透的分层结构说明图,展示三道防线与数据库回源边界;这是静态说明图,不是运行截图。

空值缓存与 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 出错故障模式与连接池限制回源并告警,避免“缓存故障变数据库故障”
Redis 缓存穿透治理观测说明图,展示 Bloom 拒绝、空值命中、回源限流和数据库结果的指标分流
图2:指标分流说明图,把请求结果拆成过滤、缓存、限流和回源四类,便于定位瓶颈;这是静态说明图。

常见问题

Bloom Filter 返回存在就一定能查到数据吗?

不一定。Bloom Filter 允许假阳性,所以返回存在只能说明“可能存在”,最终结果仍由缓存和数据库确认。

空值缓存为什么不能设置很长时间?

因为暂时不存在的资源可能随后被创建,过长 TTL 会让新数据在空值过期前持续返回不存在。

固定窗口限流够不够用?

低复杂度接口通常够用;如果突发流量明显或窗口边界造成双倍突发,应改用滑动窗口或令牌桶,并继续保持原子执行。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>