1、什么是布隆过滤器
布隆过滤器(Bloom Filter)是 1970 年由布隆提出的,是一种非常节省空间的概率数据结构,运行速度快,占用内存小,但是有一定的误判率且无法删除元素。它实际上是一个很长的二进制向量和一系列随机映射函数组成,主要用于判断一个元素是否在一个集合中。
通常我们都会遇到判断一个元素是否在某个集合中的业务场景,这个时候我们可能都是采用 HashMap的Put方法或者其他集合将数据保存起来,然后进行比较确定,但是如果元素很多的情况下,采用这种方式就会非常浪费空间,最终达到瓶颈,检索速度也会越来越慢,这时布隆过滤器(Bloom Filter)就应运而生了。
1.1 布隆过滤器的优点:
-
支持海量数据场景下高效判断元素是否存在
-
布隆过滤器存储空间小,并且节省空间,不存储数据本身,仅存储hash结果取模运算后的位标记
-
不存储数据本身,比较适合某些保密场景
1.2 布隆过滤器的缺点:
-
不存储数据本身,所以只能添加但不可删除,因为删掉元素会导致误判率增加
-
由于存在hash碰撞,匹配结果如果是“存在于过滤器中”,实际不一定存在
-
当容量快满时,hash碰撞的概率变大,插入、查询的错误率也就随之增加了
-
布隆过滤器中一个元素如果判断结果为存在的时候元素不一定存在,但是判断结果为不存在的时候则一定不存在。因此,布隆过滤器不适合那些对结果必须精准的应用场景。
1.3 其他问题
-
不支持计数,同一个元素可以多次插入,但效果和插入一次相同
-
由于错误率影响hash函数的数量,当hash函数越多,每次插入、查询需做的hash操作就越多
2、布隆过滤器原理
布隆过滤器是由一个固定大小的二进制向量或者位图(bitmap)和一系列映射函数组成的。位数组中的每个元素都只占用 1 bit ,并且数组中元素只能是 0 或者 1。这样申请一个 100w 个元素的位数组只占用 1000000Bit / 8 = 125000 Byte = 125000/1024 KB ≈ 122KB 的空间。
2.1 添加元素
通过多个hash函数,算出一个值,然后将这个值所在的方格置为1
2.2 查找元素
通过几个哈希函数分别算出各个值,然后看其对应的地方是否都是1,如果存在一个不是1的情况,该新数据一定不存在于这个布隆过滤器中。
由于多个不同的数据通过hash函数算出来的结果是会有重复的,会存在某个位置是别的数据通过hash函数置为的1,所以不一定能判定该元素一定存在,存在一定的误判率。
2.3 不能删除元素
布隆过滤器通常不支持从集合中删除元素,因为删除一个元素会影响其他元素的哈希值,增加了误判率。
注:布隆过滤器可以判断某个数据一定不存在,但是无法判断一定存在。
3、Redis布隆过滤器的实践
Google开源的Guava自带布隆过滤器不支持分布式场景,因此本文采用Redisson 来构造布隆过滤器。
public void bloom(){
RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter("sample");
//初始化布隆过滤器,长度为55000000,误判率为0.03
bloomFilter.tryInit(55000000L, 0.03);
//将数据插入布隆过滤器
bloomFilter.add(1);
bloomFilter.add(2);
bloomFilter.add("Tony");
bloomFilter.add("Tom");
//判断元素是否存在
boolean contains = bloomFilter.contains("Tony");
//布隆过滤器中元素个数
long count = bloomFilter.count();
System.out.println(contains);
System.out.println(count);
}
控制台输出结果
4、总结
想要实现10亿的数据中快速判断某一个数据是否存在,使用sql查询会造成数据库性能、负载的问题,如若将数据存到缓存中,一个数据假设是20字节,10亿数据就是20G内存,这个方案内存占用过大。相比之下0.001容错率的布隆过滤器的内存占用为1.67G,既解决了内存问题,又解决了数据库资源使用问题。