BloomFilter简介
前言
在之前的Redis使用中缓存穿透、缓存雪崩等问题总结中提到过,缓存穿透的解决办法之一就是使用布隆过滤器,BloomFilter来过滤掉无效请求。今天我们来了解一下布隆过滤器。
BloomFilter原理
BloomFilter
是一种概率型数据结构,它由一个长度为m
的二进制向量(位数组)和k
个哈希函数组成,其特点是插入和查询的效率非常高,但缺点是存在一定的误判率。
位数组初始化时各位上都是0
,如下所示:
当向BloomFilter
中存入一个key
时,经过k
个哈希函数的计算之后得到k
个不同的哈希值,这些哈希值再模以位数组的长度m
,得到k
个数组中的位置,再将这些位置上的0
修改为1
,如下所示:
当想要查询这个key
是否存在时,也很简单,通过哈希函数和位数组的长度获得key
映射在位数组上的不同位置,若是有一个位置上仍是0
,那么这个key
就一定不存在于这个bloomFilter
上。若是不同位置上都是1,则这个key
可能存在于这个BloomFilter
中。为什么说是有可能呢?考虑一下下图这个场景。
此时有key1
、key2
两个key
在BloomFilter
上,导致位数组的2
、4
、6
、10
、12
和15
位置上都为1
。假设现在有一个key3
,经过计算之后,其在位数组上的位置分别是2
、6
和12
,这三个位置上都是1
,那么这个key3
到底在不在BloomFilter
里面呢?这个就不得而知了。这也是为什么BoolFilter
存在误判的原因。
因此,当我们使用BloomFilter
检查一个元素是否存在时,若该元素经过 k
个哈希函数运算后,位数组上任意一个索引位置上都是0
,那么该值肯定不在BloomFilter中。但如果所有索引位置处均为 1
,则只能说该元素可能存在于BloomFilter
中。简而言之,不存在就一定不存在,存在也可能是不存在的。
误判率
既然存在误判率,那么我们怎么控制呢?还是要从BloomFilter
的结构上分析。当位数组长度比较小,且哈希函数比较少时,经过n
个key
之后,可以预见位数组上大部分都已经是1
,这个时候误判率将会非常高,因为你没办法区分位置上的1
是由当前key
自身生成的,还是其他key
导致的。所以,误判率是由哈希函数的个数k
、位数组长度m
以及key
个数n
共同决定的,公式如下所示:
极端情况下,当BloomFilter
没有空闲空间的时候,每一次查询都会返回true
。这就意味着我们在初始化BloomFilter
时要预估好key
的个数和位数组长度m
,需要使得m
远远大于n
。
位数组长度m
可以根据预估误判率FFP
和预估key
的数量计算得到,如下所示:
有兴趣看具体的推导过程的,可以参考这篇文章,布隆过滤器 (Bloom Filter) 详解。
当位数组长度m
确定之后,哈希函数个数k
可以依靠下面公式大概估计出来:
k = 0.7 * ( m / n )
k,最佳哈希次数,即哈希函数的个数
m,位数组长度
n,期望添加的key数量
上面的公式计算起来可能比较麻烦,网上有人提供了一个网址,可以直接刷入相关参数来获得具体的值,有兴趣的话可以自己看一下,布隆计算器。
假如在使用BloomFilter
时,位数组长度设置有误,导致最后添加的key
数量n
大于位数组长度m
时,误判率会如何变化。这时候另一个公式派上用场:
f = ( 1-0.5^t )^k
t, 实际key数量与预估key数量之比
k, 哈希函数个数
上图是关于t
增大时,误判率的变化,出自《Redis深度历险:核心原理和应用实践》一书。可以发现,当t
增大时,误判率将会增大。
总结
BloomFilter
可以快速判断一个元素是否在BloomFilter
中,但存在一定的误判,所以在实际应用中要根据业务具体判断是否可以接受误判以及能接受多大的误判率,然后调整BloomFilter
的容量和hash
函数的个数。