布隆,牛逼!布谷鸟,牛逼! (2)

Hash3(Leslie)=5

通过上面的元素,可以知道此时 0,4,5 这三个位置上都是 1。

布隆过滤器就会觉得这个元素之前可能出现过。于是就会返回给调用者:[Leslie]曾经出现过。

但是实际情况呢?

其实我们心里门清,[Leslie] 不曾来过。

这就是误报的情况。

这就是前面说的:布隆过滤器说存在的元素,不一定存在。

而一个元素经过某个 hash 计算后,如果对应位置上的值是 0,那么说明该元素一定不存在。

但是它有一个致命的缺点,就是不支持删除。

为什么?

假设要删除 [why],那么就要把 1,4,8 这三个位置置为 0。

但是你想啊,[jay] 也指向了位置 8 呀。

如果删除 [why] ,位置 8 变成了 0,那么是不是相当于把 [jay] 也移除了?

布隆,牛逼!布谷鸟,牛逼!

为什么不支持删除就致命了呢?

你又想啊,本来布隆过滤器就是使用于大数据量的场景下,随着时间的流逝,这个过滤器的数组中为 1 的位置越来越多,带来的结果就是误判率的提升。从而必须得进行重建。

所以,文章开始举的腾讯的例子中有这样一句话:

布隆,牛逼!布谷鸟,牛逼!

除了删除这个问题之外,布隆过滤器还有一个问题:查询性能不高。

因为真实场景中过滤器中的数组长度是非常长的,经过多个不同 Hash 函数后,得到的数组下标在内存中的跨度可能会非常的大。跨度大,就是不连续。不连续,就会导致 CPU 缓存行命中率低。

这玩意,这么说呢。就当八股文背起来吧。

踏雪留痕,雁过留声,这就是布隆过滤器。

如果你想玩一下布隆过滤器,可以访问一下这个网站:

https://www.jasondavies.com/bloomfilter/

左边插入,右边查询:

布隆,牛逼!布谷鸟,牛逼!

如果要布隆过滤器支持删除,那么怎么办呢?

有一个叫做 Counting Bloom Filter。

它用一个 counter 数组,替换数组的比特位,这样一比特的空间就被扩大成了一个计数器。

用多占用几倍的存储空间的代价,给 Bloom Filter 增加了删除操作。

这也是一个解决方案。

但是还有更好的解决方案,那就是布谷鸟过滤器。

另外,关于布隆过滤器的误判率,有一个数学推理公式。很复杂,很枯燥,就不讲了,有兴趣的可以去了解一下。

~cao/papers/summary-cache/node8.html

布谷鸟 hash

布谷鸟过滤器,第一次出现是在 2014 年发布的一篇论文里面:《Cuckoo Filter: Practically Better Than Bloom》

https://www.cs.cmu.edu/~dga/papers/cuckoo-conext2014.pdf

布隆,牛逼!布谷鸟,牛逼!

但是在讲布谷鸟过滤器之前,得简单的铺垫一下 Cuckoo hashing,也就是布谷鸟 hash 的知识。

因为这个词是论文的关键词,在文中出现了 52 次之多。

布隆,牛逼!布谷鸟,牛逼!

Cuckoo hashing,最早出现在这篇 2001 年的论文之中:

https://www.cs.tau.ac.il/~shanir/advanced-seminar-data-structures-2009/bib/pagh01cuckoo.pdf

主要看论文的这个地方:

布隆,牛逼!布谷鸟,牛逼!

它的工作原理,总结起来是这样的:

它有两个 hash 表,记为 T1,T2。

两个 hash 函数,记为 h1,h2。

当一个不存在的元素插入的时候,会先根据 h1 计算出其在 T1 表的位置,如果该位置为空则可以放进去。

如果该位置不为空,则根据 h2 计算出其在 T2 表的位置,如果该位置为空则可以放进去。

如果该位置不为空,就把当前位置上的元素踢出去,然后把当前元素放进去就行了。

也可以随机踢出两个位置中的一个,总之会有一个元素被踢出去。

被踢出去的元素怎么办呢?

布隆,牛逼!布谷鸟,牛逼!

没事啊,它也有自己的另外一个位置。

论文中的伪代码是这样的:

布隆,牛逼!布谷鸟,牛逼!

看不懂没关系,我们画个示意图:

布隆,牛逼!布谷鸟,牛逼!

上面的图说的是这样的一个事儿:

我想要插入元素 x,经过两个 hash 函数计算后,它的两个位置分别为 T1 表的 2 号位置和 T2 表的 1 号位置。

两个位置都被占了,那就随机把 T1 表 2 号位置上的 y 踢出去吧。

而 y 的另一个位置被 z 元素占领了。

于是 y 毫不留情把 z 也踢了出去。

z 发现自己的备用位置还空着(虽然这个备用位置也是元素 v 的备用位置),赶紧就位。

所以,当 x 插入之后,图就变成了这样:

布隆,牛逼!布谷鸟,牛逼!

上面这个图其实来源就是论文里面:

布隆,牛逼!布谷鸟,牛逼!

这种类似于套娃的解决方式看是可行,但是总是有出现循环踢出导致放不进 x 的问题。

比如上图中的(b)。

当遇到这种情况时候,说明布谷鸟 hash 已经到了极限情况,应该进行扩容,或者 hash 函数的优化。

所以,你再次去看伪代码的时候,你会明白里面的 MaxLoop 的含义是什么了。

这个 MaxLoop 的含义就是为了避免相互踢出的这个过程执行次数太多,设置的一个阈值。

其实我理解,布谷鸟 hash 是一种解决 hash 冲突的骚操作。

如果你想上手玩一下,可以访问这个网站:

布隆,牛逼!布谷鸟,牛逼!

当踢来踢去了 16 (MaxLoop)次还没插入完成后,它会告诉你,需要 rehash 并对数组扩容了:

布隆,牛逼!布谷鸟,牛逼!

布谷鸟 hash 就是这么一回事。

接着,我们看布谷鸟过滤器。

布谷鸟过滤器

布谷鸟过滤器的论文《Cuckoo Filter: Practically Better Than Bloom》开篇第一页,里面有这样一段话。

直接和布隆过滤器正面刚:我布谷鸟过滤器,就是比你屌一点。

上来就指着别人的软肋怼:

布隆,牛逼!布谷鸟,牛逼!

标准的布隆过滤器的一大限制是不能删除已经存在的数据。如果使用它的变种,比如 Counting Bloom Filter,但是空间却被撑大了 3 到 4 倍,巴拉巴拉巴拉......

而我就不一样了:

布隆,牛逼!布谷鸟,牛逼!

这篇论文将要证明的是,与标准布隆过滤器相比,支持删除并不需要在空间或性能上提出更高的开销。

布谷鸟过滤器是一个实用的数据结构,提供了四大优势:

1.支持动态的新增和删除元素。

2.提供了比传统布隆过滤器更高的查找性能,即使在接近满的情况下(比如空间利用率达到 95% 的时候)。

3.比诸如商过滤器(quotient filter,另一种过滤器)之类的替代方案更容易实现。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpfzsx.html