这是一个很典型的“看着简单、调好难”的缓存优化点。很多团队在缓存穿透这个问题上,第一反应就是“加布隆过滤器”,但真正落地的过程中,从原理理解、工具选型到参数配置,每一步都藏着不少坑。我结合自己的实际经验,把缓存穿透的排查思路、布隆过滤器的原理,以及Guava和RedisBloom这两种主流实现从选型到落地写了一份完整的复盘,希望能帮你少走弯路。
1. 先搞清楚你的系统到底是不是缓存穿透
在动手引入布隆过滤器之前,我建议你先花半天时间把问题的性质确认清楚。很多人一看到数据库压力大,就急着上布隆过滤器,结果搞了半天发现根本不是缓存穿透,而是缓存击穿或者缓存雪崩,方案完全用错了。
1.1 穿透、击穿、雪崩,很多人分不清
这三个概念在日常沟通里经常被混着说,但它们的成因、现象和解决方案截然不同。
缓存穿透:查询一个根本不存在的数据。比如查一个不存在的商品ID,缓存里没有,数据库里也没有。如果这个请求量大,每次都会打到数据库,而数据库每次也都是白查一遍。因为数据不存在,缓存永远没法生效,请求会全部漏到存储层。这就像有人拿着假门票反复闯闸机,闸机每次都认真检查,但每次放行的都是空气。
缓存击穿:一个热点key在缓存过期的瞬间,大量请求同时涌入,全部打到数据库。注意这里的数据是真实存在的,只是因为缓存恰好失效了,导致并发都穿透到DB。这就像演唱会散场时,所有人都挤同一个出口,平时没问题,那一刻就堵死了。
缓存雪崩:一大批key在同一时间段集中过期,或者Redis实例宕机,导致大量请求直接打到数据库,数据库瞬间扛不住。这跟击穿的区别在于,击穿是单个热点key,雪崩是一大片key同时失效。
这三种问题的应对策略也完全不同:穿透靠布隆过滤器或者缓存空值,击穿靠互斥锁或者逻辑过期时间,雪崩靠过期时间抖动、多级缓存或者高可用架构。
1.2 怎么确认线上就是穿透问题
诊断穿透问题,我的排查路径一般是这样的:
- 看Redis监控,如果
get请求的QPS很高,但是hit rate(命中率)突然掉得很厉害,说明大量请求在缓存里没找到数据。 - 看数据库监控,如果
select的QPS和Redis的miss量基本持平,而且查询的都是同一个范围的不存在ID,那穿透基本坐实。 - 看应用日志,如果出现大量“查询结果为空”的记录,而且请求参数集中在某些非法或者异常的ID段,比如负数、超大数、随机字符串,那就更确定了。
我遇到过最典型的场景就是:某个对外开放的API接口,调用方因为代码bug,把一个ID字段传成了随机数。结果就是Redis的QPS很高但全部miss,数据库的QPS直接被打满,慢查询日志里全是那种花费几秒钟的无效查询。后来排查下来,就是典型的缓存穿透,而不是什么高并发问题。
1.3 穿透问题的排查链路与数据收集
先通过观测工具把数据收集起来。命令示例如下(以Redis和MySQL为例):
bash复制# Redis上查看key的命中情况
redis-cli INFO stats | grep keyspace_
# 如果是Redis 4.0以上,可以用latency监控查看延迟
redis-cli --latency-history
# 通过slowlog查看慢查询,确认是否有大量空查
redis-cli SLOWLOG GET 50
通过观察expired_keys和keyspace_hits、keyspace_misses三个指标,可以比较清晰地看到缓存命中的全貌。如果是穿透,keyspace_misses会显著高于正常水平,同时数据库的Com_select会同步飙升。
核心结论:穿透的本质是请求了不存在的数据,解决思路要么是让不存在的数据在缓存层“留下痕迹”(缓存空值),要么是在缓存层之前就“拦截非法请求”(布隆过滤器)。布隆过滤器就是后一种思路的经典方案。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 布隆过滤器原理:一个位数组和k个哈希函数的故事
布隆过滤器(Bloom Filter)是1970年由Burton Howard Bloom提出的,用于判断一个元素是否在一个集合中。它的核心特点是:如果它说一个元素“不存在”,那这个元素一定不存在;如果它说“存在”,那可能是误判。
2.1 为什么不直接用Set或者Hash
很多人会问:我在Redis里直接Set一个所有合法ID的集合,查的时候直接用SISMEMBER判断不就行了吗?为什么非要用布隆过滤器?
你用Set当然可以,但如果合法ID有几千万甚至上亿个,这个Set占用的内存会非常可观。以亿级数据为例,每个ID如果是long类型8字节,加上Redis的dictEntry开销(约60-100字节),一亿个ID可能要占用6-10GB内存。这还只是一个业务场景。布隆过滤器的核心价值在于:用极小的内存空间表示海量元素的集合,代价是牺牲一定的准确性(存在误判率)。
以布隆过滤器为例,一亿个元素,误判率控制在1%,只需要大约120MB的内存。如果是Set结构,这个量级通常在几个GB以上。这就是布隆过滤器的空间优势来源:它不需要存储元素本身,只存储元素的“指纹信息”,而指纹存储在固定大小的位数组里。
2.2 原理拆解:写入和查询两个阶段
布隆过滤器底层是一个bit数组(每个位置只有0和1两种状态),加上k个独立的哈希函数。
写入过程(add):
- 元素通过k个哈希函数计算出k个位置。
- 把这k个位置的值都置为1。
举
