缓存穿透解决方案:布隆过滤器
缓存穿透这个问题,说白了就是请求绕过了缓存,直接打到数据库上。我早年做电商秒杀系统时,就吃过这个亏——恶意用户拿了一堆不存在的商品ID来刷,Redis里查不到,每次都穿透到MySQL,差点把库打挂。
那怎么解决?今天我们就来聊聊布隆过滤器(Bloom Filter)。它不是什么新概念,但对付缓存穿透确实管用。
布隆过滤器原理
布隆过滤器的核心思想很简单:用多个哈希函数,把数据映射到一个很长的二进制向量里。
你想想看,传统做法是把所有合法key存到Set里,但数据量一大,内存就扛不住了。布隆过滤器不一样,它不存原始数据,只存几个“指纹”。
具体怎么做的?
- 准备一个长度为m的bit数组,初始全为0。
- 准备k个独立的哈希函数。
- 插入元素时,用k个哈希函数算出k个位置,把这些位置都置为1。
- 查询元素时,同样算出k个位置,如果所有位置都是1,就认为元素可能存在;只要有一个是0,就肯定不存在。
关键特性:布隆过滤器说“不存在”,那一定不存在;说“存在”,只是可能存在(有误判率)。
为什么会误判?因为不同元素的哈希结果可能重叠,把别人的位置占了。但反过来,只要有一个位是0,那这个元素肯定没被插入过——这个特性正好用来拦截不存在的key。
实现步骤
在实际项目中,我一般按这几步来落地布隆过滤器:
第一步:确定参数
需要预估两个值:预期数据量n和可接受的误判率p。然后算出bit数组长度m和哈希函数个数k。
| 参数 | 含义 | 经验值 |
|---|---|---|
| n | 预期插入的元素总数 | 根据业务量估算,留30%余量 |
| p | 可接受的误判率 | 一般取1%~5% |
| m | bit数组长度 | m = -n·ln(p) / (ln2)² |
| k | 哈希函数个数 | k = (m/n)·ln2 |
我的经验:n取业务峰值数据的1.3倍,p设1%就够用。m算出来如果太大,可以拆成多个小布隆过滤器分片。
第二步:初始化bit数组
用Redis的bitmap或者Java的BitSet都行。我习惯用Redis,因为可以跨服务共享。
第三步:预热数据
系统启动时,把所有合法key(比如所有商品ID)都插入布隆过滤器。这一步很关键,漏了就等于白做。
第四步:请求拦截
每次查询先过布隆过滤器。如果返回“不存在”,直接返回空结果,不走缓存和数据库。如果返回“可能存在”,才继续查缓存。
代码示例
我用Java + Redis实现一个简单的布隆过滤器。这里用的是Redisson客户端,它内置了布隆过滤器实现。
// 1. 初始化布隆过滤器
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("productBloom");
// 预期100万数据,误判率1%
bloomFilter.tryInit(1000000L, 0.01);
// 2. 预热:把所有商品ID插入
List<String> allProductIds = productService.getAllIds();
for (String id : allProductIds) {
bloomFilter.add(id);
}
// 3. 查询拦截
public Product getProduct(String productId) {
// 第一步:布隆过滤器拦截
if (!bloomFilter.contains(productId)) {
// 一定不存在,直接返回空
return null;
}
// 第二步:查缓存
Product product = cacheService.get(productId);
if (product != null) {
return product;
}
// 第三步:查数据库(此时才真正可能穿透)
product = databaseService.get(productId);
if (product != null) {
cacheService.set(productId, product);
}
return product;
}
注意:布隆过滤器不支持删除操作。如果你有删除key的需求,可以考虑用Counting Bloom Filter,或者定期重建过滤器。
适用场景
布隆过滤器不是万能的,它有自己的“舒适区”。我总结了几种典型场景:
| 场景 | 是否推荐 | 原因 |
|---|---|---|
| 缓存穿透防护 | ✅ 强烈推荐 | 拦截不存在的key,效果立竿见影 |
| 爬虫URL去重 | ✅ 推荐 | URL数量巨大,布隆过滤器省内存 |
| 垃圾邮件过滤 | ✅ 推荐 | 允许少量误判,但能拦截大部分垃圾 |
| 需要精确删除的场景 | ❌ 不推荐 | 标准布隆过滤器不支持删除 |
| 数据量极小(几百条) | ❌ 不推荐 | 直接用Set更简单,没必要上布隆 |
一句话总结:布隆过滤器最适合“数据量大、允许少量误判、只需要判断存在性”的场景。缓存穿透防护是它的经典用法。
避坑指南
我曾经在一个项目里踩过坑,分享出来大家引以为戒:
- 误判率不是0:布隆过滤器说“存在”时,数据库里可能没有。所以查询数据库的逻辑不能省,只是概率降低了。
- 数据变化后要重建:如果业务数据频繁增删,布隆过滤器会越来越不准。我一般每天凌晨重建一次。
- 哈希函数要选好:别用太弱的哈希,容易碰撞。MurmurHash、FNV系列都行。
- bit数组别太小:m/n比值低于8时,误判率会飙升。算一下再动手。
嗯,布隆过滤器就聊到这儿。它不是什么高深技术,但用好了确实能解决大问题。下次遇到缓存穿透,不妨试试这个方案。