1. 什么是布隆过滤
布隆过滤(Bloom Filter)是布隆在 1970 年提出的一种数据结构。
将元素(x、y、z)通过一系列函数,映射到一个二进制向量(0101 序列),用于快速判断元素 w 是否在一个集合当中。如下图(来自维基百科):
相较于使用单个映射函数,在相同的地址空间下,多个映射函数能降低冲突率。因此,在相同冲突率下,多个映射函数比单个映射函数需要的地址空间更少。
Bloom Filter 使用很短的二进制向量,通过牺牲准确率获得了极高的空间利用率。在大规模数据查询场景下,能有效避免磁盘 IO,具有很高地查询效率。
其实,用于判断一个元素是否在一个大集合中,还可以使用 Bitmap 。将元素转换成整数类型 x ,x 在 Bitmap 中的索引值为 0(代表不存在) 或者 1(代表存在)。
Bloom Filter 和 Bitmap 有所类似,都是利用二进制向量和映射函数判断元素是否存在。不同的是,Bitmap 只有一个映射函数,向量大小不能小于最大的整数;Bloom Filter 具有多个映射函数,根据不同要求的误判率场景,可以选择不同大小的二进制向量。
2. 常见的应用场景
Bloom Filter 具有一定的误判率,主要解决一定不存在和可能存在两类问题。
2.1 一定不存在问题 - False
- 单词拼写检查。拼写错误的单词一定不存在
- 防止数据库穿库。查询不存在的行或者列
- 缓存穿透。没查询到缓存时,穿透到数据库
2.2 可能存在 - True
- 爬虫对 URL 去重。跳过已经抓取的 URL
- 垃圾邮件过滤。黑名单中的地址将被屏蔽
- 避免重复推荐文章。跳过已经阅读的文章 URL/ID
- Web 拦截器。拦截在黑名单中的 URL 地址
3. 布隆过滤的参数选择
上面提到 ,根据不同要求的误判率场景,布隆过滤可以选择不同大小的二进制向量。在生产过程中,我们需要平衡误判率和效率。下面是维基百科中给出的公式:
k = (m/n) ln2
其中,
- m 是二进制向量的大小
- n 是元素的数量
- k 是映射函数的个数
- ln2 是常数,约等于 0.69
下表是不同 m/n 和 k 下的误判率。
m/n | k | k=1 | k=2 | k=3 | k=4 | k=5 | k=6 | k=7 | k=8 |
---|---|---|---|---|---|---|---|---|---|
2 | 1.39 | 0.393 | 0.400 | ||||||
3 | 2.08 | 0.283 | 0.237 | 0.253 | |||||
4 | 2.77 | 0.221 | 0.155 | 0.147 | 0.160 | ||||
5 | 3.46 | 0.181 | 0.109 | 0.092 | 0.092 | 0.101 | |||
6 | 4.16 | 0.154 | 0.0804 | 0.0609 | 0.0561 | 0.0578 | 0.0638 | ||
7 | 4.85 | 0.133 | 0.0618 | 0.0423 | 0.0359 | 0.0347 | 0.0364 | ||
8 | 5.55 | 0.118 | 0.0489 | 0.0306 | 0.024 | 0.0217 | 0.0216 | 0.0229 | |
9 | 6.24 | 0.105 | 0.0397 | 0.0228 | 0.0166 | 0.0141 | 0.0133 | 0.0135 | 0.0145 |
10 | 6.93 | 0.0952 | 0.0329 | 0.0174 | 0.0118 | 0.00943 | 0.00844 | 0.00819 | 0.00846 |
11 | 7.62 | 0.0869 | 0.0276 | 0.0136 | 0.00864 | 0.0065 | 0.00552 | 0.00513 | 0.00509 |
12 | 8.32 | 0.08 | 0.0236 | 0.0108 | 0.00646 | 0.00459 | 0.00371 | 0.00329 | 0.00314 |
13 | 9.01 | 0.074 | 0.0203 | 0.00875 | 0.00492 | 0.00332 | 0.00255 | 0.00217 | 0.00199 |
14 | 9.7 | 0.0689 | 0.0177 | 0.00718 | 0.00381 | 0.00244 | 0.00179 | 0.00146 | 0.00129 |
15 | 10.4 | 0.0645 | 0.0156 | 0.00596 | 0.003 | 0.00183 | 0.00128 | 0.001 | 0.000852 |
16 | 11.1 | 0.0606 | 0.0138 | 0.005 | 0.00239 | 0.00139 | 0.000935 | 0.000702 | 0.000574 |
17 | 11.8 | 0.0571 | 0.0123 | 0.00423 | 0.00193 | 0.00107 | 0.000692 | 0.000499 | 0.000394 |
18 | 12.5 | 0.054 | 0.0111 | 0.00362 | 0.00158 | 0.000839 | 0.000519 | 0.00036 | 0.000275 |
19 | 13.2 | 0.0513 | 0.00998 | 0.00312 | 0.0013 | 0.000663 | 0.000394 | 0.000264 | 0.000194 |
20 | 13.9 | 0.0488 | 0.00906 | 0.0027 | 0.00108 | 0.00053 | 0.000303 | 0.000196 | 0.00014 |
21 | 14.6 | 0.0465 | 0.00825 | 0.00236 | 0.000905 | 0.000427 | 0.000236 | 0.000147 | 0.000101 |
22 | 15.2 | 0.0444 | 0.00755 | 0.00207 | 0.000764 | 0.000347 | 0.000185 | 0.000112 | 7.46e-05 |
23 | 15.9 | 0.0425 | 0.00694 | 0.00183 | 0.000649 | 0.000285 | 0.000147 | 8.56e-05 | 5.55e-05 |
24 | 16.6 | 0.0408 | 0.00639 | 0.00162 | 0.000555 | 0.000235 | 0.000117 | 6.63e-05 | 4.17e-05 |
25 | 17.3 | 0.0392 | 0.00591 | 0.00145 | 0.000478 | 0.000196 | 9.44e-05 | 5.18e-05 | 3.16e-05 |
26 | 18 | 0.0377 | 0.00548 | 0.00129 | 0.000413 | 0.000164 | 7.66e-05 | 4.08e-05 | 2.42e-05 |
27 | 18.7 | 0.0364 | 0.0051 | 0.00116 | 0.000359 | 0.000138 | 6.26e-05 | 3.24e-05 | 1.87e-05 |
28 | 19.4 | 0.0351 | 0.00475 | 0.00105 | 0.000314 | 0.000117 | 5.15e-05 | 2.59e-05 | 1.46e-05 |
29 | 20.1 | 0.0339 | 0.00444 | 0.000949 | 0.000276 | 9.96e-05 | 4.26e-05 | 2.09e-05 | 1.14e-05 |
30 | 20.8 | 0.0328 | 0.00416 | 0.000862 | 0.000243 | 8.53e-05 | 3.55e-05 | 1.69e-05 | 9.01e-06 |
31 | 21.5 | 0.0317 | 0.0039 | 0.000785 | 0.000215 | 7.33e-05 | 2.97e-05 | 1.38e-05 | 7.16e-06 |
32 | 22.2 | 0.0308 | 0.00367 | 0.000717 | 0.000191 | 6.33e-05 | 2.5e-05 | 1.13e-05 | 5.73e-06 |
在使用时,首先确定一个可接受的误判率,然后根据公式计算出二进制向量的大小:
m = (k * n) / ln2
例如,选择 0.003 的误判率,k = 4,m/n = 15 。如果有 100 W 条记录,那么需要二进制向量大小为 ( 4 * 10e6 ) / 0.693 ≈ 5.77 * 10e6 bit = 704.6 KB 。
4. 布隆过滤的缺点
布隆过滤的主要缺点:
- 无法删除元素。因为一个二进制向量位,可能对应多个元素的映射,不能直接将其置 0 。
- 只适用于单机系统,内存开销随数据规模成线性关系。目前已经有一些中间件提供了布隆过滤,比如 Redis ,可以用于超大规模数据的场景。
对布隆过滤的优化算法很多,无非就是增加信息的冗余,但从效率上都比不上布隆过滤。下面是几种同类算法:
- 计数布隆过滤(Counting Bloom Filter)
在标准布隆过滤的基础上,将每一个 Bit 改为一个计数器,增加一个元素时,计数加一,删除一个元素时,计数减一。
- Spectral Bloom Filters
上面的计数布隆过滤,计数器是固定位数的。Spectral Bloom Filters 的计数位是动态变化的,更加灵活,避免计数溢出。
- 压缩布隆过滤(Compressed Bloom Filters)
通过减少映射函数的数量,减少网络传输的 Bit 位。为了换取相同的误判率,二进制向量将会变大。
- D-left 计算布隆过滤(D-left Counting Bloom Filters)
基于 D-left Hashing ,减少了存储空间,降低了误判率,可以删除元素。
- Dynamic Counting Filters
支持查询元素的存储频率
- 布谷鸟过滤
布谷鸟算法不同于布隆过滤,而是模仿布谷鸟解决映射冲突问题。当不同元素因素到同一个映射位时,最后映射的元素会踢走之前映射的元素。
布谷鸟算法支持删除操作,空间利用率只有 50 %,只存储元素的指纹信息,查询效率很高。
5. Go 语言实现
这里引用 github.com/willf/bloom
,对 Bloom Filter 进行简单测试。
|
|
执行结果:
|
|