布隆过滤器

布隆过滤器_第1张图片

引言

我们知道检查一个元素是否在某一个集合中,使用HashSet是比较好的选择,因为在不发生Hash碰撞的情况下它的时间复杂度为常数级别,但是在数据量比较大的情况下,使用HashSet将会占用大量的内存空间。举个例子,长城防火墙有100亿个需要屏蔽的网址,来自计算机的每一次请求都要经过防火墙的过滤判断请求URL是否在黑名单中,如果我们使用HashSet来实现过滤的话,我们假设每个URL的大小为64B,那么100亿个就至少需要大约640GB的内存空间,这显然是不符合实际情况的。另一种解决方案是我们可以将URL存入关系型数据库,每次计算机发起请求我们对数据库进行exits查询,然而这种方案适用于并发量比较小的情况,若并发量较大,那么我们就需要对数据库进行集群。

以上俩种方案都有一定的局限性,为了解决大数据量检查问题,布隆过滤器就粉墨登场了。

原理

先来简单了解一下布隆过滤器,我们可以把它看做一个会产生误判并且占用空间极少的HashSet。它的结构是一个Bit数组(数组中每个位置只占用一个bit,每个bit位有0和1两种状态)和一系列Hash函数的集合,我们将输入域通过上述一系列Hash函数进行Hash运算得到n个key值,将这n个值对数组的长度进行取余,然后将bit数组中对应的位置bit位设为1。在数组足够大,hash碰撞足够小的情况下,每个输入域都会在数组中不同的位置将其bit位置为1,我们把集合中所有的元素都按照这个方式来一遍的话一个布隆过滤器就生成好了。

那么如何判断一个元素是否在布隆过滤器中呢,原理和生成布隆过滤器的过程差不多,我们将要判断的值通过布隆过滤器的n个Hash函数计算出n个值,对数组长度取余得到bit数组中n个位置,接下来判断这n个位置的bit位是否都为1,若都为1,则说明该元素在集合中,若有一个为0,则该元素肯定不在集合中。

误判

了解了布隆过滤器的生成过程,相信大家已经看出来了,这样会产生一定的误判,假如输入对象不再集合中,而由于元素过多并且bit数组过小导致数组中的大部分位置bit位都为1,那么有可能会误判该元素在集合中。但是如果输入对象本就在集合中,那么数组中的bit位肯定都为1,布隆过滤器是一定不会产生误判的,这就是所谓的“宁可错杀三千,绝不放过一个”。

对于已经发现的误判元素,我们可以把他放入HashSet中,在经过布隆过滤器之前先行判断一下,这部分元素较少不会占用多少内存,也在一定程度解决了布隆过滤器的误判问题。

空间占用估计

计算布隆过滤器的空间占用有一个现成的公式,,其中n为元素的个数,p为允许的误差率大小。布隆过滤器中Hash函数个数的公式为,m为个公式的结果,n为元素的个数。

如果觉得公式计算起来太麻烦,也没有关系,已经有先人帮我们做好工作了,我们只要把参数输进去,就可以直接看到结果,比如这个网站-- 计算。

上面的例子通过公式计算,假如误判率为0.01%,如果使用布隆过滤器的话我们的内存占用空间将会比使用HashSet少占用大约99.5%,但是得到的效果却基本相同。

在Redis中的应用

经过上面的介绍,已经对布隆过滤器有了大致的了解了,接下来就是他的使用了。Redis的布隆过滤器作为一个插件,在Redis在4.0中的提供了插件功能后才可以使用。使用前需要先安装插件。

这里主要介绍一下他的几个命令。

bf.add: 添加元素;

bf.exists: 查询元素是否存在

bf.madd: 添加多个

bf.mexists: 查询多个元素是否存在

开发中我们可以使用官方提供的Java客户端JReBloom,也可以使用Lettuce(前面的文章有介绍)。

其他应用(摘自Redis深度历险)

在爬虫系统中,我们需要对 URL 进行去重,已经爬过的网页就可以不用爬了。但是 URL 太多了,几千万几个亿,如果用一个集合装下这些 URL 地址那是非常浪费空间的。这时候就可以考虑使用布隆过滤器。它可以大幅降低去重存储消耗,只不过也会使得爬虫系统错过少量的页面。

布隆过滤器在 NoSQL 数据库领域使用非常广泛,我们平时用到的 HBase、Cassandra 还有 LevelDB、RocksDB 内部都有布隆过滤器结构,布隆过滤器可以显著降低数据库的 IO 请求数量。当用户来查询某个 row 时,可以先通过内存中的布隆过滤器过滤掉大量不存在的 row 请求,然后再去磁盘进行查询。

邮箱系统的垃圾邮件过滤功能也普遍用到了布隆过滤器,因为用了这个过滤器,所以平时也会遇到某些正常的邮件被放进了垃圾邮件目录中,这个就是误判所致,概率很低。

布隆过滤器_第2张图片

你可能感兴趣的:(布隆过滤器)