当前位置: 首页 > news >正文

分布式 | 布隆过滤器实战指南:原理、编码实现、应用与Redisson最佳实践

文章目录

  • 介绍
  • 工作原理
    • 流传的经典名句:不存在一定不存在,存在那不一定存在。
    • 1、添加元素
    • 2、查询元素
  • 问题:如何处理hash碰撞?
    • 了解Java中HashMap处理哈希碰撞
    • 布隆过滤器如何处理hash碰撞?
    • 如何控制误报率?
    • Java实现布隆过滤器
  • 应用
    • 1. 缓存系统:防止缓存穿透
    • 2. 数据库优化:减少磁盘 I/O
    • 3. 网络爬虫:去重 URL
    • 4. 垃圾邮件过滤
    • 应用图解
  • 最佳实践:Bloom Filter
    • 1. 底层存储:Redis 的 String 或 Hash 结构
    • 2. 哈希函数:MurmurHash3
    • 3. 原子性保障:Lua 脚本
  • 小结

介绍

  • 在处理海量数据时,如何快速判断一个元素是否存在于一个巨大的集合中?
  • 如果,使用 HashSet、HashMap ,当数据量达到数亿甚至上百亿时,这些方法会消耗巨大的内存,性能急剧下降。

这时,布隆过滤器(Bloom Filter)闪亮登场。

  • 空间效率极高
  • 概率型数据结构(probabilistic data structure)

作用:

  • 用于判断一个元素是否可能在一个集合中。

工作原理

流传的经典名句:不存在一定不存在,存在那不一定存在。

  • 不允许漏报(False Negative):有就是有,一个不能少。
  • 允许误报(False Positive):狼来了 是可以有的事情。
    在这里插入图片描述

为什么会导致这种原因?

  • 先说结论(图解):
    在这里插入图片描述

前提:

  • 在布隆过滤器中
    • 预先将商品1ID和商品2ID,多次hash运算的结果,Redis的Bitmap中相应位:1、4、8 都 置1
    • 巧合的是,当商品3ID经过多次hash运算后,得到的结果,在Bitmap:1、8 置1
  • 此时,Bitmap中位置1、8,因为商品1和商品2置为1了。布隆过滤器会判断为存在,进而去数据库中查询数据。
  • 实际上,数据库中并没有商品3的数据。

布隆过滤器由两个核心组件构成:

  • 一个长度为 m 的位数组(Bit Array):初始所有位为 0。
    • redis中的 Bitmap 、Java中 BitSet
  • k 个独立的哈希函数:每个函数将输入映射到位数组的一个位置

在这里插入图片描述

1、添加元素

当向布隆过滤器中添加一个元素时:

  • 使用 k 个哈希函数对该元素进行哈希。
  • 得到 k 个位置索引。
  • 将这 k 个位置的值都设置为 1。

2、查询元素

当查询一个元素是否“可能存在”:

  • 使用相同的 k 个哈希函数计算其位置。
  • 检查这些位置的值:
    • 如果所有位置都是 1 → 返回“存在”。
    • 如果任一位置是 0 → 返回“一定不存在”。

问题:如何处理hash碰撞?

了解Java中HashMap处理哈希碰撞

哈希碰撞:

  • 当多个键值对的 hashCode() 计算出的索引位置相同时(即发生哈希碰撞),HashMap 会通过以下机制来存储 和 查找数据。
    • 开放寻址法:碰撞了,找新的位置。(打不过,就跑)
    • 拉链法:碰撞了,跟在后面。(打不过,就加入)

反正就是打不过(碰撞了),两种策略去解决。

不过,开放寻址法无法优化,当碰撞发生时, 数组 没有空间,

  • 无法添加数据。

拉链法 呢,当碰撞发生时, 数组 有无空间无所谓。

  • 反正我碰撞了,我就要加入,具体加哪里呢。你拉出个链表,将碰撞的数据放进去。有点子嚣张。

优化:

  • 在 JDK 8 之后,HashMap 处理哈希碰撞(Hash Collision)的核心策略是:
    • 数组 + 链表 + 红黑树(自平衡的二叉查找树)。
    • 阈值(边界值)> 8,且数组长度大于64,才会将链表转为红黑树,高效查询。
    • 还采用:尾插入 的方法,不用移动其他节点的指针,相当方便。主要还是向性能看齐。

具体描述,如下图所示:
在这里插入图片描述

布隆过滤器如何处理hash碰撞?

本来,布隆过滤器是为了节省空间做大事。如果它要是使用以上的方法,那可就出不了名了。

那它要使用什么办法呢?

  • 布隆过滤器一想:既然hash计算一次它容易碰撞重复,那我就多计算几次,最后在将它们的结果汇总判断。
  • 多个hash方法计算出多个位置,汇总判断是否存在
    • 多个位置都是1,则存在。
    • 有一个为0,则不存在。

如何控制误报率?

  • 既然一定会产生误报,那如何控制误报率呢?

布隆过滤器的性能取决于三个参数:

n:预计元素数量
m:位数组长度
k:哈希函数数量

在给定 n 和期望误报率 p 时,可通过公式计算最优参数:(知道就可)

在这里插入图片描述

Java实现布隆过滤器

import java.util.BitSet;
import java.security.MessageDigest;public class BloomFilter {private BitSet bitSet; // 位数组,存储0和1private int size; // 位数组的大小private int[] seeds = {3, 5, 7, 11, 13}; // 5 个哈希函数的“种子”// 创建一个长度为 size 的 BitSet,初始所有位都是 false(即 0)。public BloomFilter(int size) {this.size = size;this.bitSet = new BitSet(size);}// 添加public void add(String value) {// 遍历seeds种子,不同种子产生不同哈希结果for (int seed : seeds) {HashFunction hash = new HashFunction(size, seed);bitSet.set(hash.hash(value), true);}}// 判断一个字符串是否“可能”存在于集合中。public boolean mightContain(String value) {for (int seed : seeds) {HashFunction hash = new HashFunction(size, seed);if (!bitSet.get(hash.hash(value))) {return false; // 一定不存在}}return true; // 可能存在}// 实现一个简单的哈希函数,将字符串映射到位数组的一个索引上。static class HashFunction {private int cap;private int seed;public HashFunction(int cap, int seed) {this.cap = cap;this.seed = seed;}// public int hash(String value) {int result = 0;for (int i = 0; i < value.length(); i++) {// 种子 * 每次遍历的返回值 + 字符串的字符位置result = seed * result + value.charAt(i);}return (cap - 1) & result;}}
}

应用

1. 缓存系统:防止缓存穿透

  • 在 Redis 等缓存系统中,如果黑客频繁查询不存在的 key,会导致大量请求打到数据库。

解决方案:优秀实现Redisson中的 Bloom Filter

  • 将所有合法 key 加入布隆过滤器。
  • 查询前,先过布隆过滤器:
    • 若返回“不存在” → 直接返回,不查数据库。
    • 若返回“可能存在” → 查缓存 → 查数据库。

2. 数据库优化:减少磁盘 I/O

  • 数据库(如 LevelDB、Cassandra)用布隆过滤器判断某个 key 是否可能存在于某个 SSTable 文件中,避免不必要的磁盘读取。

3. 网络爬虫:去重 URL

  • 爬虫在抓取网页时,用布隆过滤器记录已抓取的 URL,避免重复抓取,节省带宽和资源。

4. 垃圾邮件过滤

  • 将已知的垃圾邮件地址加入布隆过滤器,快速判断新邮件是否可能是垃圾邮件。

应用图解

在这里插入图片描述

最佳实践:Bloom Filter

Redisson:基于 Redis 的 Java 客户端

  • 不仅封装了 Redis 的基本操作,还提供了许多高级分布式数据结构和工具,其中就包括 分布式布隆过滤器(Bloom Filter)

1. 底层存储:Redis 的 String 或 Hash 结构

Redisson 使用 Redis 的 位图(Bitmap) 功能来模拟布隆过滤器的位数组。

每个布隆过滤器对应一个 Redis Key。
这个 Key 的值是一个大的位数组(bit array),每一位代表一个“槽”。
Redis 的 SETBIT 和 GETBIT 命令用于设置和读取位。

2. 哈希函数:MurmurHash3

Redisson 使用 MurmurHash3 算法作为基础哈希函数。

只需一次计算,即可生成多个不同的哈希值(通过种子偏移)。
高效且分布均匀,适合布隆过滤器场景。

3. 原子性保障:Lua 脚本

所有操作(添加、查询)都通过 Redis 的 Lua 脚本执行,确保原子性。

// 可抽取为配置文件
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);// 获取布隆过滤器实例
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("user:exists");// 初始化:预计100万个用户,允许3%误判率
bloomFilter.tryInit(100000L, 0.03);// 添加元素
bloomFilter.add("user123");
bloomFilter.add("user456");// 判断是否存在
boolean mightExist = bloomFilter.contains("user123"); // true
boolean definitelyNotExist = bloomFilter.contains("user999"); // falseredisson.shutdown();

小结

  • 布隆过滤器是一种用空间换时间、并接受一定错误率的巧妙设计。
  • 它不能告诉你“一定存在”,但可以非常高效地告诉你“一定不存在”。

各位再见!这里是 鳄鱼杆的空间,钓……鳄鱼的杆儿!

期待下次再会!

愿你的每一次垂钓之旅都能满载而归。

在这里插入图片描述

http://www.dtcms.com/a/393916.html

相关文章:

  • STM32的VSCode下开发环境搭建
  • Rsync+sersync实现数据实时同步
  • HttpServletRequest/Response/请求转发/响应重定向
  • 数据结构(2) —— 双向链表、循环链表与内核链表
  • 告别传统打版:用CLO 3D联动Substance,打造超写实数字服装
  • Linux | i.MX6ULL Sqlite3 移植和使用(第二十三章)
  • SpringBoot整合Smart Doc
  • 部署dataxweb
  • C#练习题——双向链表的创建,添加和删除
  • 大厂思维与“小快轻准”产品的矛盾
  • C++二进制转八进制
  • STL容器 --- 模拟实现 list
  • Java LTS版本进化秀:从8到21的欢乐升级之旅
  • yolo转tensorrt nano
  • paimon实时数据湖教程-分桶详解
  • kafka集群部署
  • Windows系统安装OpenSSL库最新版方法
  • 因果推断:关于工具变量的案例分析
  • 字节面试题:激活函数选择对模型梯度传播的影响
  • 5.Spring AI Alibaba
  • 如何优化Java并发编程以提高性能?
  • 【重量上下限报警灯红黄绿】2022-12-13
  • Node.js后端学习笔记:Express+MySQL
  • Ubuntu24.04 安装 禅道
  • StandardScaler,MinMaxScaler 学习
  • vscode+ssh连接server
  • 一文快速入门 HTTP 和 WebSocket 概念
  • Vue.js 项目创建指南
  • 核心策略、高级技巧、细节处理和心理
  • 算法优化的艺术:深入理解 Pow(x, n) 及其背后的思考