面试题背景
在高并发系统中,缓存穿透是一个经典且高频的面试考点。所谓缓存穿透,是指客户端请求查询一个数据库中不存在的数据,由于缓存中也没有这条数据,每次请求都会直接打到数据库上。如果这类请求被恶意利用(例如伪造大量不存在的 ID 发起攻击),数据库可能在短时间内承受巨大压力甚至被打垮。
面对这个问题,最常用的解决方案之一就是 Redis + 布隆过滤器(Bloom Filter)。本文将从面试答题的角度,把原理、落地方式和常见追问一次讲清楚。
一、缓存穿透的本质
正常缓存流程是:
- 请求先查 Redis 缓存。
- 命中则直接返回。
- 未命中则查数据库,再把结果写回缓存。
问题出在第 2、3 步之间:如果查询的 key 在数据库中根本不存在,那么缓存永远不会被写入,导致每次请求都穿透到数据库。这与缓存击穿(热点 key 失效)和缓存雪崩(大量 key 同时失效)不同,穿透的关键是“数据本身不存在”。
二、布隆过滤器是什么
布隆过滤器是一种空间效率极高的概率型数据结构,用于判断一个元素“可能存在”或“一定不存在”。
它的核心结构是一个很长的位数组(bit array)和若干个哈希函数:
- 添加元素:用 k 个哈希函数对元素求值,得到 k 个位置,把这些位置的 bit 置为 1。
- 查询元素:同样计算 k 个位置,只要有一个位置是 0,就说明该元素一定不存在;如果全为 1,则说明元素可能存在。
注意这里的关键特性:布隆过滤器只会误判“存在”,不会误判“不存在”。也就是说,它说“不存在”就一定不存在,这就足够用来拦截穿透请求了。
三、如何用它解决缓存穿透
整体思路是:在访问缓存和数据库之前,先用布隆过滤器做一层前置校验。
具体流程如下:
- 预热阶段:把数据库中所有合法的 key(例如所有商品 ID、用户 ID)预先加载进布隆过滤器。
- 请求到来时:
- 先用布隆过滤器判断该 key 是否存在。
- 如果判断为“不存在”,直接返回空结果,不查缓存、不查数据库。
- 如果判断为“可能存在”,再走正常的缓存 → 数据库查询流程。
- 新增数据时:向数据库写入新数据后,同步把该 key 加入布隆过滤器。
这样一来,那些查询不存在数据的恶意请求会在布隆过滤器这一层被直接拦掉,数据库压力大幅降低。
四、代码示例(伪代码)
public Object query(String key) {
// 1. 布隆过滤器前置校验
if (!bloomFilter.mightContain(key)) {
// 一定不存在,直接返回,避免穿透
return null;
}
// 2. 查缓存
Object cacheValue = redis.get(key);
if (cacheValue != null) {
return cacheValue;
}
// 3. 查数据库
Object dbValue = db.query(key);
if (dbValue != null) {
redis.set(key, dbValue, EXPIRE_TIME);
}
return dbValue;
}
配合 Redis 时,通常使用 RedisBloom 模块(提供 BF.ADD、BF.EXISTS 命令),或者用 Guava 的 BloomFilter 在应用层实现。分布式场景下更推荐 RedisBloom,保证多实例共享同一份过滤数据。
五、关键参数与误判率
布隆过滤器的误判率(false positive rate)由三个因素决定:
- 位数组长度 m
- 哈希函数个数 k
- 预期插入元素数量 n
在面试中常被问到如何估算。给定 n 和期望误判率 p,最优的位数组长度为:
m ≈ -n * ln(p) / (ln2)^2
最优哈希函数个数为:
k = (m / n) * ln2
例如预期插入 100 万个元素、误判率控制在 1%,大约需要 958 万 bit(约 1.14 MB)和 7 个哈希函数。可以看到,布隆过滤器非常节省内存,这也是它适合做前置拦截的原因。
六、优缺点与注意事项
优点:
- 空间占用极小,查询时间复杂度为 O(k)。
- 拦截不存在的数据,效果显著。
- 不存在“漏判不存在”的情况。
缺点与坑点:
- 存在误判:判断“存在”时可能是假阳性,导致少量请求仍会穿透,但比例可控。
- 不支持删除:标准布隆过滤器无法删除元素,因为置 1 的位可能被多个元素共享。若业务需要删除,可考虑计数布隆过滤器(Counting Bloom Filter)或布谷鸟过滤器。
- 数据同步问题:数据库新增数据后必须同步更新过滤器,否则新数据会被误判为不存在。通常采用异步补偿或定时重建。
- 预热成本:初始化时需要把全量合法 key 加载进去,数据量大时要注意耗时。
七、与其他方案的对比
面试中常被追问“还有哪些方案,怎么选”:
- 缓存空对象:把查询结果为空的 key 也缓存起来(设较短过期时间)。实现简单,但会占用额外内存,且可能被大量随机 key 攻击。
- 布隆过滤器:内存占用小,能拦截绝大多数无效请求,适合 key 集合相对固定的场景。
- 接口层限流/参数校验:从源头限制非法请求,通常作为补充手段。
实际生产中,布隆过滤器 + 缓存空对象组合使用是常见做法:过滤器挡住大部分,空对象兜住漏网的假阳性请求。
八、面试答题要点总结
回答这道题时,建议按以下逻辑组织:
- 先解释缓存穿透的定义和危害。
- 说明布隆过滤器的原理,强调“说不存在就一定不存在”。
- 讲清前置校验的完整流程。
- 补充误判率、不支持删除、数据同步等关键细节。
- 最后对比其他方案,体现方案的取舍能力。
掌握这几点,基本可以完整、有深度地回答“Redis 布隆过滤器如何解决缓存穿透”这道高频面试题。
未经允许不得转载:任鹏个人博客 » Redis 布隆过滤器如何解决缓存穿透

