关注过 @Python大星 的小伙伴应该知道,2020 年 4 月 Python 小星最近裸面了阿里巴巴菜鸟网络科技有限公司。

一面中面试官非常重视解决 Redis 缓存穿透问题的利器--布隆过滤器,关于 Python 小星菜鸟二面的情况,后续整理出来。

今天我们来盘一盘“布隆过滤器” 到底是何方妖孽?

对不起,搞错啦,不是这个“布隆”。

但是,这个“布隆过滤器”中的“布隆” 确实是个人名,1970 年提出的,用来测试一个元素是否在一个集合里。有可能” 误报 “,但肯定不会” 错报 “:对布隆过滤器的一次查询要么返回 “可能在集合中 “,要么” 肯定不在集合里 “。

举个例子,古代进城搜捕凶手,人物画像的特征太多,可能会让凶手蒙混过关进城,但是有些人可以肯定不是凶手,比如高矮胖瘦都不符合画像的。

如果你前面理解了,我们再来说下专业术语:

布隆过滤器实际上是一个很长的二进制向量和一系列随机映射函数,可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。

如果不用会出现什么情况?

我们通常用 Redis 做缓存层,减少 Mysql 数据库的连接次数。

使用 Redis 避免不了一个“缓存穿透”的问题,

缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到 DB 中去查询,失去了缓存的意义。在流量大时,可能 DB 就挂掉了,要是有人利用不存在的 key 频繁攻击我们的应用,这就是漏洞。

如果我们这里加上“布隆过滤器”,就可以缓解“缓存穿透”对 Mysql 数据库的请求压力。

布隆过滤器会通过一定的错误率换取空间。

布隆过滤器通过 bit 数组来标识数据

假设 1 个元素只占一个坑,位数组中的每个元素都只占用 1 bit ,并且每个元素只能是 0 或者 1。

那么申请一个 100w 个元素的位数组只占用 1000000Bit / 8 = 125000 Byte = 125000/1024 kb ≈ 122kb 的空间。

如果你对 hashmap 原理熟悉的话,这个布隆过滤器的原理就很容易理解了。

缺点1:为什么说布隆过滤器有一定误识别率???

这是因为 hash算法计算出来的值会出现冲突,这就是我们常说的hashmap中的 hash 冲突一样,比如我们数据没有 id = 999 的,但是计算出来的值可能和已标识为 1 的值冲突,被“布隆过滤器” 误识别。

那有小伙伴会问,如何提高布隆过滤器的识别率呢?

从 3 个方面可以提高:

① bit 数组长度

② hash 函数的个数

这就和买彩票一样,数字越多,中奖概率越低。hash 函数的个数不是越多越好,需要参考 bit 数组的长度。

③ 如果布隆过滤器存储的是黑名单,那么可以建立小的白名单,存储那些被误判的信息。

布隆过滤器误判:

如果布隆过滤器说数据存在,那有可能不存在,输入误判;

如果布隆过滤说数据不存在,那一定不存在。

缺点2:为什么说布隆过滤器删除困难???

一个放入容器的元素映射到 bit 数组的 k 个位置上是 1,删除的时候不能简单的直接置为 0,可能会影响其他元素的判断。

如何解决布隆过滤器删除困难的问题???

Counting Bloom Filter,它将标准 Bloom Filter 位数组的每一位扩展为一个小的计数器 (Counter),在插入元素时给对应的 k(k 为哈希函数个数) 个 Counter 的值分别加 1,删除元素时给对应的 k 个 Counter 的值分别减 1,Counting Bloom Filter 通过多占用几倍的存储空间的代价,给 Bloom Filter 增加了删除操作。

1、如何确定 bit 数组的长度和 hash 函数的个数

已知条件

  • 存储数量为 n
  • 预期误判率 为 fpp(False positive probability)

计算

  • Bit 数组大小 m
  • 哈希函数个数 k

① Bit 数组选择

② hash 函数个数

2、布隆过滤器的实现

Guava 中就提供了一种 Bloom Filter 的实现。

① 在 springboot 项目中引入 maven 依赖

② 布隆过滤器测试

测试结果:

从上面的简单测试,我们可以看到误判率 320 / 10000 = 0.032.

从源码中我们也可以看出 fpp = 0.03

注意:

BloomFilter.create(Funnels.integerFunnel(), n, fpp);// 可以设置自定义设置误判率

@Python大星 | 文