布隆算法

587 阅读5分钟

布隆算法(Bloom Filter) 是一种利用Bitmap数据结构来进行海量数据查重的算法。具有运行快,占用内存小的特点,但是因为算法的特性问题它只能判定一个元素绝对不在集合内或可能在集合内.

场景

用Redis作为缓存中间件的一个问题就是缓存穿透问题,指的是缓存和数据库中都没有的数据,而用户不断发起请求,我们数据库的 id 都是1开始自增上去的,如发起为id值为 -1 的数据或 id 为特别大不存在的数据。这时的用户很可能是攻击者,攻击的数据因为Redis的缓存不存在,所以会穿过Redis直击(访问)数据库,严重时会击跨数据库。

Redis缓存穿透问题

如何解决呢?日常生活中,水龙头的自来水过滤器就很常见,如果能够像自来水过滤器一样过滤掉杂质(攻击请求)就好了,还真可以有,判断是否攻击的关键判断id是否在数据库中存在,可以等价转换为请求id是否与数据库中的id重复。布隆过滤器就是专门来应付这种海量数据查重的。我们可以把数据库的数据(id)都加载到我们的过滤器中, 然后每次请求进来时,进行过滤器查重筛选,查重通过了,进继续请求数据库。不通过说明是攻击。

过滤器

实现原理

先来了解一下什么是BitMap.

BitMap Bitmap巧妙的地方就在于它用下标代表id, bit信息表示 1表示是和 0表示 否.

大致原理

布隆过滤器的原理是,当一个元素被加入集合时,通过多种Hash函数,得出多个hash值,然后在BitMap中将对应的Hash值下标,置为1,检索时,我们只要看看这些点位是不是都是1就能(大约)知道集合中有没有它。相反地,映射出来的多个Hash下标中,只要有一个点的值0,那么被检测元素一定不存在。如果都是1,则可能存在(后面会解释)。

BloomFilter

如上图所示,www.111222.com.存在Hash函数得出的下标为0,所以这个数据为不存在的数据。

www.aaabbb.com为数据库中真实存在的数据。所以必然的,对应的三个Hash值下标都为bit值都为1.

www.cccddd.com数据库中并不存在,但是呢,就是那么刚好,他所对应的Hash函数下标,别的函数下标都帮他标记好了。这种数据就是漏网之鱼。


所以说,布隆算法就像一个滤水器一样。能帮你保留所有水(所有已存在的数据),能帮你过滤掉大部分杂质(不存在的数据),但是却会有漏网之鱼(误判数据).

减少冲突

上面提到的这种误判的数据,可以通过增大Hash空间,增加Hash次数来降低冲突,比方说一个Hash函数的误判率为0.01%,那么你用8个Hash函数的下标,相应的误判率就会降低很多。

可能又有人会问,为什么同一个Bitmap会出现误判,为什么不让一种Hash算法的结果对应一个独立的Bitmap呀,别忘了,用布隆算法的初心就是为了占用内存小,这么操作会让,占用的空间成倍地增加。

另外,BloomFilter还有一个缺点,就是删除困难,一个放入容器的元素映射到bit数组的k个位置上是1,删除的时候不能简单的直接置为0,可能会影响其他元素的判断。可以采用Counting Bloom Filter

代码实现

布隆过滤器有许多实现与优化,Guava中就提供了一种Bloom Filter的实现。

在使用bloom filter时,绕不过的两点是预估数据量n以及期望的误判率fpp,绕不过的两点就是hash函数的选取以及bit数组的大小。

要使用BloomFilter,需要引入guava包

 <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>23.0</version>
 </dependency>  

现在我们来测试一下:

1.正确的数据是否会被过滤

2.10000条错误的数据,最后会有多少个误判的数据

Guava BloomFilter的使用

运行结果:

误伤的数量:320

运行结果表示,遍历这一百万个在过滤器中的数时,都被识别出来了。一万个不在过滤器中的数,误伤了320个,错误率是0.03左右。

应用场景

常见的几个应用场景:

  • cerberus在收集监控数据的时候, 有的系统的监控项量会很大, 需要检查一个监控项的名字是否已经被记录到db过了, 如果没有的话就需要写入db.
  • 爬虫过滤已抓到的url就不再抓,可用bloom filter过滤
  • 垃圾邮件过滤。如果用哈希表,每存储一亿个 email地址,就需要 1.6GB的内存(用哈希表实现的具体办法是将每一个 email地址对应成一个八字节的信息指纹,然后将这些信息指纹存入哈希表,由于哈希表的存储效率一般只有 50%,因此一个 email地址需要占用十六个字节。一亿个地址大约要 1.6GB,即十六亿字节的内存)。因此存贮几十亿个邮件地址可能需要上百 GB的内存。而Bloom Filter只需要哈希表 1/8到 1/4 的大小就能解决同样的问题。

引用

本文参考了

掘金用户三太子敖丙的避免缓存穿透的利器之BloomFilter

掘金用户程序员小灰的什么是布隆算法