← 返回题目列表

分布式系统中的缓存穿透是什么?如何解决?

高频 中等 第 3 / 26 题 更新于 2026/10/03
缓存穿透布隆过滤器缓存空值
学习 AI 实战项目

简化版

缓存穿透指请求查询的数据在缓存和数据库里都不存在,导致每次请求都绕过缓存打到数据库。常见原因是恶意请求不存在的 ID,或者业务里确实有大量空结果查询。解决方案是参数校验、缓存空值、布隆过滤器,以及对异常流量做限流和风控。

详细版

缓存穿透的关键是“数据不存在”。普通缓存只有查到数据才写缓存;如果数据库查不到,不写缓存,那么下次同样查询还是会打到数据库。

常见方案:

方案原理优点注意点
参数校验非法 ID、格式错误直接拦截成本最低只能挡明显非法请求
缓存空值DB 查不到也缓存一个空标记简单有效TTL 要短,防止新数据被空值挡住
布隆过滤器先判断 key 是否可能存在内存小、挡随机 key 攻击有误判,需维护过滤器
限流风控对异常来源限速或封禁抗攻击需要监控和策略

缓存空值适合同一个不存在 key 被反复查;布隆过滤器适合大量随机不存在 key 的攻击。

完整版教学

一、穿透为什么会打到数据库

缓存命中的前提是缓存里有值。正常商品 ID 第一次查库后会回填缓存,后续请求都走缓存。但如果用户查询 id=-1 或一个根本不存在的商品 ID,数据库返回空,很多代码不会把空结果写入缓存。于是下一次同样请求还是缓存未命中,继续查数据库。

如果只是少量请求,问题不大;如果攻击者批量构造不存在 ID,每个请求都绕过缓存,数据库就被迫处理大量无意义查询。这就是穿透。

二、参数校验是第一道门

很多穿透请求其实在业务入口就能挡掉。比如用户 ID 必须是正整数,手机号必须符合格式,订单号长度固定。如果连格式都不合法,就不应该进入缓存和数据库查询链路。

参数校验成本最低,但只能挡明显非法数据。对于格式合法但业务上不存在的 ID,比如一个很大的商品 ID,仍然需要缓存空值或布隆过滤器。

三、缓存空值怎么用

缓存空值的做法是:数据库查不到时,也往 Redis 写一个特殊空标记,比如 NULL,并设置较短 TTL。下次同样 key 再来,缓存命中空标记,直接返回不存在,不再查数据库。

短 TTL 很关键。因为数据可能后来被创建出来,如果空值缓存时间太长,新数据会被旧空值挡住。通常空值 TTL 会比正常数据短很多。还要注意空间问题:如果攻击者构造海量不同 key,缓存空值会占用大量 Redis 内存,这时布隆过滤器更合适。

四、布隆过滤器适合挡随机攻击

布隆过滤器是一种概率型数据结构,用较小内存判断一个 key 是否可能存在。在合法 key 已完整、及时加入过滤器的前提下,它不会产生假阴性,但可能把不存在的数据误判为“可能存在”。因此过滤器判定不存在时可直接拦截,判定可能存在时仍要走正常查询;若新增数据写库后未同步加入过滤器,也会错误拦截真实数据。

布隆过滤器适合把全量合法 ID 提前加载进去,用来拦截随机不存在 ID。代价是要维护它:新增数据要同步加入过滤器,删除数据如果要求精确处理,需要重建或使用支持删除的计数布隆过滤器。

五、穿透和击穿、雪崩怎么区分

穿透查的是不存在数据;击穿查的是存在的热点数据,但缓存刚好过期;雪崩查的是存在数据,但大量 key 或缓存服务同时失效。三者的根因和解法完全不同。

穿透偏安全和过滤,击穿偏热点保护,雪崩偏容量和高可用。面试时先用“数据是否存在”和“影响范围是单 key 还是多 key”切开,答案就很清楚。

六、面试追问与工程边界

面试官常会追问“布隆过滤器误判怎么办”。就数据结构本身而言,只要元素确实已加入,布隆过滤器不会把它判成不存在;但工程上若新增事件漏消费,过滤器并不完整,仍可能错杀真实数据。发布新数据时应先把 ID 加入过滤器再开放读取,或在拦截链路保留版本与重建机制。误判率可通过位数组大小和哈希函数个数控制。

还要注意数据变更。如果新商品、新用户不断创建,布隆过滤器要同步追加;如果删除很多数据,普通布隆过滤器不支持精确删除,可能需要定期重建或使用计数布隆过滤器。布隆过滤器适合挡攻击,不适合承担唯一的数据正确性判断。

七、容量计算、发布顺序与降级

布隆过滤器不是随意开一块位图。预计元素数为 n、目标误判率为 p 时,位数近似为 m=-n×ln(p)/(ln2)^2,哈希函数数约为 k=(m/n)×ln2。例如 1000 万个 ID、目标误判率 0.1%,需要约 1.44 亿 bit,即约 17.1MiB,哈希函数约 10 个;还要为未来增长留容量,否则实际误判率会上升。

新数据写入也有顺序问题。若数据库记录已经对外可见、过滤器却尚未加入该 ID,过滤器会把真实数据判成不存在。可先把 ID 加入过滤器,再提交并开放数据库记录:即使数据库写失败,最多产生一次“可能存在”的假阳性,请求仍会查库并得到不存在,不会错误拦截真实数据。异步维护时则要有补偿队列、完整性监控和可切换的新旧过滤器版本。

过滤器不可用时不能默认放行全部随机请求,否则保护层故障会直接变成数据库攻击。应保留接口级限流、合法 ID 范围校验、短期空值缓存和数据库回源预算;过滤器判定不存在的比例突然升高,也要警惕发布漏写或版本切换错误。

布隆过滤器的“无假阴性”成立于目标元素已被正确加入且位图未被错误删除;工程同步不完整仍会制造假阴性。

八、为什么空值缓存不能替代过滤器

空值缓存只对“重复查询同一个不存在 Key”有效。若攻击者每次生成新 ID,第一次仍会查数据库,而且 Redis 会积累大量负缓存。假设 500 万个随机 Key 连同键名、value 和对象开销平均占 80B,仅一轮就约占 5000000×80≈400MB,还未计内存碎片。

因此负缓存要限制 TTL、Key 长度和写入速率,必要时只缓存重复频率达到阈值的不存在 Key。布隆过滤器用固定规模位图更适合大基数随机攻击,但误判请求仍会进入数据库,数据库侧的索引、限流和超时不能省略。

无效请求形态优先手段原因
同一不存在 ID 高频重复短 TTL 空值一次查库后即可命中
大量随机合法格式 ID布隆过滤器 + 限流负缓存无法复用且占内存
明显非法格式或越界 ID参数校验无需进入缓存链路
过滤器故障或重建回源预算 + 降级防止保护层失效拖垮数据库

数据库超时与明确查无记录也必须区分。超时代表结果未知,不能写空值;否则短暂故障会把真实数据缓存成不存在。只有权威查询明确返回不存在,才能写负缓存或 tombstone。

九、过滤器怎样滚动重建

普通布隆过滤器无法精确删除,数据大量删除或容量超预期后,需要构建新版本。可先离线扫描权威 ID 生成 bloom:v2,构建期间把新增 ID 同时写入 v1/v2;校验元素量和抽样误判率后,原子切换读指针,观察稳定再回收 v1。

重建期间若只扫描一次数据库而没有承接增量,新建记录会遗漏并形成工程假阴性。若切换失败,应继续使用旧版本并依靠限流,而不是切到一个只完成部分分片的过滤器。

监控过滤器拒绝率、放行后数据库不存在率、位图容量利用和新增同步失败。放行后“不存在”比例持续上升,可能是攻击增强、误判率恶化或删除数据长期残留,需要结合容量和事件链路判断。

构建 v2 全量快照 -> 新增 ID 双写 v1/v2 -> 校验 v2
-> 切换读指针到 v2 -> 观察拒绝率与误判 -> 延迟回收 v1

十、常见误区与追问

  • 误区:缓存穿透就是缓存没命中。 关键是数据库也没有该数据,导致恶意或重复请求持续打库。
  • 误区:布隆过滤器能准确判断存在。 布隆过滤器可判断一定不存在,存在判断可能有误判。
  • 误区:空值缓存 TTL 可以很长。 过长会影响后续真实数据写入后的可见性。
  • 追问:空值缓存有什么风险? 可能缓存大量无效 key,占内存,并造成短时间旧空值。
  • 追问:布隆过滤器删除难怎么办? 可重建过滤器、使用计数布隆或结合短 TTL。
  • 追问:接口层还要做什么? 参数校验、鉴权、限流,拦截明显非法 key。

十一、加强记忆

缓存穿透的关键词是“不存在的数据”。缓存没有,数据库也没有,每次都打到 DB。治理顺序是入口参数校验先挡明显非法请求,同一个不存在 key 用缓存空值,大量随机不存在 key 用布隆过滤器,再用限流风控兜住攻击流量。穿透和击穿、雪崩最大的区别是:穿透查的数据本来就不存在。