精华内容
下载资源
问答
  • 布隆过滤器是有效,节省空间的数据结构,用于简洁地表示数据集并支持近似成员资格查询。 传统上,研究人员通常认为,Bloom过滤器有可能返回假阳性,但在行为良好的操作下绝不会返回假阴性。 但是,通过研究主流变体...
  • 使用扩展计数布隆过滤器的快速近似哈希表
  • 在介绍布隆过滤器之前我们首先引入几个场景。 场景一 在一个高并发的计数系统中,如果一个key没有计数,此时我们应该返回0,但是访问的key不存在,相当于每次访问缓存都不起作用了。那么如何避免频繁访问数量为0的...
  • 计数布隆过滤器(counting bloom filter)Redis实现分析

    Bloom filter简介

    关于布隆过滤器有众多文章做过介绍,这里不作详解,仅贴出简介:Bloom filter 是由 Howard Bloom 在 1970 年提出的二进制向量数据结构,它具有很好的空间和时间效率,被用来检测一个元素是不是集合中的一个成员。如果检测结果为是,该元素不一定在集合中;但如果检测结果为否,该元素一定不在集合中。因此Bloom filter具有100%的召回率。这样每个检测请求返回有“在集合内(可能错误)”和“不在集合内(绝对不在集合内)”两种情况,可见 Bloom filter 是牺牲了正确率和时间以节省空间。
    在这里插入图片描述
    Bloom filter 优点就是它的插入和查询时间都是常数,另外它查询元素却不保存元素本身,具有良好的安全性。它的缺点也是显而易见的,当插入的元素越多,错判“在集合内”的概率就越大了,另外 Bloom filter 也不能删除一个元素,因为多个元素哈希的结果可能在 Bloom filter 结构中占用的是同一个位,如果删除了一个比特位,可能会影响多个元素的检测。

    Counting Bloom filter

    标准Bloom filter对于需要精确检测结果的场景将不再适用,而带计数器的Bloom filter的出现解决了这个问题。Counting Bloom filter实际只是在标准Bloom filter的每一个位上都额外对应得增加了一个计数器,在插入元素时给对应的 k (k 为哈希函数个数)个 Counter 的值分别加 1,删除元素时给对应的 k 个 Counter 的值分别减 1。
    在这里插入图片描述
    Counting Bloom Filter通过多占用几倍的存储空间的代价,给Bloom Filter增加了删除操作。这其中最关键的问题是Counting Bloom filter需要增加多少存储量?在论文中给出了相关计算,假设counter数组的长度为m(对应bloom filter的位数组),Ci表示counter数组中第i个counter的大小,即哈希函数映射到第i位的次数,则每个counter最少位数N为:
    在这里插入图片描述
    SBF(Spectral Bloom Filter)作为Counting Bloom Filter的一种实现,将所有counter排成一个位串,counter之间完全不留空隙,然后通过建立索引结构来访问counter,并达到了只使用O(N) + O(m)位的存储目标,O(m)的构建时间。虽然SBF解决了动态counter的存储问题,但其引入了复杂的索引结构,这让每个counter的访问变得复杂而耗时。

    Dynamic Count Filter

    为改进SBF的缺点,人们又发明了DCF(Dynamic Count Filter),其使用两个数组来存储所有的counter,它们的长度都为m(即bloom filter的位数组长度)。第一个数组是一个基本的CBF(即下图中的CBFV,counting bloom filter vector),counter的长度固定,为x = log(M/n),其中M是集合中所有元素的个数,n为集合中不同元素的个数。第二个数组用来处理counter的溢出(即下图中的OFV,overflow vector),数组每一项的长度并不固定,根据counter的溢出情况动态调整。
    在这里插入图片描述
    在查询一个counter时,DCF要求两次内存访问。假设想查询位置为j的counter的值,我们先读出CBFV和OFV的值,分别为Cj和OFj,那么counter的值就可以表示为Vj = (2x×OFj + Cj)。

    在集合增加元素时,如果OFV的最大值从2x – 1增加到2x,OFV就需要给每一项增加1位,否则就会溢出。对应的,当OFV的最大值从2x减少到2x – 1时,OFV就需要减少1位。每次OFV大小改变的时候都需要重新创建一个OFV数组,然后把旧OFV数组的值拷贝到新建的OFV数组中,最后把旧OFV数组的空间释放掉。对于减少的情况,可以采用一些策略延迟OFV的重建,以避免一些临时性的减少导致OFV反复重建。

    基于Redis的实现

    鉴于单机有内存限制,我们不禁就会想到使用外部存储来实现,其中Redis是较好的选择。Redis有如下优点:1. 性能优异,2.接口功能丰富,3.可靠稳定。

    对于DCF的Redis实现,最简单的就是采用standalone模式,主要是因为其能方便支持Lua script并使用事务,另外还需要在客户端实现Sharding分片算法。但其有个缺点就是需要在开始时规定Redis实例数量,如要横向扩展Redis实例则需要将数据进行重分片,代价很大。
    客户端函数包含 query,add,delete 三种操作,且都先通过mod运算进行分片,对应到具体Redis实例后,执行对应Lua脚本。基本流程图如下:
    在这里插入图片描述

    参考资料

    1. bloom filter
    2. Summary Cache: A Scalable Wide-Area
      Web Cache Sharing Protocol
    3. Counting Bloom Filter
    4. Bloom Filter概念和原理
    5. Dynamic Count Filter
    6. Accurate Counting Bloom Filters for Large-Scale Data Processing
    展开全文
  • 布隆过滤器(bloom filter)介绍以及php和redis实现布隆过滤器实现方法 引言 在介绍布隆过滤器之前我们首先引入几个场景。 场景一 在一个高并发的计数系统中,如果一个key没有计数,此时我们应该返回0,但是访问的...

    布隆过滤器(bloom filter)介绍以及php和redis实现布隆过滤器实现方法

    引言

    在介绍布隆过滤器之前我们首先引入几个场景。

    场景一

    在一个高并发的计数系统中,如果一个key没有计数,此时我们应该返回0,但是访问的key不存在,相当于每次访问缓存都不起作用了。那么如何避免频繁访问数量为0的key而导致的缓存被击穿?

    有人说, 将这个key的值置为0存入缓存不就行了吗?确实,这是一个好的方案。大部分情况我们都是这样做的,当访问一个不存在的key的时候,设置一个带有过期时间的标志,然后放入缓存。不过这样做的缺点也很明显,浪费内存和无法抵御随机key攻击。

    场景二

    在一个黑名单系统中,我们需要设置很多黑名单内容。比如一个邮件系统,我们需要设置黑名单用户,当判断垃圾邮件的时候,要怎么去做。比如爬虫系统,我们要记录下来已经访问过的链接避免下次访问重复的链接。

    在邮件很少或者用户很少的情况下,我们用普通数据库自带的查询就能完成。在数据量太多的时候,为了保证速度,通常情况下我们会将结果缓存到内存中,数据结构用hash表。这种查找的速度是O(1),但是内存消耗也是惊人的。打个比方,假如我们要存10亿条数据,每条数据平均占据32个字节,那么需要的内存是64G,这已经是一个惊人的大小了。

    一种解决思路

    能不能有一种思路,查询的速度是O(1),消耗内存特别小呢?前辈门早就想出了一个很好的解决方案。由于上面说的场景判断的结果只有两种状态(是或者不是,存在或者不存在),那么对于所存的数据完全可以用位来表示!数据本身则可以通过一个hash函数计算出一个key,这个key是一个位置,而这个key所对的值就是0或者1(因为只有两种状态),如下图:

     

    布隆过滤器原理

    上面的思路其实就是布隆过滤器的思想,只不过因为hash函数的限制,多个字符串很可能会hash成一个值。为了解决这个问题,布隆过滤器引入多个hash函数来降低误判率。

    下图表示有三个hash函数,比如一个集合中有x,y,z三个元素,分别用三个hash函数映射到二进制序列的某些位上,假设我们判断w是否在集合中,同样用三个hash函数来映射,结果发现取得的结果不全为1,则表示w不在集合里面。

    布隆过滤器处理流程

    布隆过滤器应用很广泛,比如垃圾邮件过滤,爬虫的url过滤,防止缓存击穿等等。下面就来说说布隆过滤器的一个完整流程,相信读者看到这里应该能明白布隆过滤器是怎样工作的。

    第一步:开辟空间

    开辟一个长度为m的位数组(或者称二进制向量),这个不同的语言有不同的实现方式,甚至你可以用文件来实现。

    第二步:寻找hash函数

    获取几个hash函数,前辈们已经发明了很多运行良好的hash函数,比如BKDRHash,JSHash,RSHash等等。这些hash函数我们直接获取就可以了。

    第三步:写入数据

    将所需要判断的内容经过这些hash函数计算,得到几个值,比如用3个hash函数,得到值分别是1000,2000,3000。之后设置m位数组的第1000,2000,3000位的值位二进制1。

    第四步:判断

    接下来就可以判断一个新的内容是不是在我们的集合中。判断的流程和写入的流程是一致的。

    误判问题

    布隆过滤器虽然很高效(写入和判断都是O(1),所需要的存储空间极小),但是缺点也非常明显,那就是会误判。当集合中的元素越来越多,二进制序列中的1的个数越来越多的时候,判断一个字符串是否在集合中就很容易误判,原本不在集合里面的字符串会被判断在集合里面。

    数学推导

    布隆过滤器原理十分简单,但是hash函数个数怎么去判断,误判率有多少?

    假设二进制序列有m位,那么经过当一个字符串hash到某一位的概率为:

    1?

    也就是说当前位被反转为1的概率:

    ?(1)=1?

    那么这一位没有被反转的概率为:

    ?(0)=1−1?

    假设我们存入n各元素,使用k个hash函数,此时没有被翻转的概率为:

    ?(0)=(1−1?)??

    那什么情况下我们会误判呢,就是原本不应该被翻转的位,结果翻转了,也就是

    ?(误判)=1−(1−1?)??

    由于只有k个hash函数同时误判了,整体才会被误判,最后误判的概率为

    ?(误判)=(1−(1−1?)??)?

    要使得误判率最低,那么我们需要求误判与m、n、k之间的关系,现在假设m和n固定,我们计算一下k。可以首先看看这个式子:

    (1−1?)??

    由于我们的m很大,通常情况下我们会用2^32来作为m的值。上面的式子中含有一个重要极限

    lim?→∞(1+1?)?=?

    因此误判率的式子可以写成

     ?(误判)=(1−(?)−??/?)?

    接下来令?=−?/?,两边同时取对数,求导,得到:

    ?′1?=??(1−???)+?????(−???)1−???

    让?′=0,则等式后面的为0,最后整理出来的结果是

    (1−???)??(1−???)=????????

    计算出来的k为??2??,约等于0.693??,将k代入p(误判),我们可以得到概率和m、n之间的关系,最后的结果

    (1/2)??2??,约等于0.6185?/?

    以上我们就得出了最佳hash函数个数以及误判率与mn之前的关系了。

    下表是m与n比值在k个hash函数下面的误判率

    m/nkk=1k=2k=3k=4k=5k=6k=7k=8
    21.390.3930.400      
    32.080.2830.2370.253     
    42.770.2210.1550.1470.160    
    53.460.1810.1090.0920.0920.101   
    64.160.1540.08040.06090.05610.05780.0638  
    74.850.1330.06180.04230.03590.03470.0364  
    85.550.1180.04890.03060.0240.02170.02160.0229 
    96.240.1050.03970.02280.01660.01410.01330.01350.0145
    106.930.09520.03290.01740.01180.009430.008440.008190.00846
    117.620.08690.02760.01360.008640.00650.005520.005130.00509
    128.320.080.02360.01080.006460.004590.003710.003290.00314
    139.010.0740.02030.008750.004920.003320.002550.002170.00199
    149.70.06890.01770.007180.003810.002440.001790.001460.00129
    1510.40.06450.01560.005960.0030.001830.001280.0010.000852
    1611.10.06060.01380.0050.002390.001390.0009350.0007020.000574
    1711.80.05710.01230.004230.001930.001070.0006920.0004990.000394
    1812.50.0540.01110.003620.001580.0008390.0005190.000360.000275
    1913.20.05130.009980.003120.00130.0006630.0003940.0002640.000194
    2013.90.04880.009060.00270.001080.000530.0003030.0001960.00014
    2114.60.04650.008250.002360.0009050.0004270.0002360.0001470.000101
    2215.20.04440.007550.002070.0007640.0003470.0001850.0001127.46e-05
    2315.90.04250.006940.001830.0006490.0002850.0001478.56e-055.55e-05
    2416.60.04080.006390.001620.0005550.0002350.0001176.63e-054.17e-05
    2517.30.03920.005910.001450.0004780.0001969.44e-055.18e-053.16e-05
    26180.03770.005480.001290.0004130.0001647.66e-054.08e-052.42e-05
    2718.70.03640.00510.001160.0003590.0001386.26e-053.24e-051.87e-05
    2819.40.03510.004750.001050.0003140.0001175.15e-052.59e-051.46e-05
    2920.10.03390.004440.0009490.0002769.96e-054.26e-052.09e-051.14e-05
    3020.80.03280.004160.0008620.0002438.53e-053.55e-051.69e-059.01e-06
    3121.50.03170.00390.0007850.0002157.33e-052.97e-051.38e-057.16e-06
    3222.20.03080.003670.0007170.0001916.33e-052.5e-051.13e-055.73e-06

    php+Redis实现的布隆过滤器

    由于Redis实现了setbit和getbit操作,天然适合实现布隆过滤器,redis也有布隆过滤器插件。这里使用php+redis实现布隆过滤器。

    首先定义一个hash函数集合类,这些hash函数不一定都用到,实际上32位hash值的用3个就可以了,具体的数量可以根据你的位序列总量和你需要存入的量决定,上面已经给出最佳值。

    class BloomFilterHash
    {
    	/**
    	 * 由Justin Sobel编写的按位散列函数
    	 */
    	public function JSHash($string, $len = null)
    	{
        	$hash = 1315423911;
        	$len || $len = strlen($string);
        	for ($i=0; $i<$len; $i++) {
        		$hash ^= (($hash << 5) + ord($string[$i]) + ($hash >> 2));
        	}
    		return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
    	}
    
    	/**
    	 * 该哈希算法基于AT&T贝尔实验室的Peter J. Weinberger的工作。
    	 * Aho Sethi和Ulman编写的“编译器(原理,技术和工具)”一书建议使用采用此特定算法中的散列方法的散列函数。
    	 */
    	public function PJWHash($string, $len = null)
    	{
    		$bitsInUnsignedInt = 4 * 8; //(unsigned int)(sizeof(unsigned int)* 8);
        	$threeQuarters = ($bitsInUnsignedInt * 3) / 4;
        	$oneEighth = $bitsInUnsignedInt / 8;
        	$highBits = 0xFFFFFFFF << (int) ($bitsInUnsignedInt - $oneEighth);
        	$hash = 0;
        	$test = 0;
        	$len || $len = strlen($string);
        	for($i=0; $i<$len; $i++) {
    			$hash = ($hash << (int) ($oneEighth)) + ord($string[$i]); } $test = $hash & $highBits; if ($test != 0) { $hash = (($hash ^ ($test >> (int)($threeQuarters))) & (~$highBits));
        	}
    		return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
    	}
    
    	/**
    	 * 类似于PJW Hash功能,但针对32位处理器进行了调整。它是基于UNIX的系统上的widley使用哈希函数。
    	 */
    	public function ELFHash($string, $len = null)
    	{
    		$hash = 0;
    		$len || $len = strlen($string);
        	for ($i=0; $i<$len; $i++) {
            	$hash = ($hash << 4) + ord($string[$i]); $x = $hash & 0xF0000000; if ($x != 0) { $hash ^= ($x >> 24);
            	}
            	$hash &= ~$x;
        	}
    		return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
    	}
    
    	/**
    	 * 这个哈希函数来自Brian Kernighan和Dennis Ritchie的书“The C Programming Language”。
    	 * 它是一个简单的哈希函数,使用一组奇怪的可能种子,它们都构成了31 .... 31 ... 31等模式,它似乎与DJB哈希函数非常相似。
    	 */
    	public function BKDRHash($string, $len = null)
    	{
        	$seed = 131;  # 31 131 1313 13131 131313 etc..
        	$hash = 0;
        	$len || $len = strlen($string);
        	for ($i=0; $i<$len; $i++) {
            	$hash = (int) (($hash * $seed) + ord($string[$i]));
        	}
    		return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
    	}
    
    	/**
    	 * 这是在开源SDBM项目中使用的首选算法。
    	 * 哈希函数似乎对许多不同的数据集具有良好的总体分布。它似乎适用于数据集中元素的MSB存在高差异的情况。
    	 */
    	public function SDBMHash($string, $len = null)
    	{
    		$hash = 0;
    		$len || $len = strlen($string);
    		for ($i=0; $i<$len; $i++) {
    			$hash = (int) (ord($string[$i]) + ($hash << 6) + ($hash << 16) - $hash);
    		}
    		return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
    	}
    
    	/**
    	 * 由Daniel J. Bernstein教授制作的算法,首先在usenet新闻组comp.lang.c上向世界展示。
    	 * 它是有史以来发布的最有效的哈希函数之一。
    	 */
    	public function DJBHash($string, $len = null)
    	{
    		$hash = 5381;
    		$len || $len = strlen($string);
    		for ($i=0; $i<$len; $i++) {
    			$hash = (int) (($hash << 5) + $hash) + ord($string[$i]);
    		}
    		return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
    	}
    
    	/**
    	 * Donald E. Knuth在“计算机编程艺术第3卷”中提出的算法,主题是排序和搜索第6.4章。
    	 */
    	public function DEKHash($string, $len = null)
    	{
    		$len || $len = strlen($string);
    		$hash = $len;
    		for ($i=0; $i<$len; $i++) {
    			$hash = (($hash << 5) ^ ($hash >> 27)) ^ ord($string[$i]);
    		}
    		return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
    	}
    
    	/**
    	 * 参考 http://www.isthe.com/chongo/tech/comp/fnv/
    	 */
    	public function FNVHash($string, $len = null)
    	{
    		$prime = 16777619; //32位的prime 2^24 + 2^8 + 0x93 = 16777619
    		$hash = 2166136261; //32位的offset
    		$len || $len = strlen($string);
    		for ($i=0; $i<$len; $i++) {
    			$hash = (int) ($hash * $prime) % 0xFFFFFFFF;
    			$hash ^= ord($string[$i]);
    		}
    		return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
    	}
    }

    接着就是连接redis来进行操作

    /**
     * 使用redis实现的布隆过滤器
     */
    abstract class BloomFilterRedis
    {
    	/**
    	 * 需要使用一个方法来定义bucket的名字
    	 */
    	protected $bucket;
    
    	protected $hashFunction;
    
    	public function __construct($config, $id)
    	{
    		if (!$this->bucket || !$this->hashFunction) {
    			throw new Exception("需要定义bucket和hashFunction", 1);
    		}
    		$this->Hash = new BloomFilterHash;
    		$this->Redis = new YourRedis; //假设这里你已经连接好了
    	}
    
    	/**
    	 * 添加到集合中
    	 */
    	public function add($string)
    	{
    		$pipe = $this->Redis->multi();
    		foreach ($this->hashFunction as $function) {
    			$hash = $this->Hash->$function($string);
    			$pipe->setBit($this->bucket, $hash, 1);
    		}
    		return $pipe->exec();
    	}
    
    	/**
    	 * 查询是否存在, 存在的一定会存在, 不存在有一定几率会误判
    	 */
    	public function exists($string)
    	{
    		$pipe = $this->Redis->multi();
    		$len = strlen($string);
    		foreach ($this->hashFunction as $function) {
    			$hash = $this->Hash->$function($string, $len);
    			$pipe = $pipe->getBit($this->bucket, $hash);
    		}
    		$res = $pipe->exec();
    		foreach ($res as $bit) {
    			if ($bit == 0) {
    				return false;
    			}
    		}
    		return true;
    	}
    
    }

    上面定义的是一个抽象类,如果要使用,可以根据具体的业务来使用。比如下面是一个过滤重复内容的过滤器。

    /**
     * 重复内容过滤器
     * 该布隆过滤器总位数为2^32位, 判断条数为2^30条. hash函数最优为3个.(能够容忍最多的hash函数个数)
     * 使用的三个hash函数为
     * BKDR, SDBM, JSHash
     *
     * 注意, 在存储的数据量到2^30条时候, 误判率会急剧增加, 因此需要定时判断过滤器中的位为1的的数量是否超过50%, 超过则需要清空.
     */
    class FilteRepeatedComments extends BloomFilterRedis
    {
    	/**
    	 * 表示判断重复内容的过滤器
    	 * @var string
    	 */
    	protected $bucket = 'rptc';
    
    	protected $hashFunction = array('BKDRHash', 'SDBMHash', 'JSHash');
    }
    展开全文
  • 布隆过滤器 为什么需要布隆过滤器 如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。 但是...

    布隆过滤器

    基本概念

    布隆过滤器(Bloom Filter,下文简称BF)由Burton Howard Bloom在1970年提出,是一种空间效率高的概率型数据结构。它专门用来检测集合中是否存在特定的元素。
    它实际上是一个很长的二进制向量和一系列随机映射函数。

    为什么需要布隆过滤器

    如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。
    但是随着集合中元素的增加,我们需要的存储空间越来越大。同时检索速度也越来越慢,上述三种结构的检索时间复杂度分别为O(n) O(log n) O(1)。
    布隆过滤器即可以解决存储空间的问题, 又可以解决时间复杂度的问题.
    布隆过滤器的原理是,当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。

    实现原理

    布隆过滤器的原理是,当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。
    BF是由一个长度为m比特的位数组(bit array)与k个哈希函数(hash function)组成的数据结构。位数组均初始化为0,所有哈希函数都可以分别把输入数据尽量均匀地散列。
    当要插入一个元素时,将其数据分别输入k个哈希函数,产生k个哈希值。以哈希值作为位数组中的下标,将所有k个对应的比特置为1。
    当要查询(即判断是否存在)一个元素时,同样将其数据输入哈希函数,然后检查对应的k个比特。如果有任意一个比特为0,表明该元素一定不在集合中。如果所有比特均为1,表明该集合有(较大的)可能性在集合中。为什么不是一定在集合中呢?因为一个比特被置为1有可能会受到其他元素的影响(hash碰撞),这就是所谓“假阳性”(false positive)。相对地,“假阴性”(false negative)在BF中是绝不会出现的。
    下图示出一个m=18, k=3的BF示例。集合中的x、y、z三个元素通过3个不同的哈希函数散列到位数组中。当查询元素w时,因为有一个比特为0,因此w不在该集合中。

    在这里插入图片描述

    优点

    1.不需要存储数据本身,只用比特表示,因此空间占用相对于传统方式有巨大的优势,并且能够保密数据;
    2.时间效率也较高,插入和查询的时间复杂度均为, 所以他的时间复杂度实际是
    3.哈希函数之间相互独立,可以在硬件指令层面并行计算。

    缺点

    1.存在假阳性的概率,不适用于任何要求100%准确率的情境;
    2.只能插入和查询元素,不能删除元素,这与产生假阳性的原因是相同的。我们可以简单地想到通过计数(即将一个比特扩展为计数值)来记录元素数,但仍然无法保证删除的元素一定在集合中。

    使用场景

    BF在对查准度要求没有那么苛刻,而对时间、空间效率要求较高的场合非常合适.
    另外,由于它不存在假阴性问题,所以用作“不存在”逻辑的处理时有奇效,比如可以用来作为缓存系统(如Redis)的缓冲,防止缓存穿透

    展开全文
  • 布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,...

    简介

    布隆过滤器(Bloom Filter)是1970年由一个叫Bloom的老哥提出的。本质上属于一种数据结构,实际组成是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。

    思想

    布隆过滤器的原理:当一个元素被加入集合时,通过 K 个散列(hash)函数将这个元素映射成一个位(bit)数组中的 K 个点,把它们置为 1。检索时,我们只要看看这些点是不是都是 1 就(大约)知道集合中有没有它了:如果这些点有任何一个 0,则被检元素一定不在;如果都是 1,则被检元素很可能存在。

    特点

    某条数据一定不存在或可能存在。

    优点

    空间:存储数据使用的位数组,极大节省了空间。若在内存中存储海量数据,提升是很可观的;

    时间:插入、查询的时间复杂度都是O(k),k为hash函数数量;

    效率:哈希函数相互之间没有关系,可以在硬件指令层面并行计算;

    数据加密:布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势;

    缺点

    准确率:“某样东西一定不存在或者可能存在”,不能保证100%的准确匹配;

    无法删除数据:只能插入和查询元素,一般情况下不能从布隆过滤器中删除元素。我们很容易想到把位列阵变成整数数组,每插入一个元素相应的计数器加1, 这样删除元素时将计数器减掉就可以了。然而要保证安全的删除元素并非如此简单。首先我们必须保证删除的元素的确在布隆过滤器里面. 这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题。

    在降低误算率方面,有不少工作,使得出现了很多布隆过滤器的变种。

    图解

    网上找的图:

     图三:假设集合里面有3个元素{x, y, z},哈希函数的个数为3。首先将位数组进行初始化,将里面每个位都设置位0。对于集合里面的每一个元素,将元素依次通过3个哈希函数进行映射,每次映射都会产生一个哈希值,这个值对应位数组上面的一个点,然后将位数组对应的位置标记为1。查询W元素是否存在集合中的时候,同样的方法将W通过哈希映射到位数组上的3个点。如果3个点的其中有一个点不为1,则可以判断该元素一定不存在集合中。反之,如果3个点都为1,则该元素可能存在集合中。

    java代码

    布隆过滤器添加元素

    • 将要添加的元素给k个哈希函数
    • 得到对应于位数组上的k个位置
    • 将这k个位置设为1

    布隆过滤器查询元素

    • 将要查询的元素给k个哈希函数
    • 得到对应于位数组上的k个位置
    • 如果k个位置有一个为0,则肯定不在集合中
    • 如果k个位置全部为1,则可能在集合中

    使用google的Guava布隆过滤器

    1.引入:

            <dependency>
                <groupId>com.google.guava</groupId>
                <artifactId>guava</artifactId>
                <version>30.1.1-jre</version>
            </dependency>

    2.使用:

    import com.google.common.base.Charsets;
    import com.google.common.hash.BloomFilter;
    import com.google.common.hash.Funnels;
    
    /**
     * 谷歌开源的Guava布隆过滤器
     * @author yangzihe
     * @date 2021/6/3
     */
    public class GuavaBloomFilter {
        public static void main(String[] args) {
            // 总数量
            int total = 1000000;
            // 默认误判率fpp0.03
            BloomFilter<CharSequence> bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), total);
            // 初始化 total 条数据到过滤器中
            for (int i = 0; i < total; i++) {
                bf.put("" + i);
            }
            // 判断值是否存在过滤器中
            int count = 0;
            for (int i = 0; i < total + 10000; i++) {
                if (bf.mightContain("" + i)) {
                    count++;
                }
            }
            System.out.println("已匹配数量 " + count);// (1000309 - 1000000)/(1000000 + 10000) * 100 ≈ 0.030594059405940593
    
            //指定误判率:万分之一,提高匹配精度
            BloomFilter<CharSequence> bfWithFpp = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), total, 0.0001);
            for (int i = 0; i < total; i++) {
                bfWithFpp.put("" + i);
            }
            int countFpp = 0;
            for (int i = 0; i < total + 10000; i++) {
                if (bfWithFpp.mightContain("" + i)) {
                    countFpp++;
                }
            }
            //误判率 fpp 的值越小,匹配的精度越高。当减少误判率 fpp 的值,需要的存储空间也越大,所以在实际使用过程中需要在误判率和存储空间之间做个权衡。
            System.out.println("指定误判率已匹配数量 " + countFpp);// (1000001 - 1000000)/(1000000 + 10000) * 100 ≈ 0.0001
        }
    }

    布隆过滤器的误判率(FPP):

    • n:要添加的元素数量;
    • k:哈希的次数;
    • m:布隆过滤器的长度(如比特数组的大小);

    布隆过滤器的长度 m 可以根据给定的误判率(FFP)的和期望添加的元素个数 n 的通过如下公式计算:

    最优哈希函数数量k,如果m是数组长度,n是插入的元素个数,k是hash函数的个数,k计算公式如下:

    Guava布隆过滤器源码记录

    BloomFilter的4个成员变量:

        /** bit数组:Guava实现的以CAS方式设置每个bit位 */
        private final LockFreeBitArray bits;
        /** hash函数的个数 */
        private final int numHashFunctions;
        /** guava中将对象转换为byte的通道 */
        private final Funnel<? super T> funnel;
        /** 将byte转换为n个bit的策略,也是bloomfilter hash映射的具体实现 */
        private final Strategy strategy;

    LockFreeBitArray:封装了布隆过滤器底层bit数组的操作。
    numHashFunctions:哈希函数的个数。
    Funnel:它和PrimitiveSink配套使用,能将任意类型的对象转化成Java基本数据类型,默认用java.nio.ByteBuffer实现,最终均转化为byte数组。
    Strategy:strategy是布隆过滤器的哈希策略,即数据如何映射到位数组,其具体方法在BloomFilterStrategies枚举中。代码如下,主要有2个方法,put和mightContain。

        interface Strategy extends java.io.Serializable {
            /** 设置元素 */
            <T> boolean put(T object, Funnel<? super T> funnel, int numHashFunctions, BloomFilterStrategies.LockFreeBitArray bits);
            /** 判断元素是否存在 */
            <T> boolean mightContain(T object, Funnel<? super T> funnel, int numHashFunctions, BloomFilterStrategies.LockFreeBitArray bits);
            ...
        }

    构造函数:

    BloomFilter只有一个私有构造函数,对外提供了5个public重载的create方法,在缺省情况下误判率设定为3%,采用BloomFilterStrategies.MURMUR128_MITZ_64的实现。5个重载的create方法最终实现逻辑:

      @VisibleForTesting
      static <T> BloomFilter<T> create(
          Funnel<? super T> funnel, long expectedInsertions, double fpp, Strategy strategy) {
        checkNotNull(funnel);
        checkArgument(
            expectedInsertions >= 0, "Expected insertions (%s) must be >= 0", expectedInsertions);
        checkArgument(fpp > 0.0, "False positive probability (%s) must be > 0.0", fpp);
        checkArgument(fpp < 1.0, "False positive probability (%s) must be < 1.0", fpp);
        checkNotNull(strategy);
    
        if (expectedInsertions == 0) {
          expectedInsertions = 1;
        }
        /*
         * TODO(user): Put a warning in the javadoc about tiny fpp values, since the resulting size
         * is proportional to -log(p), but there is not much of a point after all, e.g.
         * optimalM(1000, 0.0000000000000001) = 76680 which is less than 10kb. Who cares!
         */
        long numBits = optimalNumOfBits(expectedInsertions, fpp);
        int numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits);
        try {
          return new BloomFilter<T>(new LockFreeBitArray(numBits), numHashFunctions, funnel, strategy);
        } catch (IllegalArgumentException e) {
          throw new IllegalArgumentException("Could not create BloomFilter of " + numBits + " bits", e);
        }
      }

    该方法接受4个参数:funnel是插入数据的Funnel,expectedInsertions是期望插入的元素总个数n,fpp即期望误判率p,strategy即哈希策略。

    m:位数组的长度;

    k:哈希函数的个数;

    由上可知,m和k分别通过optimalNumOfBits()方法和optimalNumOfHashFunctions()方法来估计。

      @VisibleForTesting
      static long optimalNumOfBits(long n, double p) {
        if (p == 0) {
          p = Double.MIN_VALUE;
        }
        return (long) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
      }
    
      @VisibleForTesting
      static int optimalNumOfHashFunctions(long n, long m) {
        // (m / n) * log(2), but avoid truncation due to division!
        return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
      }

                                                          

    optimalNumOfHashFunctions()方法的逻辑                         optimalNumOfBits()方法的逻辑

    如果指定期望误判率p,那么最优的m值与期望元素数n呈线性关系。
    最优的k值实际上只与p有关,与m和n都无关,即:

    所以,在创建BloomFilter时,确定合适的p和n值很重要。

    在BloomFilterStrategies枚举中定义了两种哈希策略,都基于著名的MurmurHash算法,分别是MURMUR128_MITZ_32和MURMUR128_MITZ_64。

    参考:https://www.jianshu.com/p/bef2ec1c361f

    Guava 提供的布隆过滤器的实现还是很不错的,但是它有一个重大的缺陷就是只能单机使用(另外,容量扩展也不容易),而现在互联网一般都是分布式的场景。为了解决这个问题,我们就需要用到 Redis 中的布隆过滤器了。然后,为了解决布隆过滤器无法删除数据的问题,引入了counting bloom filter计数式布隆过滤器。redis布隆、counting布隆有缘再续吧。。。

     

    展开全文
  • 布隆过滤器是空间高效的概率 数据结构,通过设想伯顿霍华德布卢姆于1970年,是用于测试一个是否元件是一个的成员组。可能会出现假阳性匹配,但否定否定匹配-换句话说,查询返回“可能在集合中”或“绝对不在集合中”...
  • 详解布隆过滤器

    千次阅读 2019-07-01 00:45:40
    假设我们想要开发一个邮件系统,那么如何实现垃圾邮件的过滤呢。 最简单的办法就是把确定为是垃圾邮件的地址都保存起来,存入黑名单中。当用户接收到黑名单地址的邮件时,直接将邮件归类到垃圾箱中。 垃圾邮件的...
  • 使用布隆过滤器解决缓存穿透问题 什么是缓存穿透? 缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,但是出于容错的考虑,如果从存储层查不到数据则不写入缓存层。 注:一般的逻辑 查询缓存 ->...
  • 什么是布隆过滤器? 本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希...
  • 1、什么是布隆过滤器
  • Java redis 模拟布隆过滤器

    千次阅读 2018-12-13 16:21:40
    基本概念 如果想判断一个元素是不是在一个集合里,...首先我们必须保证删除的元素的确在布隆过滤器里面. 这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题。   redis对bitmaps的解释: ...
  • 布隆过滤器(Bloom Filter)的核心实现是一个超大的位数组和几个哈希函数。假设位数组的长度为m,哈希函数的个数为k 以上图为例,具体的操作流程:假设集合里面有3个元素{x, y, z},哈希函数的个数为3。首先将...
  • 如果已经统计,则不做处理,如果没有则进行计数累加,并更新布隆过滤器 if (!flag) { //先取出旧值 val uvCountMap = "uvCount" val currentKey = w.getEnd.toString var count = 0L if (jedis.hget(uvCountMap, ...
  • 2 计数布隆过滤器 2.1 优点 支持添加和删除 支持计数 2.2 缺点 内存消耗是标准布隆过滤器的3-4倍 3 布谷鸟过滤器 3.1 优点 支持动态新增和删除元素 布谷鸟哈希表更加紧凑,可以更加节省空间,散列确保了高...
  • 布隆过滤器原理解析

    2019-11-07 17:34:32
    在撸码的时候,经常要判断一个元素是否已经存在。常用的做法是,把已经存在的元素全部存储到一个集合里,然后新的元素查一下看它是否在集合里来确定是否...如果你刚好遇到这方面的问题,那么可以考虑一下布隆过滤器...
  • 什么是布隆过滤器 介绍+优点 应用场景 设计原理 布隆实战 总结 存在的问题 + 总结
  • 布隆过滤器

    千次阅读 2019-07-22 09:46:17
    1.布隆过滤器 1.1 什么是布隆过滤器 本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。 ...
  • 布隆过滤器(Bloom Filter)原理分析及代码实现 直观的说,bloom算法类似一个hash set,用来判断某个元素(key)是否在某个集合中。 和一般的hash set不同的是,这个算法无需存储key的值,对于每个key,只需要k个比特...
  • 布隆过滤器 1.为什么诞生 为了高效的插入数据和查询数据 现实场景:从上亿数据中判断一条数据是否存在,使用map,set等常规数据结构,导致内存急速膨胀,需要大量空间,需要一种更为巧妙的方式,判断数据是否存在,...
  • 与普通的布隆过滤器一样,Chazelle实现布隆过滤器也可以达到较低的空间消耗,但同时也会产生false positive,不过,在Bloomier filter中,某 key 如果不在 map 中,false positive在会返回时会被定义出的。...
  • 过滤器使用场景: 比如有如下几个需求: 1.原本有10亿个号码,现在又来了10万个号码,要快速准确判断这10万个号码是否在10亿个号码库中?  解决办法一:将10亿个号码存入数据库中,进行数据库查询,准确性有了,...
  • 1.布隆过滤器使用场景 比如有如下几个需求: ①、原本有10亿个号码,现在又来了10万个号码,要快速准确判断这10万个号码是否在10亿个号码库中? 解决办法一:将10亿个号码存入数据库中,进行数据库查询,准确性有了...
  • 布隆过滤器 ---- 实现以及性能分析

    千次阅读 2019-10-05 16:49:35
    问题来了,新闻客户端推荐系统如何实现推送去重的? 用服务器记录了用户看过的所有历史记 录,当推荐系统推荐新闻时会从每个用户的历史记录里进行筛选,过滤掉那些已经存在的记录。 如何快速查 找呢? 1. 用哈希表...
  • 本文是《玩转Redis》系列第【11】篇,本文关键字:玩转Redis、Bloom filter、布隆过滤器、无偏hash函数; - 布隆过滤器的底层原理 - 布隆过滤器的底层结构 - 最佳hash函数数量与错误率的关系 - 所需存储空间与...
  • 1.【基础题】–实现一个简单的布隆过滤器。 如果想要判断一个元素是不是在一个集合里,一般想到的是将所有元素保存起来,然后通过比较确定。链表,树等等数据结构都是这种思路. 但是随着集合中元素的增加,我们...
  • 布隆过滤器详解

    2019-10-23 14:55:46
    什么是布隆过滤器 本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。 相比于传统的 List...
  • 谈谈布隆过滤器

    2019-12-17 17:16:23
    布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,...
  • 布隆过滤器学习

    2019-07-04 11:04:24
    布隆过滤器 学习起因,因上课听老师讲有一个数据结构很好用,比学习其他数据结构更有价值,这个数据结构就是布隆过滤器. 具体我并有在项目中使用它,现在是当做一种了解,学习它的原理和应用场景. 什么是布隆过滤器 本质...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,651
精华内容 1,460
关键字:

计数布隆过滤器实现