拓展阅读

一致性 hash 算法

java hash 实现

hash 冲突处理

bloom filter

bloom filter 使用最佳实践,让你少采坑!

todo

工具包包的封装+文档

资源可视化

前后端===》项目

弱点解决===》场景解决方案/技术咨询

chat

详细介绍一下 BloomFliter

好的,我们来系统、详细地介绍一下 Bloom Filter(布隆过滤器)


一、Bloom Filter 是什么?

Bloom Filter 是一种空间效率极高的“概率型数据结构”,用于判断一个元素是否在一个集合中。

它的特点是:

  • 可以判断“可能存在”或“一定不存在”
  • 存在一定误判率(可能把不存在的元素误判为存在)
  • 不会漏判(存在的元素一定判断为存在)
  • 不支持删除元素(标准版)

它特别适合大数据量、对存储和速度要求高,但容忍一定误差的场景


二、Bloom Filter 的核心原理

1. 数据结构组成

Bloom Filter 本质上由两部分组成:

  • 一个 bit 数组(初始全为 0)
  • 一组哈希函数(k 个)

2. 添加元素(以字符串 "hello" 为例)

  • 用 k 个哈希函数分别对 "hello" 进行哈希,得到 k 个下标。
  • 在 bit 数组中,将这 k 个下标位置置为 1。

3. 查询元素(比如查询 "world"

  • 用相同的 k 个哈希函数对 "world" 哈希,得到 k 个下标。
  • 检查这些下标在 bit 数组中对应的位置是否都是 1:

    • 如果有任意一个是 0,说明一定 没出现过
    • 如果全部是 1,说明 可能出现过(但也可能是其他元素共同“碰巧”设置的)。

三、例子:可视化理解

假设我们有一个 bit 数组长度为 10:

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

我们有 3 个哈希函数,向 Bloom Filter 中加入 "cat"

  • hash1("cat") % 10 = 1
  • hash2("cat") % 10 = 4
  • hash3("cat") % 10 = 7

将这些位置设为 1:

[0, 1, 0, 0, 1, 0, 0, 1, 0, 0]

查询 "dog" 时:

  • 如果某一个位置是 0,直接返回“不存在”
  • 如果三个位置都是 1,那就说“可能存在”

四、优缺点总结

✅ 优点

  • 节省空间:比哈希表或集合更节省内存
  • 查询速度快:时间复杂度为 O(k),k 是哈希函数个数
  • 适合并发场景:因为只涉及 bit 操作,且不支持删除,线程安全性好

❌ 缺点

  • 有误判率:查询可能返回 false positive
  • 不支持删除(除非使用 Counting Bloom Filter)
  • 哈希函数选择很关键:影响误判率和性能
  • 无法获取元素本身的集合内容:只能判断是否存在

五、实际应用场景

🔍 1. 网页黑名单过滤

判断某个 URL 是否在黑名单中,节省内存,误判也能容忍。

📚 2. 搜索引擎的缓存过滤器

判断查询请求是否在缓存中,避免频繁查数据库或分布式系统。

🛒 3. 电商防重复推荐

防止给用户推荐已经看过或买过的商品。

📬 4. 邮件反垃圾系统

快速判断邮件地址是否在垃圾地址库中。

🧠 5. 区块链

比如 Bitcoin 中用于过滤区块数据,客户端只接受可能相关的交易。


六、扩展变种

名称 特点
Counting Bloom Filter 支持删除功能,用计数器代替 bit 位
Scalable Bloom Filter 自动扩容以适应无限数据增长,维持误判率
Compressed Bloom Filter 更节省空间,适合传输
Partitioned Bloom Filter 将 bit 数组分为 k 段,每个哈希函数只对应一段,减少冲突

七、误判率计算(了解)

设:

  • m:bit 数组大小
  • k:哈希函数个数
  • n:插入元素个数

则查询某个元素的误判率大约为:

\[\left(1 - e^{-kn/m} \right)^k\]

你可以根据这个公式,选择合理的 m 和 k,来控制误判率。


八、与 HashSet 的比较

功能/特点 Bloom Filter HashSet
占用内存 少得多
查询速度
支持删除 不支持(标准版) 支持
可枚举内容
是否存在误判
是否线程安全 较容易实现 需要加锁或并发容器

如果你正在做类似缓存、反垃圾、推荐系统或大规模日志去重等场景,Bloom Filter 是一个非常实用的工具。

需要的话,我可以给你写一份 Java 或 Python 的 Bloom Filter 实现。

如何解决数据被删除的问题呢?比如定期重新构建一下?业界比较推荐的做法是?

这是个很实际的问题,也是 Bloom Filter 在生产环境中经常需要面对的挑战:如何解决“不能删除元素”的问题?


一、背景:为什么 Bloom Filter 不支持删除?

因为 Bloom Filter 使用的是一个 bit 数组,当你添加元素时,多个元素可能会影响同一位

比如:

  • 元素 A 和元素 B 都将第 5 位设为了 1
  • 如果你删除 A,把第 5 位清零了,B 的存在也被破坏了

这就是“误删”问题。


二、常见解决方案

✅ 方案一:使用 Counting Bloom Filter(计数型布隆过滤器)

原理: 把 bit 数组变成 计数数组(比如每个位置是一个 uint8,初始为 0)

  • 插入元素时,对应位置 +1
  • 删除元素时,对应位置 -1
  • 查询时,只要对应位置 > 0 就认为“可能存在”

优点

  • 完整支持删除操作
  • 实现简单

缺点

  • 占用空间比普通 Bloom Filter 大(从 1bit 变成 4bit 或 8bit)
  • 删除操作仍然不能解决“误判”问题(误删别人的数据)

👉 使用场景:比较适合缓存、实时数据流、有生命周期的数据场景。


✅ 方案二:定期“重建” Bloom Filter(Rebuild

原理

  • Bloom Filter 本身不支持删除
  • 但你可以在后台维护一份真实的数据(比如 Redis、数据库)
  • 然后定期全量重新构建 Bloom Filter,清除脏数据

常见操作时机

  • 每隔一段时间,比如 30 分钟、1 小时
  • 或者数据量达到某个阈值时触发
  • 或者后台触发器检测到某个 key 需要删除时,打个标记等待清理

优点

  • 实现简单,兼容原始 Bloom Filter
  • 不增加 Bloom Filter 的体积

缺点

  • 需要额外维护真实数据源
  • 重建 Bloom Filter 期间有可能有“脏读”

👉 使用场景:大规模缓存穿透拦截(如 Redis 缓存层)


✅ 方案三:分片 + 版本化 Bloom Filter(Double Buffer / Rotating Bloom Filter

原理

  • 同时维护多个布隆过滤器,如:FilterA 和 FilterB
  • 当前写入 FilterA,同时老的 FilterB 还在使用
  • 等 FilterB 过期后丢弃,轮换成新的 FilterA(类似“热数据-冷数据”)

你可以把这个模型想象成一个“定时轮换”的轮子。

优点

  • 不影响读写
  • 支持定时清除旧数据

缺点

  • 需要管理多份 Bloom Filter
  • 可能短期内会存在一定冗余

👉 使用场景:搜索引擎反爬、日志系统、消息去重


✅ 方案四:使用 LRU + Bloom Filter 组合方案

原理

  • Bloom Filter 做第一层判断
  • 后面再跟一个 LRU 缓存记录真正查询/访问过的 key
  • 当 LRU 淘汰某个 key 时,同时记录下来,延迟触发 Bloom Filter 的 rebuild

👉 使用场景:高并发系统的缓存命中判断(比如 CDN、热点视频缓存)


三、业界推荐实践(总结)

场景 推荐方案 理由
缓存穿透拦截 普通 Bloom Filter + 定期重建 简单高效,误判影响小
短期数据去重 计数 Bloom Filter 支持删除,去重精度高
大规模高并发去重 分片 Bloom Filter + 热备 低冲突,读写分离
需极高精度 用哈希集合 + LFU 或 LRU 替代 Bloom Filter 不容忍误判时使用

四、小贴士:什么时候不应该用 Bloom Filter?

  • 对误判极度敏感(如金融风控)
  • 元素量小,不差这点空间(直接用 Set 更简单)
  • 需要频繁删除和修改(会增加实现成本)

如果你给我具体场景(比如你在自动化测试平台里使用 Bloom Filter 的用途),我可以帮你选最合适的方案并提供具体实现建议。是否考虑加入 Redis、Guava、RoaringBitmap 等工具库?也可以配套分析一下。

是否可以通过 bloom-filter 作为第一层,redis 之类的缓存作为第二层,mysql 兜底的结合方案?来解决误判的问题

非常棒的想法!你说的这个组合方案,实际上已经是**业界最经典、最成熟的“缓存穿透防护三段式架构”**之一了👇


✅ 一、架构概览:三层过滤机制

        请求 Key
           ↓
╭────────────────────╮
│ 1. Bloom Filter (内存) │→ 若为不存在,则直接拒绝请求(一定不存在)
╰────────────────────╯
           ↓
╭────────────────────╮
│ 2. Redis 缓存层       │→ 命中则直接返回数据
╰────────────────────╯
           ↓
╭────────────────────╮
│ 3. MySQL 等数据库层   │→ 真正查询并回填缓存
╰────────────────────╯

✅ 二、这个组合能解决什么问题?

🌩️ 1. 防止 缓存穿透

某些恶意或错误请求频繁查询根本不存在的 key,直接穿透到 DB:

  • ❌ Redis 中没有
  • ❌ 数据库也没有
  • ✅ Bloom Filter 先挡掉大部分不存在的 key!

🎯 2. 降低 Redis 压力

由于 Bloom Filter 是在本地内存中执行 bit 数组判断,速度极快,能挡掉大量无效请求,Redis 能只处理更“可能真实”的 key。

📉 3. 缓解 数据库压力

对于 Bloom Filter 误判漏掉的少量 key,通过 Redis 兜一层,减轻 DB 查询频率。


✅ 三、示例流程(以用户请求 GET /product?id=12345 为例)

  1. Bloom Filter 判断 key=product:12345 是否可能存在

    • 若明确不存在 → 直接拒绝请求,返回空
    • 若可能存在 → 继续查询缓存
  2. Redis 查询是否缓存该商品信息

    • 若命中 → 返回数据
    • 若未命中 → 进入数据库查询
  3. MySQL 查询真实数据

    • 若查到 → 返回数据 + 写入 Redis 缓存
    • 若没查到 → 返回空 + 可以选择写入 Redis 空值(防止短时间内重复穿透)

✅ 四、配套策略建议

项目 建议配置
Bloom Filter 本地内存(Guava、Caffeine 或 Redis 模块),定期重建,或 Counting Bloom
Redis 缓存 设置过期时间,热点数据可以用永久 key,冷数据可自动淘汰
缓存空值 Redis 可以存 "__null__" 作为空占位,避免重复穿透
MySQL 查询后 若查到 → 缓存;若查不到 → 空值或异步补数据
熔断机制 Redis 和 DB 出现问题时快速 fail,避免雪崩
数据同步 可用 Binlog、定时任务、消息队列等同步数据到 Bloom Filter

✅ 五、代码角度如何组织?(伪代码)

public Product getProduct(String id) {
    // 1. bloom filter 判断
    if (!bloomFilter.mightContain(id)) {
        return null; // 一定不存在,直接返回
    }

    // 2. redis 查询
    String redisKey = "product:" + id;
    Product cached = redis.get(redisKey);
    if (cached != null) {
        return cached.equals("__null__") ? null : cached;
    }

    // 3. DB 查询
    Product dbProduct = mysql.queryProductById(id);
    if (dbProduct != null) {
        redis.set(redisKey, dbProduct, EXPIRE_TIME);
    } else {
        redis.set(redisKey, "__null__", SHORT_EXPIRE); // 空值防穿透
    }

    return dbProduct;
}

✅ 六、实际案例

公司 应用 Bloom Filter 的场景
淘宝 商品推荐防止重复展示
美团 高频业务请求去重、请求过滤
百度 网页索引的去重与过滤
腾讯 海量用户账号黑名单快速判断
今日头条 推荐系统历史记录过滤

✅ 七、小结

优势 实现方式
✅ 快速拦截不存在的 key Bloom Filter
✅ 缓存热点数据、减少 DB 压力 Redis
✅ 保证数据最终一致性 MySQL 兜底
✅ 减少误判副作用 Redis 空值缓存,或异步纠正
✅ 易于扩展 可以引入分布式布隆过滤器、Cuckoo Filter 等新方案

如果你用在自动化测试平台中,比如:判断某个测试任务是否已执行、某个任务 ID 是否存在于计划中等场景,这种架构也完全适用。

需要我帮你写具体的实现代码(Java + Redis + Guava BloomFilter)也可以告诉我 👍

除了上面的问题之外,bloom-filter 使用还有哪些要注意的点?

Bloom Filter 虽然强大,但它也不是万能的,使用时确实需要注意一些 陷阱、限制和设计决策。下面给你系统整理一下:


✅ 一、核心特性再强调一次(理解这些才能避免误用)

特性 说明
只支持新增,不支持删除(除非用 Counting Bloom) 因为不同元素可能共享 bit 位,删除容易误删
存在一定误判(False Positive) 会误判不存在的 key 为“可能存在”
一定不会漏判(False Negative) 真正存在的 key 一定会返回“可能存在”
无法列出所有已添加的元素 因为 Bloom Filter 不存储元素,只存 bit 信息

🧱 二、使用 Bloom Filter 时要特别注意的点

1️⃣ 哈希函数的选择 至关重要

  • 哈希函数要独立、分布均匀
  • 通常选择 MurmurHash、Guava 提供的 Funnels 等高质量 hash
  • 太弱的 hash 函数会导致 bit 位集中,误判率暴涨

💡 业界常见做法:

  • 使用一个强哈希函数 + 多次扰动(例如 hash1(x), hash1(x+salt1), hash1(x+salt2)…)

2️⃣ 合理配置参数:m, k, n

  • m: bit 数组长度
  • k: 哈希函数个数
  • n: 预期元素个数

推荐使用误判率公式进行预估(误判率 fp):

\[fp \approx \left(1 - e^{-kn/m}\right)^k\]

🧠 最优的哈希函数数量 k 为:

\[k = \frac{m}{n} \ln 2\]

👉 若 m/n 比例不足、k 设置不当,会让误判率严重偏高。


3️⃣ 误判带来的影响不能被忽视

即使误判率只有 0.1%,在高频系统中也很可观:

  • 每天 1 亿请求 → 误判 10 万次
  • 如果误判带来“请求落库”,会造成数据库雪崩

💡 解决办法:

  • 使用 Redis 缓存空值
  • 过滤后仍需要“二次兜底验证”

4️⃣ 容量是固定的,一旦满了误判率会迅速升高

Bloom Filter 是容量敏感型结构,当加入的数据远多于预计值 n 时:

  • 会导致 bit 数组变得越来越“稠密”
  • 误判率接近 100%

🧩 解决办法:

  • Scalable Bloom Filter(支持自动扩容)
  • 或者当使用量超过某阈值时,主动 rebuild

5️⃣ 不支持取出数据(不是传统意义的集合)

  • 无法知道到底存了哪些元素
  • 无法遍历、删除、查看 key 的元信息
  • 因此不能替代哈希集合(Set)或数据库

6️⃣ 不适合对数据准确性要求极高的场景

⚠️ 禁止 Bloom Filter 使用场景:

  • 支付、交易、风控、金融等不容许误判的业务
  • 权限控制(误判可能导致非法访问)
  • 安全认证(误判就是漏洞)

7️⃣ 分布式部署注意“版本同步”问题

在多个实例上共享 Bloom Filter(如 Redis Bloom 模块)时,要注意:

  • 所有服务使用同一个版本的 Bloom Filter
  • 否则可能因版本不一致、bit 数据未同步 → 判断结果不同步

💡 建议:

  • 使用 Redis Bloom、Guava BloomFilter 并封装成一个独立服务
  • 保证集群间读写一致性(用消息队列或定时同步)

8️⃣ 冷启动期间,bloom filter 是“空的”

系统刚启动时,Bloom Filter 可能还没加载完历史数据,导致:

  • 本应存在的 key 被误判为“不存在”
  • 从而引发“该去查数据库的却被挡掉了”

💡 解决方法:

  • 冷启动时延迟启用 Bloom Filter
  • 启动阶段查询 Redis 和 DB,再逐步灌入 Bloom Filter

✅ 三、Bloom Filter 的使用 checklist

是否处理
是否预估了最大数据量 n
是否根据目标误判率计算了 mk
哈希函数是否足够分散?
是否考虑误判的后果?
是否设计了重建机制?
是否有冷启动方案?
是否记录了 Bloom Filter 的“构建版本”?
是否使用 Redis 空值/缓存兜底?

✅ 四、推荐工具库和组件

工具 描述
Guava BloomFilter Java,简单易用,适合本地内存构建
Caffeine + BloomFilter 本地缓存 + 过滤器结合
RedisBloom Redis 模块,支持分布式部署和计数功能
Apache Commons BloomFilter Java 系列工具
Scalable BloomFilter(Google 论文) 自动扩容版 Bloom Filter
Roaring Bitmap 另一个压缩位图过滤器,用于整数集过滤

如果你有具体使用场景(比如某模块接口防穿透、任务ID去重、日志过滤等),我可以基于实际场景帮你再优化一版配置和设计方案。