层峦叠崎一一布隆过滤器
布隆过滤器( Bloom Filter )在起到去重作用的同时,在空间上还能节省 90% 以上,只是稍微有那么点不精确,也就是有一定的误判概率。
怎样理解布隆过滤器?
布隆过滤器理解为一个不怎么精确的 set 结构,当你使用它的 contains 法判断某个对象是否存在时,它可能会误判。当布隆过滤器说某个值存在时,这个值可能不存在;当它说某个值不存在时, 那就肯定不存在。
使用场景:布隆过滤器能准确过滤掉那些用户已经看过的内容, 那些用户没有看过的新内容,它也会过滤掉极小一部分(误判) ,但是绝大多数新内容它都能准确识别。这样就可以保证推荐给用户的内容都是无重复的。
Redis 中的布隆过滤器
Redis从4.0开始提供了插件功能,才有布隆过滤器
基本用法
bf.add
添加元素,bf.exists
查询元素是否存在
一次添加多个,就需要用到 bf.madd
指令。一次查询多个元素是否存在,就需要用到 bf.mexists
指令。
注意事项
用户在使用之前一定要尽可能地精确估计元素数量,还需要加上一定的冗余空间以避免实际元素可能会意外高出估计值很多。
布隆过滤器的 error_rate 越小,需要的存储空间就越大,对于不需要过于精确的场合, error_rate 设置稍大一点也无伤大雅。比如在新闻客户端的去重应用上,误判率高一点只会让小部分文章不能被合适的人看到。
布隆过滤器的原理
每个布隆过滤器对应到 Redis 的数据结构里面就是一个大型的位数组和几个不一 样的无偏 hash 函数,无偏就是能够把元素的 hash 值算得比较均匀,让元素被 hash 映射到位数组中的位置比较随机。
向布隆过滤器中添加 key 肘,会使用多个 hash 函数对 key 进行 hash ,算得一个 整数索引值,然后对位数组长度进行取模运算得到一个位置,每个 hash 函数都会算得一个不同的位置。再把位数组的这几个位置都置为 1,就完成了 add 操作。
向布隆过滤器询问 key 是否存在时,跟 add 一样,也会把 hash 的几个位置都算出来,看看位数组中这几个位置是否都为 1,只要有一个位为 0,那么说明布隆过滤器中这个 key 不存在。如果这几个位置都是 1,并不能说明这个 key 就一定存在,只是极有可能存在,因为这些位被置为 1可能是因为其他的 key 存在所致。如果这个位数组比较稀疏,判断正确的概率就会很大,如果这个位数组比较拥挤,判断正确的概率就会降低。
使用时不要让实际元素数量远大于初始化数量,当实际元素数量开始超出初始化数量时,应该对布隆过滤器进行重建,重新分配一个 size 更大的过滤器,再将所有的历史元素批量 add 进去(这就要求我们在其他的存储器中记录所有的历史元素)。
空间占用估计
布隆过滤器有两个参数,第一个是预计元素的数量 n,第二个是错误率 f。公式根据这两个输入得到两个输出,第一个输出是位数组的长度 l,也就是需要的存储空间大小( bit ),第二个输出是 hash 函数的最佳数量 k。hash 函数的数量也会直接影响到错误率,最佳的数量会有最低的错误率。
k=0 . 7* (l/n) #约等于
f=0 . 6185 <( l/n ) #^表示次方计算,也就是 math.pow
结论
- 位数组相对越长(l/n ),错误率 f 越低,这个和直观上理解是一致的。
- 位数组相对越长(l/n) , hash 函数需要的最佳数量也越多,影响计算效率
- 当一个元素平均需要 1 个字节( 8bit )的指纹空间时(l/n=8 ),错误率大约为 2%
- 错误率为 10% 时,一个元素需要的平均指纹空间为 4.792 个bit,大约为 5bit
- 错误率为 1% 时,一个元素需要的平均指纹空间 9.585 bit ,大约为 I0bit
- 错误率为 0.1% 时, 一个元素需要的平均指纹空间为 14.377 bit,大约为 15bit
实际元素超出时, 误判率会怎样变化
引入参数t 表示实际元素和预计元素的倍数
f=(1-0.5t)k #极限近似,k是 hash 函数的最佳数量
结论
- 错误率为 10% 时,倍数比为 2 时,错误率就会升至接近 40% ,这个值就比较危险了。
- 错误率为 1% 时,倍数比为 2 时,错误率会升至 15% ,也挺可怕的。
- 错误率为 0.1% 时,倍数比为 2 时,错误率会升至 5% ,也是比较高的。