使用场景
适用于数据命中不高、 数据相对固定、 实时性低(通常是数据集较大) 的应用场景。比如:
- 解决缓存穿透;
- 爬虫时记录已爬取的网页;
- 记录黑名单;
原理
数据结构是一个bit数组,布隆过滤器通过hash算法(无偏hash函数)将值换算成对应的bit位,并存入布隆过滤器。所谓无偏hash函数就是能够把元素的hash值算的比较均匀。
如果查询的值经过hash换算成值后在布隆过滤器没有找到对应的bit(存在一个bit位不匹配),表示该值一定不存在,如果找到了对应的匹配位,也不能确定该值一定存在,因为有可能是别的值占据了对应的bit(hash冲突)。
可以多次hash生成多个bit位,降低hash冲突概率。
值的存储示意图如下:
优缺点
优点
缓存空间占用很少,效率高。
缺点
- 不支持删除
因为多个不同的数据对应的可能是同一组bit位,如果删除了一个数据bit位可能将其他的数据对应bit位也删除了。
可以通过维护一个counter对应bit位来支持删除。
- 存在一定误差
判断某一个值存在,存在一定误差,因为hash碰撞导致不同值生成的哈希值相同,当某个不存在的值计算的hash值已存在,则得出的结论也是存在的。为了减少误差率,可以增加hash的次数和增加bit数组位的长度;
实现方式
redis
redis中的布隆过滤器,redis4.0之后加入了module,适合分布式场景。
引入redisson客户端:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.5</version>
</dependency>
示例伪代码:
public class RedissonBloomFilter {
public static void main(String[] args) {
Config config = new Config();
config.useSingleServer().setAddress("redis://localhost:6379");
// 构造Redisson
RedissonClient redisson = Redisson.create(config);
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("nameList");
// 初始化布隆过滤器:预计元素为100000000L(1亿),误差率为3%,根据这两个参数会计算出底层的bit数组大小
bloomFilter.tryInit(100000000L, 0.03);
// 将jay插入到布隆过滤器中
bloomFilter.add("jay");
// 判断下面号码是否在布隆过滤器中
System.out.println(bloomFilter.contains("jolin")); // false
System.out.println(bloomFilter.contains("kunlin")); // false
System.out.println(bloomFilter.contains("jay")); // true
}
}
使用布隆过滤器需要把所有数据提前放入布隆过滤器,并且在增加数据时也要往布隆过滤器里放,布隆过滤器缓存过滤伪代码:
// 初始化布隆过滤器
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("nameList");
// 初始化布隆过滤器:预计元素为100000000L(1亿),误差率为3%
bloomFilter.tryInit(100000000L,0.03);
// 把所有数据存入布隆过滤器
void init(){
for (String key: keys) {
bloomFilter.put(key);
}
}
String get(String key) {
// 从布隆过滤器这一级缓存判断下key是否存在
Boolean exist = bloomFilter.contains(key);
if(!exist){
return "";
}
// 从缓存中获取数据
String cacheValue = cache.get(key);
// 缓存为空
if (StringUtils.isBlank(cacheValue)) {
// 从存储中获取
String storageValue = storage.get(key);
cache.set(key, storageValue);
// 如果存储数据为空,需要设置一个过期时间(300秒)
if (storageValue == null) {
cache.expire(key, 60 * 5);
}
return storageValue;
} else {
// 缓存非空
return cacheValue;
}
}
注意:布隆过滤器不能删除数据,如果要删除得重新初始化数据。
guava
google的guava包实现了布隆过滤器,适合单机场景。
实际应用
抢单王通过布隆过滤器判断电子行业料号是否存在,不存在才继续流程。线上redis版本为2.8,不支持redis提供的布隆过滤器,通过guava来实现的,guava是google提供的java核心类库。