前言
Redis 防止缓存穿透的一种方法,就是设置一个布隆过滤器。阅读了一些博客,这里简单给自己总结一下布隆过滤器的原理。
前提
在计算机编程中个,判断一个元素是否在一个集合中,是一个经常遇到的问题。最直接的办法如下:
- 遍历集合
这个办法会有一个时间过长,计算量过多的问题。于是,就有了Hash表,即利用空间换时间的概念。
- Hash 散列表
散列表是可行的,但在绝对庞大的数据下,散列表需要的空间会成为瓶颈。
转机
以上是基于计算机绝对正确的概念去思考解决问题的。但在实际工程中,假如可以收获的绝大的性能优势,一定程度是允许出现误判的。布隆过滤器是解决这类问题的一种有用的解法。
原理
布隆过滤器是利用一个很长的二进制向量和一系列的随机映射函数(F1,F2...F8)来定义的,很长的二进制向量,大概需要散列表的1/8或1/4的空间。假如要判断一个商品Id是否属于我们数据表的中的数据。假设传入的商品Id为:a,商品Id的集合为:ASet = {ai | ai 属于 ASet}
STEP 1 全量加载ASet的数据,初始化过滤器。
- 1、初始化
二进制向量,把所有的 bit 都设置为0; - 2、顺序取出
ASet的一个数据 ai,通过随机映射函数(F1,F2..F8),获得8个信息指纹(f1,f2,...f8),再使用一个随机数产生器G将8个指纹映射到二进制向量的8个bit中,假如发现bit上的数值为1,则不变,假如为0,则设置为1. - 3、重复2,直接集合遍历完成。
STEP 2 验证一个值a是否存在集合中
- 通过随机映射函数(F1,F2..F8),获得8个信息指纹(f1,f2,...f8),再使用一个随机数产生器G将8个指纹映射到
二进制向量的8个bit中。假如bit都为1,则认为a值在集合中。 - 误判:假如STEP 1 的集合数量足够多,而
二进制向量的长度比较短,导致二进制的bit位等于1的数量很多,则会一个元素通过映射后,都会落中bit=1的位,引起误判。这个需要在一些参数配置上重新设定一下。通过概率论也可以推导证明。
误判的处理方法
确认误判后,可以另外建一个集合去保留误判的数值,则:这些值不存在数据表中,但布隆过滤器会误判的值。
代码实现:Google Guava
int total = 100_0000;
BloomFilter<String> bf = BloomFilter.create(
Funnels.stringFunnel(Charsets.UTF_8), total, 0.03); # 定制误判率 fpp
// 初始化
for (int i = 0; i < total; i++) {
bf.put("" + i);
}
int count = 0;
for (int i = 0; i < total + 10000; i++) {
if (bf.mightContain("" + i)) {
count ++;
}
}
System.out.println("已匹配数量 " + count);
System.out.println("误判率:" + (count - total)/((total + 10000)*1.0F) * 100);
已匹配数量 1000309
误判率:0.03059406
小结
布隆过滤器是解决一个一个元素是否在集合的问题,其不会漏判,只会误判。精确值比Hash要差(基于概率推导是可以定制精确值的,Guava是有现成的工具),但利用的空间会比Hash方法会少。误判时,可以设置一个白名单,直接处理。布隆过滤器常用于一下场景:
- 1、垃圾邮件判断
- 2、爬虫网站去重处理
- 3、缓存防穿透
- 4、集合重复元素去重
参考
- 《数学之美》第二版 P204-208,作者:吴军
- 掘金博文:5 分钟搞懂布隆过滤器,亿级数据过滤算法你值得拥有!