布隆过滤器实战!垃圾邮件识别?重复元素判断?缓存穿透?

本文深入解析布隆过滤器的工作原理,探讨其在URL去重、垃圾邮件识别等场景的应用,通过实战演示如何使用Guava库实现布隆过滤器,降低误报率。

作者:semlinker  
https://segmentfault.com/a/1190000021136424

在程序的世界中,布隆过滤器是程序员的一把利器,利用它可以快速地解决项目中一些比较棘手的问题。如网页 URL 去重、垃圾邮件识别、大集合中重复元素的判断和缓存穿透等问题。

布隆过滤器(Bloom Filter)是 1970 年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难

一、布隆过滤器简介

当你往简单数组或列表中插入新数据时,将不会根据插入项的值来确定该插入项的索引值。这意味着新插入项的索引值与数据值之间没有直接关系。这样的话,当你需要在数组或列表中搜索相应值的时候,你必须遍历已有的集合。若集合中存在大量的数据,就会影响数据查找的效率。

针对这个问题,你可以考虑使用哈希表。利用哈希表你可以通过对 “值” 进行哈希处理来获得该值对应的键或索引值,然后把该值存放到列表中对应的索引位置。这意味着索引值是由插入项的值所确定的,当你需要判断列表中是否存在该值时,只需要对值进行哈希处理并在相应的索引位置进行搜索即可,这时的搜索速度是非常快的。

根据定义,布隆过滤器可以检查值是 “可能在集合中” 还是 “绝对不在集合中”。“可能” 表示有一定的概率,也就是说可能存在一定为误判率。那为什么会存在误判呢?下面我们来分析一下具体的原因。

布隆过滤器(Bloom Filter)本质上是由长度为 m 的位向量或位列表(仅包含 0 或 1 位值的列表)组成,最初所有的值均设置为 0,如下图所示。

为了将数据项添加到布隆过滤器中,我们会提供 K 个不同的哈希函数,并将结果位置上对应位的值置为 “1”。在前面所提到的哈希表中,我们使用的是单个哈希函数,因此只能输出单个索引值。而对于布隆过滤器来说,我们将使用多个哈希函数,这将会产生多个索引值。

如上图所示,当输入 “semlinker” 时,预设的 3 个哈希函数将输出 2、4、6,我们把相应位置 1。假设另一个输入 ”kakuqo“,哈希函数输出 3、4 和 7。你可能已经注意到,索引位 4 已经被先前的 “semlinker” 标记了。此时,我们已经使用 “semlinker” 和 ”kakuqo“ 两个输入值,填充了位向量。当前位向量的标记状态为:

当对值进行搜索时,与哈希表类似,我们将使用 3 个哈希函数对 ” 搜索的值 “进行哈希运算,并查看其生成的索引值。假设,当我们搜索”fullstack“ 时,3 个哈希函数输出的 3 个索引值分别是 2、3 和 7:

从上图可以看出,相应的索引位都被置为 1,这意味着我们可以说 ”fullstack“ 可能已经插入到集合中。事实上这是误报的情形,产生的原因是由于哈希碰撞导致的巧合而将不同的元素存储在相同的比特位上。幸运的是,布隆过滤器有一个可预测的误判率(FPP):

  • n 是已经添加元素的数量;

  • k 哈希的次数;

  • m 布隆过滤器的长度(如比特数组的大小);

极端情况下,当布隆过滤器没有空闲空间时(满),每一次查询都会返回 true 。这也就意味着 m 的选择取决于期望预计添加元素的数量 n ,并且 m 需要远远大于 n 。

实际情况中,布隆过滤器的长度 m 可以根据给定的误判率(FFP)的和期望添加的元素个数 n 的通过如下公式计算:

了解完上述的内容之后,我们可以得出一个结论,当我们搜索一个值的时候,若该值经过 K 个哈希函数运算后的任何一个索引位为 ”0“,那么该值肯定不在集合中。但如果所有哈希索引值均为”1“,则只能说该搜索的值可能存在集合中

二、布隆过滤器应用

在实际工作中,布隆过滤器常见的应用场景如下:

  • 网页爬虫对 URL 去重,避免爬取相同的 URL 地址;

  • 反垃圾邮件,从数十亿个垃圾邮件列表中判断某邮箱是否垃圾邮箱;

  • Google Chrome 使用布隆过滤器识别恶意 URL;

  • Medium 使用布隆过滤器避免推荐给用户已经读过的文章;

  • Google BigTable,Apache HBbase 和 Apache Cassandra 使用布隆过滤器减少对不存在的行和列的查找。
    除了上述的应用场景之外,布隆过滤器还有一个应用场景就是解决缓存穿透的问题。所谓的缓存穿透就是服务调用方每次都是查询不在缓存中的数据,这样每次服务调用都会到数据库中进行查询,如果这类请求比较多的话,就会导致数据库压力增大,这样缓存就失去了意义。

利用布隆过滤器我们可以预先把数据查询的主键,比如用户 ID 或文章 ID 缓存到过滤器中。当根据 ID 进行数据查询的时候,我们先判断该 ID 是否存在,若存在的话,则进行下一步处理。若不存在的话,直接返回,这样就不会触发后续的数据库查询。需要注意的是缓存穿透不能完全解决,我们只能将其控制在一个可以容忍的范围内。

三、布隆过滤器实战

布隆过滤器有很多实现和优化,由 Google 开发著名的 Guava 库就提供了布隆过滤器(Bloom Filter)的实现。在基于 Maven 的 Java 项目中要使用 Guava 提供的布隆过滤器,只需要引入以下坐标:


   
  1. <dependency>

  2.    <groupId>com.google.guava</groupId>

  3.    <artifactId>guava</artifactId>

  4.    <version>28.0-jre</version>

  5. </dependency>

在导入 Guava 库后,我们新建一个 BloomFilterDemo 类,在 main 方法中我们通过 BloomFilter.create 方法来创建一个布隆过滤器,接着我们初始化 1 百万条数据到过滤器中,然后在原有的基础上增加 10000 条数据并判断这些数据是否存在布隆过滤器中:


   
  1. import com.google.common.base.Charsets;

  2. import com.google.common.hash.BloomFilter;

  3. import com.google.common.hash.Funnels;

  4. public class BloomFilterDemo {

  5.     public static void main(String[] args) {

  6.         int total = 1000000; // 总数量

  7.         BloomFilter<CharSequence> bf = 

  8.           BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), total);

  9.         // 初始化 1000000 条数据到过滤器中

  10.         for (int i = 0; i < total; i++) {

  11.             bf.put("" + i);

  12.         }

  13.         // 判断值是否存在过滤器中

  14.         int count = 0;

  15.         for (int i = 0; i < total + 10000; i++) {

  16.             if (bf.mightContain("" + i)) {

  17.                 count++;

  18.             }

  19.         }

  20.         System.out.println("已匹配数量 " + count);

  21.     }

  22. }

当以上代码运行后,控制台会输出以下结果:


   
  1. 已匹配数量 1000309

很明显以上的输出结果已经出现了误报,因为相比预期的结果多了 309 个元素,误判率为:


   
  1. 309/(1000000 + 10000) * 100 ≈ 0.030594059405940593

如果要提高匹配精度的话,我们可以在创建布隆过滤器的时候设置误判率 fpp:


   
  1. BloomFilter<CharSequence> bf = BloomFilter.create(

  2.   Funnels.stringFunnel(Charsets.UTF_8), total, 0.0002

  3. );

在 BloomFilter 内部,误判率 fpp 的默认值是 0.03:


   
  1. // com/google/common/hash/BloomFilter.class

  2. public static <T> BloomFilter<T> create(Funnel<? super T> funnel, long expectedInsertions) {

  3.   return create(funnel, expectedInsertions, 0.03D);

  4. }

在重新设置误判率为 0.0002 之后,我们重新运行程序,这时控制台会输出以下结果:


   
  1. 已匹配数量 1000003

通过观察以上的结果,可知误判率 fpp 的值越小,匹配的精度越高。当减少误判率 fpp 的值,需要的存储空间也越大,所以在实际使用过程中需要在误判率和存储空间之间做个权衡。

四、总结

本文主要介绍的布隆过滤器的概念和常见的应用场合,在实战部分我们演示了 Google 著名的 Guava 库所提供布隆过滤器(Bloom Filter)的基本使用,同时我们也介绍了布隆过滤器出现误报的原因及如何提高判断准确性。最后为了便于大家理解布隆过滤器,我们介绍了一个简易版的布隆过滤器 SimpleBloomFilter。

(end)

【推荐阅读】

Lucene&Solr&ElasticSearch-面试题

 Java 9 ← 2017,2019 → Java 13 ,来看看Java两年来的变化

永远不要在代码中使用「User」这个单词

ElasticSearch最全详细使用教程:入门、索引管理、映射详解

ElasticSearch最全详细使用教程:索引别名、分词器、文档管理、路由、搜索详解

java思维导图

长按关注,每天java一下,成就架构师

我就知道你在看!

<think>嗯,用户现在想评估自己对分布式系统的理解深度,以便做出职业选择和技能提升的决策。之前他提到自己在大数据和Java后端之间纠结,现在需要更具体的评估方法。首先,我得回想之前给出的建议,里面提到了可行性测试和技能验证,比如手写Hive SQL和Java的生产者-消费者模型。现在用户需要针对分布式系统的评估方法,可能需要结合理论和实践。 用户提供的引用内容里有关于架构评估的方法,比如效用树分析、场景评估、原型演示等。这些可以作为评估框架的一部分。比如引用[2]提到分析架构方法,确定风险决策和权衡点,这可能适用于用户自我评估的场景。另外,引用[3]提到高性能架构需要关注限流、扩容和优化,这也是分布式系统的重要方面。 接下来,用户的需求是具体的方法,而不仅仅是理论。所以应该分层次,从基础概念到设计能力,再到实战经验。可能包括自测题目、项目案例分析、动手实验等。同时,考虑到用户的双非背景,需要强调如何通过项目和开源贡献来提升竞争力,这在之前的回答中也提到过。 还要注意用户可能的实际水平,比如是否了解CAP定理、一致性算法,能否设计高可用系统。需要设计不同难度的问题,帮助用户定位自己的水平。比如,是否理解Raft协议,能否处理数据倾斜问题,这些都是大数据和分布式系统的关键点。 另外,结合引用中的架构评估方法,可以建议用户进行场景分析,比如设计一个电商系统,考虑如何分库分表,如何处理分布式事务。这样的案例分析能检验用户的实际应用能力。同时,引用[4]的场景视图可以作为综合评估的一部分,通过用例来验证其他视图的一致性。 最后,要给出提升建议,比如学习路径、推荐资料,以及如何通过项目或认证弥补学历短板。之前的回答中提到了AWS/Aliyun认证和开源贡献,这里可以再次强调。同时,建议用户参与实际项目或模拟环境,比如用Docker搭建集群,处理实际问题,来加深理解。 总结下来,回答需要结构清晰,分层次评估,结合理论和实践,引用相关内容,并给出可行的提升策略,帮助用户明确自己的水平,并制定下一步计划。</think>### 分布式系统理解深度评估框架 --- #### 一、基础概念理解度测试(理论层) **1. 核心定理验证** - **CAP定理应用测试** ```markdown Q: 某银行系统要求强一致性,但遭遇网络分区时该如何取舍? A: 必须选择CP(放弃可用性),通过拒绝服务保证数据一致性[^3] ``` **通过标准**:能举例说明不同业务场景下的CAP选择策略 - **BASE理论实践判断** $$最终一致性 = \sum_{i=1}^{n} \frac{补偿事务次数}{时间窗口}$$ **测试案例**:设计一个最终一致性订单系统,要求说明: - 如何实现异步通知 - 幂等性保障机制 - 对账补偿逻辑 **2. 协议算法掌握度** ```mermaid graph TD A[能否手写Raft选举流程伪代码?] --> B[理解Leader心跳机制] C[是否能用Paxos解释Zookeeper选举?] --> D[区分ZAB与Paxos差异] E[是否理解Gossip协议在Consul中的应用?] --> F[能计算传播收敛时间] ``` --- #### 二、架构设计能力评估(设计层) **1. 系统拆分设计测试** - **微服务划分验证** ```java // 给定电商系统,能否识别以下耦合点? class OrderService { void createOrder() { inventoryService.reduceStock(); // 跨服务调用 userService.addPoints(); // 业务耦合 paymentService.process(); // 事务边界 } } ``` **达标要求**:能提出DDD聚合根改造方案,设计Saga事务补偿机制[^2] **2. 分布式存储设计** - **分库分表方案评估** $$查询复杂度 = \log_2(分片数) + \frac{跨节点Join次数}{索引覆盖度}$$ **测试题**:设计用户订单表分片策略,要求: - 支持用户ID和商户ID双维度查询 - 处理热点商户数据倾斜问题 - 保证range查询性能 **3. 容灾架构设计** **评估矩阵**: | 故障类型 | 检测方法 | 恢复方案 | 验证指标 | |----------------|-----------------------|-------------------------|-------------------| | 网络分区 | 心跳探测+Quorum机制 | 自动降级为分区模式 | MTTR < 30s | | 节点宕机 | ZooKeeper临时节点监听 | 流量切换+副本重平衡 | 数据丢失量=0 | | 磁盘故障 | SMART监控+RAID校验 | 热备盘自动替换 | IOPS下降率<5% | --- #### 三、实战能力检验(工程层) **1. 开发验证实验** ```python # 用Docker搭建最小化分布式系统 def test_distributed_system(): deploy_etcd_cluster(nodes=3) # 键值存储 setup_kafka_cluster(brokers=2) # 消息队列 create_grpc_services( # 服务通信 service_a =负载均衡策略, service_b =熔断配置 ) inject_failure(network_partition=True) # 混沌测试 validate_consistency() # 数据一致性检查 ``` **通过标准**:能解释每个组件的选型依据和故障处理过程 **2. 性能调优挑战** **场景**:某推荐系统响应时间从200ms突增至2s **诊断流程**: $$根因定位 = \frac{日志分析 + 链路追踪}{监控指标关联度} \times 压测验证$$ **关键检查点**: - 分布式锁竞争(Redisson锁续期机制) - 缓存穿透布隆过滤器实现) - 消息堆积(Kafka消费者rebalance策略) --- #### 四、深度理解评估标准 **1. 认知层次对照表** | **层级** | 特征 | 检验方法 | |----------|-----------------------------|-----------------------------| | L1入门 | 能复述CAP/BASE理论 | 解释Paxos算法基本流程 | | L2进阶 | 设计多副本一致性方案 | 实现Raft日志复制状态机 | | L3专家 | 解决跨数据中心同步难题 | 设计混合逻辑时钟(HLC)方案 | | L4架构师 | 预判新技术对架构的影响 | 评估Service Mesh对现有系统的改造成本 | **2. 量化评估公式** $$理解深度 = \frac{设计案例数 \times 复杂度}{概念盲区数} + \log(故障排查经验)$$ --- #### 五、能力提升路线图 **1. 渐进式学习路径** ```mermaid graph LR A[《DDIA》精读] --> B[MIT6.824实验] C[参与CNCF项目] --> D[实现自定义CRDT] E[分析Flink Checkpoint机制] --> F[改进现有系统容错设计] ``` **2. 关键能力培养** - **架构嗅觉训练**:每周分析1个知名系统设计(如Redis Cluster、K8s调度器) - **故障模拟实践**:使用ChaosMesh制造以下场景: ```bash # 模拟典型故障 chaosd attack network delay --latency 500ms --jitter 100ms chaosd attack pod kill --label app=payment-service ``` **3. 项目经验积累策略** - **改造现有项目**: ```java // 将单体应用升级为分布式系统 public class LegacySystem { public void refactor() { addFeature("分布式ID生成"); // 实现Snowflake算法 addFeature("自适应限流"); // 基于Sentinel动态规则 addFeature("可观测性体系"); // OpenTelemetry集成 } } ``` --- ### 评估结果应用建议 **1. 职业选择决策树** ```mermaid graph TD A[能否独立设计跨数据中心同步方案?] -->|是| B[主攻分布式存储/中间件开发] A -->|否| C[能否实现Raft选举算法?] C -->|是| D[选择云计算基础设施方向] C -->|否| E[专注业务系统微服务化] ``` **2. 技能缺口修复优先级** $$修复权重 = \frac{岗位需求度}{当前掌握度} \times 学习成本系数$$ **典型排序**: 1. 分布式事务(Saga/TCC) 2. 一致性协议(Raft/Paxos) 3. 服务网格(Istio流量管理) --- ### 关键问题解答 **Q1:如何验证对共识算法的真实理解?** - **深度检验法**: 1. 在白板上推导Raft领导选举的数学约束条件 2. 用Go语言实现Paxos的basic版本(200行内) 3. 解释etcd为什么选择Raft而非Paxos[^1] **Q2:分布式系统知识如何转化为项目经验?** - **低成本实践方案**: ```python # 用300行代码构建迷你分布式系统 class MiniCluster: def __init__(self): self.nodes = [Node() for _ in range(3)] self.register_raft_consensus() self.implement_sharding('consistent_hashing') self.add_failure_detection() ``` --- ### 相关问题 1. 如何设计有效的混沌工程实验来验证分布式系统健壮性? 2. 在微服务架构中如何选择服务发现机制(CP vs AP)? 3. 分布式事务的两阶段提交与三阶段提交的核心差异是什么? 4. 如何评估不同分布式锁方案(Redis RedLock vs Zookeeper)的适用场景? 5. 云原生时代下传统分布式系统设计需要做出哪些适应性改变? : 架构评估需要结合具体技术选型场景 [^2]: 效用树分析法适合验证架构决策合理性 [^3]: 性能设计需贯穿系统全生命周期 [^4]: 场景视图帮助建立完整系统认知
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值