前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >详解布隆过滤器原理,及分布式运用方法_布隆过滤器最小误差

详解布隆过滤器原理,及分布式运用方法_布隆过滤器最小误差

作者头像
全栈程序员站长
发布于 2022-11-08 07:37:30
发布于 2022-11-08 07:37:30
1.4K00
代码可运行
举报
运行总次数:0
代码可运行

1.什么是布隆过滤器

布隆过滤器是一个叫“布隆”的人提出的,本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure)。它本身是一个很长的二进制向量,特点是高效地插入和查询,可以用来确定 “某一条数据一定不存在或者可能存在一个集合中”

相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少(因为是个二进制的向量),但是缺点是其返回的结果是概率性的,而不是确切的。

2.布隆过滤器数据结构

布隆过滤器是一个 bit 向量或者说 bit 数组,长度为8的布隆过滤器,默认值都是0,就像下面这样:

如果我们要映射一个值到布隆过滤器中,需要使用多个不同的哈希函数生成多个哈希值,并将bit向量里位置 等于哈希值的元素设置为1。例如针对值 userId=10的数据 进行三个不同的哈希函数分别生成了哈希值 1、4、7,则布隆过滤器转变为:

我们现在再存一个值 userId=18的数据,如果三次哈希函数返回0、 4、6 的话,布隆过滤器图继续变为:

需要注意的是:bit向量index=4位置由于两个值的哈希函数都返回了这个 bit 位,因此它被覆盖了。现在如果想查询 userId=20 这个数据在布隆过滤器中是否存在,三次哈希函数返回了 1、5、7三个值,结果发现 bit向量index=4位置的值为 0,说明没有任何一个值映射到这个 bit 位上,因此我们可以很确定地说userId=20 这个数据不存在布隆过滤器中。

而当查询 userId=10 这个数据是否存在的话,那么三次哈希函数必然会返回 1、4、7,然后我们检查发现这三个 bit 位上的值均为 1,那么可以说 userId=10 这个数据在布隆过滤器中存在了么?答案是不一定,只能是 userId=10 这个值可能存在。为什么是这样呢?因为随着增加的数据越来越多,被置为 1 的 bit 位也会越来越多,比如某个userId=1的数据即使没有被存储过,但是万一三次哈希后返回的三个 bit 位都被其它值置位了 1 ,那么还是不能判断 userId=1 这个值在布隆过滤器中存在。

所以得出了布隆过滤器的结论:可以用来确定某一条数据一定不存在 或 者可能存在于一个集合中,不能判断一定存在。

3.如何选择哈希个数和长度

知道了Bloom Filter的原理后,那么如何选择哈希函数个数 和 布隆过滤器长度呢?很显然,布隆过滤器长度过小,所有的 bit 位很快就均为 1了,那么查询任何值都会返回“可能存在”,起不到过滤的目的。布隆过滤器的长度会直接影响误报率,布隆过滤器越长其误报率越小。

另外,哈希函数的个数也需要权衡,个数越多则布隆过滤器 所有 bit 位被置1的速度越快,且布隆过滤器的效率越低;但是如果太少的话,那我们的误报率会变高。

先来看网上的一张统计的图:k 为哈希函数个数,m 为布隆过滤器长度,n 为插入的元素个数,p 为误报率。

从上图的统计结果,可以得出几个结论:

  • (1)当布隆过滤器长度m 、插入的元素个数n一定时,哈希函数个数k越小,隆过滤器误报率p就越大。
  • (2)当布隆过滤器哈希函数个数k、插入的元素个数n一定时,布隆过滤器长度m越小,隆过滤器误报率p就越大。

那么如何选择适合业务的 k 和 m 值呢,这里有一个公式:

k 为哈希函数个数,m 为布隆过滤器长度,n 为插入的元素个数,p 为误报率。一般可以根据自己的业务场景确认出插入元素个数n 和 误报率p。关于公式是如何推导出来的,网上很多大家自行百度一下。

4.最佳实践

(1)应用场景

  • (1)黑名单:比如邮件黑名单过滤器,判断邮件地址是否在黑名单中、内网系统用IP黑白名单防止网络爬虫等。
  • (2)防止Redis缓存击穿。

(2)布隆过滤器实现

1)guava布隆过滤器

布隆过滤器实现难点的在于如何设计随机映射函数、到底进行几次hash映射,二进制向量的长度设置为多少比较好,这些开发起来都比较困难,好在Google大佬在guava已经为我们封装了布隆过滤器实现。

maven坐标

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!--guava:布隆过滤器-->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>19.0</version>
</dependency>

实现代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class BloomFilterObj {
    //预计要插入元素个数
    private static int size = 1000000;
    //期望的误判率
    private static double fpp = 0.01;
    //布隆过滤器
    private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, fpp);

    public static void main(String[] args) {
        HashMap 
        //插入数据
        for (int i = 0; i < 1000000; i++) {
            bloomFilter.put(i);
        }
        int count = 0;
        for (int i = 1000000; i < 3000000; i++) {
            if (bloomFilter.mightContain(i)) {
                count++;
                System.out.println(i + "误判了");
            }
        }
        System.out.println("总共的误判数:" + count);
    }
}

上面实例执行结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//最后几行输出
2999753误判了
2999838误判了
2999923误判了
2999928误判了
总共的误判数:20339

guava实现布隆过滤器存在一个问题:数据是存放在本地内存中,分布式系统中无法实现布隆过滤器的共享。那怎么解决这个问题呢?可以redis来实现分布式系统的布隆过滤器。

2)分布式布隆过滤器实现

用redis来实现布隆过滤器,需要我们自己根据插入元素个数n 和 误判率p来计算出bloom filter的长度m、哈希函数个数k,无疑是一个艰辛的任务,但是我们可以参考guava中的实现。guava中布隆过滤器取hash运算运算时,为了达到更离散目的,实现原理 和 HashMap的取hash运算有点类似,都是用到高位和低位运算。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
* @author: feiwang_6
* @create: 2020/6/4 22:14
* @description: Redis实现布隆过滤器
*/
public class RedisBloomFilter {
//预计插入的元素个数
static final int expectedInsertions = 1000;
//期望的误判率
static final double fpp = 0.01;
//bit数组长度
private static long bitSize;
//hash函数数量
private static int numHashFunctions;
static {
bitSize = optimalNumOfBits(expectedInsertions, fpp);
numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, bitSize);
}
public static void main(String[] args) {
//初始化Redis布隆过滤器
Jedis jedis = new Jedis("localhost", 6379);
for (int i = 0; i < 1000; i++) {
long[] indexArray = getIndexArray(String.valueOf(i));
for (long index : indexArray) {
jedis.setbit("bloom_key_name", index, true);
}
}
//测试布隆过滤器的误报率
int num = 0;
for (int i = 1000; i <= 2000; i++) {
long[] indexArray = getIndexArray(String.valueOf(i));
for (long index : indexArray) {
if (!jedis.getbit("bloom_key_name", index)) {
System.out.println(i + " 一定不存在");
num++;
break;
}
}
}
System.out.println("一定不存在的有 " + num + " 个");
}
/**
* 根据key获取布隆过滤器bitmap下标
* (guava的布隆过滤器实现源码)
*/
private static long[] getIndexArray(String key) {
//低32位
long hash1 = (int)hash(key);
//高32位
long hash2 = (int)(hash1 >>> 32);
long[] result = new long[numHashFunctions];
for (int i = 0; i < numHashFunctions; i++) {
long combinedHash = hash1 + i * hash2;
if (combinedHash < 0) {
combinedHash = ~combinedHash;
}
result[i] = combinedHash % bitSize;
}
return result;
}
/**
* hash计算
* @param key
* @return
*/
private static long hash(String key) {
return Hashing.murmur3_128().hashBytes(key.getBytes()).asLong();
}
/**
* 计算hash函数个数
* 根据上面公式hash函数个数k = (m / n) * log(2),需要考虑到避免因除法而截断小数部分
*  (guava的布隆过滤器实现源码)
* @param n
* @param m
* @return
*/
private static int optimalNumOfHashFunctions(long n, long m) {
return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
}
/**
* 根据上面公式计算布隆过滤器bit数组长度m 
*     (guava的布隆过滤器实现源码)
* @param n
* @param p
* @return
*/
private static long optimalNumOfBits(long n, double p) {
if (p == 0) {
p = Double.MIN_VALUE;
}
return (long) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
}
}

上面实例执行结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//最后几行运行结果:
1997 一定不存在
1998 一定不存在
1999 一定不存在
2000 一定不存在
一定不存在的有988

说明:在分布式系统中使用redis,一般都是和spring集成使用,由于本地没有redis集群环境,就写了一个单机手动连接测试用例,没有真正实现分布式,但这里主要讲解的是实现思路,大同小异。

最后抛一个问题:在分布式系统中,使用redis的bit来实现bloom filter时,每次请求都会去读redis,当并发量大时每次都去读严重影响性能,那么怎么进行优化?

总结:

  • (1)不同的数据结构有不同的适用场景和优缺点,需要自己仔细权衡业务需求之后妥善适用它们,布隆过滤器就是这样。
  • (2)使用布隆过滤器来加速查找和判断是否存在,那么性能很低的哈希函数不是个好选择,大神们推荐 MurmurHash、KetamaHash、Fnv 。
  • (3)分布式服务中使用布隆过滤器时,一定要给自己预留一个初始化的接口,比如swagger接口等,在后端系统上线后先初始化布隆过滤器。

2020年06月04日 晚 于北京记

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/185190.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年10月6日 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Redis实现布隆过滤器解析
    1)布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
忧愁的chafry
2022/10/30
1.4K0
Redis实现布隆过滤器解析
品味布隆过滤器 Bloom filter的设计之美
你可能没想到: RocketMQ、 Hbase 、Cassandra 、LevelDB 、RocksDB 这些知名项目中都有布隆过滤器的身影。
勇哥java实战
2023/04/14
2.3K0
品味布隆过滤器 Bloom filter的设计之美
Guava的布隆过滤器
 程序世界的算法都要在时间,资源占用甚至正确率等多种因素间进行平衡。同样的问题,所属的量级或场景不同,所用算法也会不同,其中也会涉及很多的trade-off。
程序员历小冰
2019/04/28
1.2K0
Guava的布隆过滤器
什么是布隆过滤器?如何使用?
很多人想到的是HashMap。 确实可以将值映射到 HashMap 的 Key,然后可以在 O(1) 的时间复杂度内返回结果,效率奇高。但是 HashMap 的实现也有缺点,例如存储容量占比高,考虑到负载因子的存在,通常空间是不能被用满的,而一旦你的值很多例如上亿的时候,那 HashMap 占据的内存大小就变得很可观了。
Java技术债务
2022/08/09
4.1K0
什么是布隆过滤器?如何使用?
布隆过滤器:原理与应用
这个时候,布隆过滤器(Bloom Filter)就派上了用场。 作为一种空间高效的概率型数据结构,布隆过滤器能够快速有效地检测一个元素是否属于一个集合。其应用广泛,从网络爬虫的网页去重,到数据库查询优化,乃至比特币网络的交易匹配,都离不开它的身影。
BookSea
2023/10/12
4720
什么是布隆过滤器,隆过滤器是干什么用的?
大家看下这幅图,用户可能进行了一次条件错误的查询,这时候 redis 是不存在的,按照常规流程就是去数据库找了,可是这是一次错误的条件查询,数据库当然也不会存在,也不会往 redis 里面写值,返回给用户一个空,这样的操作一次两次还好,可是次数多了还了得,我放 redis 本来就是为了挡一挡,减轻数据库的压力,现在 redis 变成了形同虚设,每次还是去数据库查找了,这个就叫做缓存穿透,相当于 redis 不存在了,被击穿了,对于这种情况很好解决,我们可以在 redis 缓存一个空字符串或者特殊字符串,比如 &&,下次我们去 redis 中查询的时候,当取到的值是空或者 &&,我们就知道这个值在数据库中是没有的,就不会在去数据库中查询。
用户2242639
2021/06/29
6070
大数据量下的集合过滤—Bloom Filter
算法背景 如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路,存储位置要么是磁盘,要么是内存。很多时候要么是以时间换空间,要么是以空间换时间。 在响应时间要求比较严格的情况下,如果我们存在内里,那么随着集合中元素的增加,我们需要的存储空间越来越大,以及检索的时间越来越长,导致内存开销太大、时间效率变低。 此时需要考虑解决的问题就是,在数据量比较大的情况下,既满足时间要求,又满足空间的要求。
欠扁的小篮子
2018/07/04
1.8K0
牛逼哄哄的布隆过滤器,到底有什么用?
本文是站在小白的角度去讨论布隆过滤器,如果你是科班出身,或者比较聪明,又或者真正想完全搞懂布隆过滤器的可以移步。
Java技术栈
2020/07/02
5250
布隆过滤器
布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
Vincent-yuan
2022/05/06
4520
布隆过滤器
好玩的布隆过滤器
本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 「“某样东西一定不存在或者可能存在”」。
chengcheng222e
2021/11/04
3740
布隆过滤器,一文总结快速掌握,你能够get多少?
假如有一个15亿用户的系统,每天有几亿用户访问系统,要如何快速判断是否为系统中的用户呢?
Java程序猿阿谷
2021/03/11
1.4K0
布隆过滤器,一文总结快速掌握,你能够get多少?
最牛一篇布隆过滤器详解
我们之前讲了Redis的缓存雪崩、穿透、击穿。在文章里我们说了解决缓存穿透的办法之一,就是布隆过滤器,但是上次并没有讲如何使用布隆过滤器。
公众号 IT老哥
2020/10/27
7.8K1
最牛一篇布隆过滤器详解
布隆过滤器
  布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
OPice
2019/10/23
6500
Redis集群实现布隆过滤器
封面为好友拍摄的照片,想查看更多微信公众号搜索:JavaBoy王皓或csdn博客搜索:TenaciousD
胖虎
2019/06/26
1.7K0
Redis集群实现布隆过滤器
基于Guava布隆过滤器的海量字符串高效去重实践
使用Google Guava库来实现基于布隆过滤器的海量字符串去重是一个很好的选择。布隆过滤器是一种空间效率极高的概率型数据结构,它利用位数组表示集合,并使用哈希函数将元素映射到位数组的某些位置。布隆过滤器可以高效地检查一个元素是否可能属于某个集合,但有一定的误报率。
公众号:码到三十五
2024/03/19
2180
基于Guava布隆过滤器的海量字符串高效去重实践
布隆过滤器你值得拥有的开发利器
在程序的世界中,布隆过滤器是程序员的一把利器,利用它可以快速地解决项目中一些比较棘手的问题。如网页 URL 去重、垃圾邮件识别、大集合中重复元素的判断和缓存穿透等问题。
阿宝哥
2019/11/29
1K0
Java|存储|Guava Bloom Filter源码剖析
Bloom Filter(布隆过滤器)以牺牲少量正确率为代价,利用较少的空间实现O(1)的查询,在LSM Tree、Cache中作为常见的读优化手段。本文结合谷歌的Guava源码介绍Bloom Filter的实现。
朝闻君
2021/11/22
7410
Java|存储|Guava Bloom Filter源码剖析
布隆过滤器原理
在空间上相对于其他数据结构,有很大优势, 20亿的数据需要 2000000000bit/8/1024/1024 = 238 M ,如果使用数组来存储,假设每个用户 ID 占用 4个字节的空间,存储20亿用户需要 2000000000byte/4/8/1024/1024 = 7600M 的空间,是布隆过滤器的32倍。
王小明_HIT
2020/08/03
7790
布隆过滤器解读(Java实现)
布隆过滤器:(布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。
一个风轻云淡
2023/12/13
4890
布隆过滤器
数据结构是个很有意思的东西,很多设计非常巧妙的数据结构能够大大降低某项操作的时间或者空间复杂度。所以我来开个专题来讲一些高级的,用途广泛的数据结构。搞数据结构专题的好处就是能够普及一些数据结构的原理和思想,第二个就是能够省下我很多考虑分享主题的时间。低级的数据结构,比如Hash,Set,链表队列之类的我就不讲了默认大家都会了。今天是第一篇,我们来讲讲布隆过滤器。
王小明_HIT
2023/07/08
1580
布隆过滤器
相关推荐
Redis实现布隆过滤器解析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验