缓存穿透是查询根本不存在的key导致请求击穿缓存直压数据库;布隆过滤器通过“不存在即确定不存在”的特性,在入口拦截99%非法key,需与数据写入强同步、常驻内存、合理配置误判率与容量。

缓存穿透是什么,为什么布隆过滤器能挡得住
缓存穿透本质是查一个**根本不存在的 key**,比如恶意请求 user_id=999999999 这种数据库里压根没记录的值。Redis 没命中就打到 DB,DB 也查不到,还把空结果(或不缓存)放行——大量这类请求直接拖垮数据库。
布隆过滤器(Bloom Filter)是个「存在性概率判断器」:它说「不存在」,那一定不存在;说「可能存在」,才需要继续查 Redis/DB。用它在请求入口拦一遍,就能筛掉 99% 的非法 key。
Go 生态里最常用的是 github.com/yourbasic/bloom 或 golang.org/x/exp/bloom(后者仍在 x/exp 下,非正式稳定版)。别自己手写,哈希碰撞和位图管理很容易出错。
怎么在 Go 请求链路里加布隆过滤器
核心原则:布隆过滤器必须和数据写入强同步,否则漏加就等于白加。不能只在读路径初始化,得在「数据落库成功后」立刻插入过滤器。
立即学习“go语言免费学习笔记(深入)”;
- 初始化时用预估总量和误判率算容量,比如预计 1000 万用户、容忍 0.1% 误判,
bloom.New(uint64(10_000_000), 0.001) - 写 DB 成功后,立刻调用
filter.Add([]byte(strconv.Itoa(user.ID)))—— 注意 key 类型要和查询时完全一致(比如都转 string,别一个用 int 一个用 string) - 读请求来时,先
filter.Test([]byte(userIDStr)),返回false就直接返回空或错误,不碰 Redis 和 DB - 过滤器本身建议常驻内存(如作为全局变量),不要每次请求 new,更不要存在 Redis 里(序列化开销大且难更新)
常见踩坑点:误判率、扩容、热更新全得手动管
布隆过滤器不是“设好就完事”的黑盒。它没有删除操作,也不支持动态扩容,这些都得你兜底。
- 误判率不是越低越好:
0.0001比0.01占两倍内存,但实际业务中 1% 误判率已足够拦截绝大多数穿透请求 - 一旦预估总量翻倍,旧过滤器就会快速饱和,误判率飙升——必须提前规划分片(比如按 user_id % 16 分 16 个 filter)或定时重建
- 删数据时布隆过滤器无法删除对应项,只能接受「该 key 后续会短暂误判为存在」,靠 TTL 或定期全量重建缓解
- 如果用
golang.org/x/exp/bloom,注意它不支持自定义哈希函数,且Test和Add的输入必须是同一类型切片([]byte最稳妥)
要不要用 Redis 实现布隆过滤器
Redis 4.0+ 有 BF.RESERVE/BF.ADD,但生产环境慎用。
- 网络 RTT 会让每次查询多一次 round-trip,比内存 filter 慢 10 倍以上,对高 QPS 场景是硬伤
- Redis Bloom 是概率结构,同样面临扩容难、无法删除的问题,还多了 failover 同步延迟风险
- 真正适合它的场景只有两种:跨服务共享过滤器(比如多个 Go 实例共用)、或业务完全无法改代码只能靠中间件拦截
- 单体 Go 服务内,老老实实用内存 filter + 写库后同步更新,简单、快、可控
布隆过滤器本身不解决「合法 key 查空」问题(比如用户注销后还查他),那种得靠缓存空值 + 随机过期时间,和布隆是两套逻辑,别混在一起。










