SpringBoot +Redis/Guava 实现布隆过滤器

2 篇文章 0 订阅
1 篇文章 0 订阅

一、概念

布隆过滤器(Bloom Filter)是一种空间效率极高的概率型数据结构,用于判断一个元素是否在一个集合中。它使用位数组(bit array)和一系列哈希函数(hash functions)来减少存储空间。布隆过滤器能够告诉你某个元素“可能”在集合中,或者“一定”不在集合中。但是,它不能告诉你某个元素“一定”在集合中,因为可能存在哈希冲突。

包含以下基本概念:

1.位数组:布隆过滤器使用一个位数组(bit array)来存储信息。这个数组中的所有位都初始化为0。

2.哈希函数:布隆过滤器使用多个哈希函数(通常为k个哈希函数,其中k是一个较小的整数)来计算输入元素的哈希值。每个哈希函数都会将输入元素映射到位数组的一个位置。

3.添加元素:当要添加一个元素到布隆过滤器时,使用所有的哈希函数计算该元素的哈希值,并将位数组中对应的位设置为1。

4.查询元素:当要查询一个元素是否在布隆过滤器中时,使用相同的哈希函数计算该元素的哈希值,并检查位数组中对应的位是否都为1。如果所有的位都为1,则该元素“可能”在集合中;如果有任何一个位为0,则该元素“一定”不在集合中。总之,不存在的一定不存在,存在的可能存在(false if always false, true maybe not true)。

5.误判率:布隆过滤器判断一个元素在集合中,但实际上该元素不在集合中的概率称为误判率(假阳性率)。产生误判率的原因主要是哈希碰撞产生的。

二、原理

1、数据结构

布隆过滤器是由一个固定大小的二进制向量或者位图(bitmap)和一系列映射函数组成的。

对于长度为n的位数集合,初始状态时,所有的位置都被设置为0。如图:

位数组中的每个元素只占用1bit,且数组中的元素只能为0或1。如申请1000w个元素的位数组,只占用 10000000 Bit /8 = 1250000 Byte(B) = 125000B / 1024 KB ≈ 1220KB = 1220KB / 1024 ≈ 1.19M 的空间。从上面的计算来看,布隆过滤器位数组占用很少空间。

2、添加元素

1.使用过滤器中的hash函数对元素值计算,得到取模后的值(多少hash函数 多少hash值)。

2.根据hash值,在位数组中将下标对应的值设置为1。

添加元素 “baidu”,hash值为1、5、7,如下图:

再添加一个元素“xinlang”,hash值为 3、5、8 如下图:

需注意,下标5这个bit位,因两个元素的hash后都返回了5这个hash值,因此baidu的5bit位被xinlang的覆盖。

3、查询元素

1.对元素进行添加时相同的hash函数计算。

2.得到hash值后,判断位数组中每个bit位上是否都为1,若都为1,则元素存在过滤器中。若有一个不为1,则该元素不在过滤器中。

如元素“cunzai”是否存在,hash后返回 1、5、8三个值,如下图:

三个hash值对应的bit位都为1,则“cunzai”可能存在。

为什么是可能存在,而不是一定存在呢,因hash函数是散列函数,本身会有碰撞的情况发生。可产生以下几种:

情况1:一个字符串可能是 “chongtu” 经过相同的三个映射函数运算得到的三个点跟 “xinlang” 是一样的,这种情况下我们就说出现了误判。

情况2: “chongtu” 经过运算得到三个点位上的 1 是两个不同的变量经过运算后得到的,这也不能证明字符串 “chongtu” 是一定存在的。

如下图:

根据上面的情况。不同字符串可能hash后值一样,导致bit位值覆盖。可以通过适当增加位数组大小和hash函数数量降低碰撞概率,从而降低误判率。

过滤器判断元素存在,小概率存在误判。判断元素不存在,则一定不存在。

4、删除元素

过滤器无法删除元素,因存在bit位值覆盖情况,若是删除元素的话,会导致被覆盖的bit位值变成0。那其他元素在这个bit位上值也是0。判断其他元素时 也会显示不存在。就会出现误判情况。

5、位数组大小、容错率、hash函数个数、元素数关系

字母说明:

n:需要过滤的元素数(Number of items in the filter)

m:过滤器的位数组大小(Number of bits in the filter)

p:假阳性概率/误判容错率/误判概率(Probability of false positives, fraction between 0 and 1 or a number indicating 1-in-p)

k:hash函数数量(Number of hash functions)

计算公式:

n = ceil(m / (-k / log(1 - exp(log(p) / k))))

p = pow(1 - exp(-k / (m / n)), k)

m = ceil((n * log(p)) / log(1 / pow(2, log(2))));

k = round((m / n) * log(2));

计算地址:Bloom filter calculator

三、优点

1. 空间效率高:只需一个位数组和若干哈希函数,不存储数据本身,仅存储hash结果取模运算后的位标记。因此空间占有率很低。

2. 时间效率高:添加和查询元素的平均时间复杂度为O(1),及常数时间复杂度。

3. 支持海量数据场景下高效判断元素是否存在。

四、缺点

1. 不存储数据本身,只能添加无法进行删除,因为删除数据会导致误判率增加。

2. 由于存在hash碰撞,存在误判的可能性,可能将不存在的数据误判为存在,但误判只会给出“可能存在”判断。

3. 容器快满时,hash碰撞的概率会变大,插入、查询的错误率也会增加。

4. 由于错误率影响hash函数的个数,当hash函数越多,每次插入、查询需做的hash操作越多,会增加CPU的计算。

五、使用场景

1. 缓存击穿:一般数据存储在缓存中,不存在查询数据库。若一大批不存在数据过来,会导致缓存大量击穿,造成雪崩效应,同时也会使数据库压力过大,出现宕机。可以通过布隆过滤器做缓存索引,存在查询缓存,没查到再查询数据库。不存在,直接返回。但会有一定误判。

2. 网页爬虫:通过过滤器对已爬过的URL进行存储,再进行下一次爬取时,可以判断URL是否爬取过。

3. 恶意请求攻击:WEB请求拦截,如相同请求或IP拦截,防止重复攻击。将请求特征如IP、URL模式、参数等放到布隆过滤器,若所有对应为都被设置过,那该请求为恶意的,需进行拦截或下一步处理。因存在误判情况,可通过增加位数和hash函数的数量来降低误判率,但会增加内存空间和CPU计算成本。也可对误判的通过正则进行二次验证。

4. 黑白名单:针对对不同用户是否存在白名单或黑名单,虽有一定误判,但能在一定程度上能快速实现处理判断。

六、java实现

1、java+guava实现

1.1、依赖
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre</version>
</dependency>
1.2、demo测试
	//创建一个存储100w数据的布隆过滤器,用不存在的一定量数据测试误判率
	public class Test{
		
		public void BloomTest(){
			//开始时间
			long startTime = System.currentTimeMillis();
			//初始化误判次数
			BigDecimal count = new BigDecimal("0");
			//百分比换算
			BigDecimal mult = new BigDecimal("100");
			//测试数据长度 用于计算误判率
			BigDecimal testCount = new BigDecimal("100000");
			//用于计数的常量
			BigDecimal one = new BigDecimal("1");
			
			//第一个参数为数据类型,第二个为数组长度,第三个为误判率
			BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), 1000000L, 0.01);
			
			for(int i = 1; i <= 1000000; i++ ){
				bloomFilter.put(i);
			}
			
			//测试10w个不存在的数据
			for(int i = 2000000; i<= 2100000; i++){
				boolean mightContain = bloomFilter.mightContain(i);
				if(mightContain){
					count = count.add(one);
				}
			}
			System.out.println("总耗时:" + (System.currentTimeMillis() - startTime) + "ms");
			System.out.println("误判个数:" + count);
			System.out.println("误判率:" + (count.divide(testCount).multiply(mult)) + "%");		
		}	
	}

	运行结果如下:
	总耗时:257ms
	误判个数:1004
	误判率:1.00400%

误判个数为1004,符合我们设定的0.01的误判率。

注意:不是误判率越小越好,设置的越小 进行的哈希次数越多,哈希函数执行次数越多耗时大。

所以根据业务需要,设置合理的误判率。就像hashMap负载因子为0.75一样,为1哈希冲突验证。小于0.5冲突变小了,但空间利用率也下降了。

1.3、项目使用
	/**
	* 通过实现CommandLineRunner接口 让实现类在项目(容器)启动后加载,在run方法中加载
	*  或通过 @PostConstruct注解方式进行初始化加载  @PostConstruct注解的方法,会在构造方法之后执行;
	* 加载顺序为:Constructor > @Autowired > @PostConstruct > 静态方法;
	
	*/
	
	@configuration
	@Log4j2
	public class BloomInit implements CommandLineRunner{
		
		@Resource	
		private ActivityMapper activityMapper;
		
		@Override
		public void run(String... args) throws Exception{
			this.bloomInit();
		}
		
		/**
		* 初始化布隆过滤器
		*/
		@Bean
		public BloomFilter bloomInit(){
			// 初始化布隆过滤器,设置数据类型,数组长度和误差值
	    BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), 1000000L, 0.01);
	    // 获取要装入过滤器的数据
	    List<ActivityInfo> activityInfos = activityMapper.getSkillActivityGoods();
	    //将活动信息装载到过滤器
	    for(ActivityInfo activity: activityInfos){
	    	bloomFilter.put(Constants.SKLL + activity.getActivityId());
	    }
	    log.info("完成布隆过滤器装载");
	    return bloomFilter;
		}
	}
	
	/**
	* 在获取活动信息的地方加一层布隆过滤器,先从过滤器中获取值,若存在则放行,不存在直接返回,
	* 有效解决了请求直接击穿redis,直接访问数据库造成不必要的压力。
	* 
	*/
	public class activityService{
		
		@Resource
		private BloomFilter bloomFilter;
		
		@Resource
		private RedisTemplate redisTemplate;
		
		@Resource	
		private ActivityMapper activityMapper;
		
		public ActivityInfo getActivityInfo(String activityId){
			boolean contains = bloomFilter.mightContains(Constants.SKLL + activityId);
			if(!contains){
				return null;
			}
			// 从redis 获取
			String activity = redisTemplate.opsForValue().get(Constants.SKLL + activityId);
			if(activity == null){
				 // 从数据库中获取
	    	ActivityInfo activityInfo = activityMapper.getSkillActivityByActivityId(activityId);
	    	redisTemplate.opsForValue().set(Constants.SKLL + activityId, JSON.toString(activityInfo), 24, TimeUnti.HOURS);
	    	return activityInfo;
			}
			return JSONObject.parseObject(activity, ActivityInfo.class);
		}
	}

2、java+redis实现

2.1、依赖
<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson-spring-boot-starter</artifactId>
   <version>3.13.1</version>
</dependency>
2.2、demo测试
public class Test{
    @Resource
    private RedissonClient redisson;
			
	/**
	* 测试创建布隆过滤器
	*/
	public void testRedisson(){
	    //创建一个 ceshi_name 的布隆过滤器
	    RBloomFilter<String> bloomFilter =  redisson.getBloomFilter("ceshi_name");	
	    //设置 过滤器估算Bit位数为 1000,容错率为0.03,通过初始化方法会计算bit位的总位数以及0.03容错率下需要的hash函数个数。
	    bloomFilter.tryInit(1000, 0.03);
	    for(int i = 0 ; i< 1000; i++){
		    bloomFilter.add("name" + i);
	    }
	    System.out.println("name 1 是否存在:" + bloomFilter.contains("name" + 1));
	    System.out.println("xiaoming 是否存在:" + bloomFilter.contains("xiaoming"));
	    System.out.println("预计插入数量:" + bloomFilter.getExpectedInsertions());
	    System.out.println("容错率:" + bloomFilter.getFalseProbabilty());
	    System.out.println("hash函数个数:" + bloomFilter.getHashInterations());
	    System.out.println("插入对象个数:" + bloomFilter.count());
	}
}

运行结果如下:
name 1 是否存在:true
xiaoming 是否存在:false
预计插入数量:10000
容错率:0.03
hash函数个数:5
插入对象个数:10000

  • 22
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
布隆过滤器(Bloom Filter)是一种重要的数据结构,它用于快速判断一个元素是否存在于一个集合中。布隆过滤器的核心思想是通过一系列哈希函数来对元素进行多次哈希,然后将得到的哈希值映射到一个位数组中,并将对应的位置设为1。当需要判断一个元素是否存在时,同样对其进行多次哈希,检查对应位数组的值是否都为1,若都为1则可以确定元素可能存在;若存在一个0,则可以确定元素一定不存在。因此,布隆过滤器是一种基于概率的数据结构,可以高效地进行查找。 然而,布隆过滤器也存在一些问题。首先,由于多个不同的元素可能会哈希到相同的位上,因此在查询时可能出现误判,即判断一个元素存在时实际上并不存在。这种误判是由于多个元素共享了某一位的原因导致的。其次,布隆过滤器的特性决定了它无法支持元素的删除操作,因为删除一个元素可能会影响其他元素的判断结果,从而增加误判率。 要注意的是,计数布隆过滤器(Counting Bloom Filter)提供了一种实现删除操作的可能性,但并不能保证在后续查询时该值一定返回不存在。因此,不能说计数布隆过滤器支持删除,而是说计数布隆过滤器提供了实现删除的可能。 [3<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [【海量数据处理】布隆过滤器BloomFilter](https://blog.csdn.net/qq_43727529/article/details/127180864)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *3* [Java --- redis7之布隆过滤器BloomFilter](https://blog.csdn.net/qq_46093575/article/details/130613434)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值