最近看流式系统的时候有提到Exactly-Once 策略 可以使用布隆过滤器(Bloom Filter) 优化, 所以今天来整理一下与其相关的知识 (非科班, 底子比较薄)。
应用原理:
- Bloom Filter can return false positives but never false negatives.
- 布隆过滤器能判断数据是否一定不存在, 而无法判断数据是否一定存在。
- 因此它可以用于对数据进行查重, 从而保证 Exactly-Once 的策略
什么是布隆过滤器?
- 他它本身是一个很长的二进制向量(0, 1), 假设以下为一个长度为16的布隆过滤器, 默认值为0
值 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
坐标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
- 假设添加了一条数据,其Hash散列得出的结果分别为 4,7,12, 如下所示:
值 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
坐标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
- 添加的数据占据了4, 7, 12 这三个格子, 但布隆过滤器本身没有存放完整的数据, 只是运用一系列随机随机映射函数算出位置, 然后填充二进制向量。
- 假设此时又来了一条数据, 要判断该数据是否重复, 只需要将该数据进行哈希散列, 查看该数据要占据哪些格子, 只要有一个格子的值不为1, 那么就代表这个数字不在其中。
- 但如果该条数据的哈希散列值对应的格子的值都为1, 不一定代表给定的数据一定重复, 可能其他数据经过该散列函数算出来的结果也是相同的。(不过概率非常小) [不同对象的哈希取模可能是相等的]
优缺点
- 优点: 由于存放的不是完整的数据, 所以占用的内存很少, 而且新增, 查询速度够快
- 缺点:
- 随着数据的增加, 误判率随之增加
- 无法做到删除数据(牵一发而动全身, 会影响其他存储于该格子的数据)
- 只能判断数据是否一定不存在, 而无法判断数据是否一定存在。
数据查重使用策略
-
只要布隆过滤器任务数据不存在, 就可以执行插入。
-
当布隆过滤器任务数据重复时, 统计可能重复的数据。
-
布隆过滤器的误判率(通过迭代 + 极限计算可得):
[f approx (1 - e^frac{-nk}{m})^k ]m: 二进制向量的大小 (bit)
k: 为hash运算的数量
n: 加入其中的key的数量
-
由于误判率可以计算且较小, 只要对可能重复的数据再重复进行过滤就可以将误判的数据逐渐剔除。
-
这些数据只要有一次被判定为非重复数据, 那么它们就不是重复数据。
通过Google开源的guava实现布隆过滤器
-
Guava 是Google 开源的 常用方法 的 java语言工具包
-
通过maven导入Guava坐标:
<!-- https://mvnrepository.com/artifact/com.google.guava/guava --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>28.0-jre</version> </dependency>
-
写代码执行测试
package com.ronnie.bloom; import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; public class BloomDemo { // 预计要插入的数据 private static int size = 1000000; // 预估的误判率 private static double falseProbability = 0.01; private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, falseProbability); public static void main(String[] args) { // 插入数据 for (int i = 0; i < size; i++){ bloomFilter.put(i); } int count = 0; for (int i = size; i < size * 2; i++){ if (bloomFilter.mightContain(i)){ count++; System.out.println(i + "被误判了"); } } System.out.println("总共误判了 " + count + " 个数"); } }
结果输出:
1999501被误判了 1999567被误判了 1999640被误判了 1999697被误判了 1999827被误判了 1999942被误判了 总共误判了 10314 个数
[frac{10314}{1000000}approx 0.01 ]