1 为什么需要布隆过滤器
在Redis的学习中,我们知道存在两种常见问题是缓存穿透和缓存雪崩。其中缓存穿透是指大量请求的key不存在于缓存中,导致请求绕过或者“穿透”了缓存,直接到了数据库上,根本没有经过缓存这一层。可以通过缓存无效key的方法来应对缓存穿透,即如果缓存和数据库都查不到某个key的数据,那我们就再写一个到Redis并设置过期时间。但缓存无效key的方法有点治标不治本,如果请求的key变化频繁的话,那么在缓存中就存入了过多的无效key了。因此还有一种方法,就是使用布隆过滤器。 布隆过滤器(英语:Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
2 什么是布隆过滤器
如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间越来越大,同时检索速度也越来越慢。
而布隆过滤器可以看作为由二进制向量(或者说位数组)和一系列随机映射函数(哈希函数)两部分组成的数据结构。
布隆过滤器原理
当一个元素加入布隆过滤器中的时候,会进行如下操作:
- 使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)。
- 根据得到的哈希值,在位数组中把对应下标的值置为1。
当我们需要判断一个元素是否存在于布隆过滤器的时候,会进行如下操作:
- 对给定元素再次进行相同的哈希计算;
- 得到值之后判断位数组中的每个元素是否都为1,如果值都为1,那么说明这个值在布隆过滤器中,如果存在一个值不为1,说明该元素不在布隆过滤器中。
布隆过滤器应用场景
- 判断给定数据是否存在:比如判断一个数字是否存在于包含大量数字的数字集中(数字集很大,5亿以上!)、 防止缓存穿透(判断请求的数据是否有效避免直接绕过缓存请求数据库)等等、邮箱的垃圾邮件过滤、黑名单功能等等。
- 去重:比如爬给定网址的时候对已经爬取过的 URL 去重。
从中可以看出,不同的字符串可能存在哈希冲突现象,因此布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。为了尽量避免这种情况,我们可以适当增加位数组大小或者调整我们的哈希函数。那么,如何选择哈希函数个数和布隆过滤器长度呢?
3 如何选择哈希函数个数和位数组大小(布隆过滤器长度)
很显然,过小的布隆过滤器很快所有的 bit 位均为 1,那么查询任何值都会返回“可能存在”,起不到过滤的目的了。布隆过滤器的长度会直接影响误报率,布隆过滤器越长其误报率越小。
另外,哈希函数的个数也需要权衡,个数越多则布隆过滤器 bit 位置位 1 的速度越快,且布隆过滤器的效率越低;但是如果太少的话,那我们的误报率会变高。
如图所示,k为哈希函数个数,m为布隆过滤器长度,n为插入的元素个数,p为误报率。
选择适合的k和m计算公式见下图。
布隆过滤器支持删除吗?
传统的布隆过滤器并不支持删除操作。但是名为Counting Bloom filter的变种可以用来测试元素计数个数是否绝对小于某个阈值,它支持元素删除。可以参考文章Counting Bloom Filter的原理和实现。