缓存穿透说的是:有人查一个数据库里根本不存在的数据,缓存里没有,每次请求都直接打到了数据库上。我们线上确实遇到过有人拿一堆乱填的 id 来刷接口。空值缓存、布隆过滤器、接口限流三种我都试过,这里把各自解决什么、不解决什么说清楚。

当时第一反应是想上布隆过滤器,因为听起来最「正统」,布隆过滤器、限流这些词在文章里出镜率特别高。但真把三种都跑完一轮,才明白它们不是一回事,也不是谁都能替代谁。下面按我实际用下来的感受分开讲。

一、空值缓存:最省事,但有前提

查不到的时候,往 Redis 里塞一个空值(比如 null 或一个约定的占位对象),设一个短过期,比如 60 秒。这样同一个不存在的 key 在过期时间内就不会再打到库上。我最后只用这一种,原因就是它改动最小:原本的查询逻辑外面套一层就行,不用引入新组件。

它的代价是:如果有人拿海量不同的不存在 id 来刷,缓存里会堆一堆空值,占内存。所以过期一定要短,而且最好对 key 的格式先做校验,明显不合法的 id 根本不进缓存逻辑。这点我一开始没注意,后来是看 Redis 内存涨了才补上的。

二、布隆过滤器:能挡在缓存前面

布隆过滤器用一个位数组判断「这个 key 可能存在 /一定不存在」。把所有合法的 id 预先放进去,请求进来先问过滤器,确定不存在的直接返回,连缓存都不用查。它解决的是「海量不存在的 key」那类穿透,而且几乎不占内存。

但它不解决误判:过滤器说「可能存在」时,其实也可能不存在,所以后面该查缓存查缓存、该查库查库,它只是帮你在最前面拦掉大部分明显非法的请求。另外,如果 id 是动态增长的(比如新注册的用户),过滤器得持续更新,我们那套系统是离线批处理为主的,维护这个成本我觉得划不来,所以没长期用。

三、接口限流:它其实不是一回事

这个经常被和前两个放在一起讲,但我想单独拎出来说:限流挡的是「请求太多」,不管你查的是不是存在的数据。它防的是有人拿穿透当手段来打挂你的数据库,解决的是量,不解决「查不存在数据」这个问题本身。我们后来在网关层加了按 IP 的限流,作为兜底,但它和空值缓存是互补关系,不是替代关系。

我最后怎么留的

空值缓存加短过期,作为第一道防线;网关层限流作为兜底,挡掉恶意刷量。布隆过滤器在我们的场景里收益不明显,就没上。说「三种都试过」不是都留着,而是每种都实跑过才决定哪些不要——这点我建议也按自己系统的请求特征来,别照抄。