布隆过滤器,你真的懂吗?
来源:布隆过滤器,你真的懂吗?

简历上谁都会写着“精通 Redis”、“擅长处理海量数据”吧?
现在面试官问:“现在有 10 亿条黑名单数据,给你 512MB 内存,怎么判断一个 ID 是否在黑名单里?”
你立刻回答:“用布隆过滤器。”
这一步谁都会。但面试官紧接着会问你下面一些关键问题:
- 布隆过滤器怎么支持数据删除?比如这个黑名单用户洗白了,怎么将他踢出去?
- 2如果不支持删除,工程上如何解决数据更新滞后的问题?
- 在这个过程中,布隆过滤器的误判率(False Positive)和漏判率(False Negative)如何控制?
这三个问题一问出来,就没有那个业务场景那么简单了。很多人支支吾吾半天,最后只会憋出一句:“布隆过滤器……好像不能删吧?”
这就是你简历上写的“精通架构设计”?说不清楚这些工程中实际遇到的问题,你的技术深度在面试官眼里几乎为零。
基础原理与核心价值

要回答这些问题,首先要弄清楚布隆过滤器的基础原理。
布隆过滤器本质上就是一个很长的位图(BitMap)以及 K 个哈希函数。当数据到来时,我不存原始数据,而是通过 K 个哈希函数计算出 K 个位置,并将这些位图中对应的位置都置为 1。
例如,用户 A 的哈希映射结果是 [1, 4, 7],而用户 B 的哈希映射结果是 [4, 9, 10]。需要特别注意的位置 4:发生了哈希冲突,这意味着两个不同的用户映射到了同一个位置。
查询时,再次通过哈希函数计算数据。如果这 K 个位置全是 1,说明此数据可能存在;如果有任何一个是 0,说明该数据一定不存在。
关于布隆过滤器的设计,主要有以下几方面的考虑:

- 极致空间效率: 布隆过滤器不存储原始数据,而是仅存储位(Bit)。这样,它能够将存储数据的空间需求降至最低。例如,如果使用 HashSet 存储 10 亿个 Long 型 ID,光数据本身就需要约 8GB 内存,这还不包括对象头的开销。而布隆过滤器只需几百兆内存就能容纳这些数据,这样在处理海量数据时,就极大地减少了内存占用。
- 极速查询 : 布隆过滤器查询速度与数据集的大小无关,只与哈希函数的数量有关。通过 K 个哈希函数来判断数据是否存在,布隆过滤器可以在常数时间内完成查询操作。因此布隆过滤器非常适合那些需要高速查询的场景。
- 数据安全:
布隆过滤器不存储明文信息,这样就具有天然的数据脱敏特性。这一点在敏感数据的存储与查询中非常关键,保证了用户信息的隐私与安全性。
致命缺陷
然而,布隆过滤器也存在几个致命缺陷,需特别注意:
- 存在误判(False Positive): 布隆过滤器判断“该数据存在”,但实际上这个数据可能并不存在。这就是他在设计上的根本问题。在业务中需要合理设计,避免造成误解。
- 无法删除数据: 这是布隆过滤器最大的缺陷!后面也会介绍在工程上如何避免出现问题。
- 容量难以扩展: 布隆过滤器在初始化后长度是固定的,若需要扩展,必须重建整个过滤器。这在动态场景下可能会带来额外的复杂性。
所以在设计和使用布隆过滤器时,就必须权衡空间效率和查询速度带来的优势,以及删除和扩容带来的不足。对业务进行合理设计,才能在实际应用中做出最佳选择。
痛点深挖:删除的死穴

但这东西有个致命的死穴。面试官问:“现在业务变更了,我要删除一个数据,你怎么做?”
你可能会想着去把 K 个位置的 1 改回 0?
这是大错特错!哈希是会冲突的!假如数据 A 和数据 B 的哈希位置重叠了,比如说位置 4。你为了删 A,把位置 4 置为 0,那 B 是不是也被你“误杀”了?
B 明明在,但你判断有个位置是 0,就认为它不存在了。这就叫漏判(False Negative)。在布隆过滤器的定义中,误判(明明不在却误报存在)是可以容忍的,但漏判(明明在却报不在)是绝对不允许的!
因此,标准的布隆过滤器根本不支持删除操作!
那如果工程中有这样的需求,怎么办呢?
工程化解决方案:

进阶方案 1:计数布隆过滤器
那业务方非要支持删除怎么办?很多教科书会教你第一招:计数布隆过滤器(Counting Bloom Filter)。既然 1 个 bit 存不下,那就扩容,把 BitMap 里的每一个 bit 改成多个bit,用来记录该位置重复添加的次数。
比如,bitmap中原来每一位只存 0 和 1,现在变成存4个 bit。加数据时,K 个位置的计数器全部 +1;删数据时,K 个位置的计数器全部 -1,减到 0 就代表数据删除干净了。
这个方案能用吗?能用,但很鸡肋。你原来只用 1 个 bit,现在用了 4 个 bit,空间消耗直接翻了 4 倍以上!
布隆过滤器的核心优势就是节省空间,改了之后优势全没了。而且,如果某个热点数据重复严重,计数器一旦溢出,整个过滤器的数据就全乱了。
进阶方案 2:布谷鸟过滤器
那有没有既能省空间,又能支持删除,查询还超快的方案?
这时你得拿出杀手锏:布谷鸟过滤器(Cuckoo Filter)。它抛弃了 BitMap 的设计,采用了布谷鸟哈希(Cuckoo Hashing)。它存的是数据的指纹(Fingerprint)。
数据到来时,计算两个哈希桶的位置,哪个桶空着就放哪个。如果两个桶都满了,怎么办?
重点来了!它会把其中一个桶的老数据踢走,将新数据放进去。被踢出去的老数据去备用位置看看,如果有空,它就可以住进去了;如果仍然没有空位,就继续踢,直到所有数据都有位置。
由于其存储的是指纹,删除时直接匹配指纹进行删除,根本不影响其他数据。这才是真正的技术进阶。现在Redis的官方模块RedisBloom中就直接提供了布谷鸟过滤器的功能支持。适合高性能、又需要动态增删数据的生产场景
工程化落地:回归现实
那是不是所有场景都要上布谷鸟过滤器?并不是。布谷鸟过滤器实现复杂,且如果桶太满,踢来踢去有可能陷入死循环,导致插入失败,需要扩容或重建。而布谷鸟过滤器的重建成本显然是高于布隆过滤器的。
所以在绝大多数公司的实际工程中,最稳妥、最简单的方案其实是——定时重建(Rebuild)厐过滤器。
如果你的数据没有实时性要求,可以使用两个标准的布隆过滤器。通过双Buffer后台更新的方式,减少重建布隆过滤器期间对业务的影响。
比如每隔 30 分钟,利用后台任务,基于数据库里的最新全量数据,重新构建一个新的过滤器,构建完成后直接替换旧的。这既解决了删除数据滞后的问题,又保留了布隆过滤器最原始的空间优势,且代码极其简单,不容易出 Bug。
总结
所以,面试时不仅要掌握标准布隆过滤器的原理,还需了解其删除缺陷,明白计数版的不足,以及布谷鸟过滤器的原理。最后,要能回归到工程化的实际方案,比如定时重建,这才是架构师应有的回答逻辑。