布隆过滤器介绍
巴顿.布隆于一九七零年提出,布隆过滤器中有一个很长的二进制向量(位数组)一系列随机函数 (哈希) 空间效率和查询效率高存在一定的误判率
布隆过滤器原理
布隆过滤器(Bloom Filter)的核心实现是一个超大的位数组和几个哈希函 数。假设位数组的长度为m,哈希函数的个数为k
以上图为例,具体的操作流程:假设集合里面有3个元素{x, y, z},哈希函 数的个数为3。首先将位数组进行初始化,将里面每个位都设置位0。对于 集合里面的每一个元素,将元素依次通过3个哈希函数进行映射,每次映射 都会产生一个哈希值,这个值对应位数组上面的一个点,然后将位数组对 应的位置标记为1。查询W元素是否存在集合中的时候,同样的方法将W通 过哈希映射到位数组上的3个点。如果3个点的其中有一个点不为1,则可以 判断该元素一定不存在集合中。反之,如果3个点都为1,则该元素可能存 在集合中。注意:此处不能判断该元素是否一定存在集合中,可能存在一 定的误判率。可以从图中可以看到:假设某个元素通过映射对应下标为4, 5,6这3个点。虽然这3个点都为1,但是很明显这3个点是不同元素经过哈 希得到的位置,因此这种情况说明元素虽然不在集合中,也可能对应的都 是1,这是误判率存在的原因。
布隆过滤器添加元素原理
将要添加的元素给k个哈希函数,得到对应于位数组上的k个位置,将这k个位置设为1
布隆过滤器查询元素原理
将要查询的元素给k个哈希函,得到对应于位数组上的k个位置,如果k个位置有一个为0,则肯定不在集合中,如果k个位置全部为1,则可能在集合中
演示布隆过滤器误判问题
(1) 新建一个maven工程,引入guava包,可以自行选取版本
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>x.x</version>
</dependency>
</dependencies>
(2) 测试一个元素是否属于一个百万元素集合所需耗时
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import java.util.ArrayList;
/**
* @program: demo
* @description: 布隆过滤器误差演示
* @author: 老王
* @create: 2022-02-18 17:53
**/
public class Test {
private static int size = 1000000;
private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(),size);
public static void main(String[] args) {
//模拟从0~1000000以此存放在过滤器中
for(int i = 0;i < size;i++){
bloomFilter.put(i);
}
ArrayList<Integer> integers = new ArrayList<>();
//模拟新的数字在过滤器中进行比较判定
for (int i = size+10000; i < size+20000; i++) {
if(bloomFilter.mightContain(i)){
integers.add(i);
}
}
System.out.println("误判的数量:" +integers.size());
}
}
输出结果如下
误判对数量:330
如果上述代码所示,我们故意取10000个不在过滤器里的值,却还有330个 被认为在过滤器里,这说明了误判率为0.03.即,在不做任何设置的情况 下,默认的误判率为0.03。
下面上源码来证明:
接下来我们来看一下,误判率为0.03时,底层维护的bit数组的长度如下图所示
占用内存为:7298440
将bloomfilter的构造方法改为
private static BloomFilter bloomFilter=BloomFilter.create(Funnels.integerFunnel(), size,0.01);
即,此时误判率为0.01。在这种情况下,底层维护的bit数组的长度如下图所示
占用内存为:9585058
由此可见,误判率越低,则底层维护的数组越长,占用空间越大。因此,在使用布隆过滤器时,误判率实际取值,根据服务器所能够承受的负载来决定,而不是随意决定。
布隆过滤器用户解决缓存穿透
将所有可能存在的数据缓存放到布隆过滤器中,当黑客访问不存在的缓存时迅速返回避免缓存及DB挂掉。
redis伪代码如下:
String get(String key) {
String value = redis.get(key); //先从缓存获取。
if (value == null) { //缓存没有命中
if(!bloomfilter.mightContain(key)){//查看布隆过滤器钟是否存在
return null;
}else{
value = db.get(key); //查询数据库
redis.set(key, value);
}
}
return value;
}
可以参照此方案根据需求改写应用