Bitmap,布隆过滤器初步了解
创始人
2024-03-28 01:24:07

布隆过滤器使用教程

文章目录

  • 布隆过滤器使用教程
    • 1.背景
    • 2.什么是Bitmap
    • 3.布隆过滤器
      • 3.1 什么是布隆过滤器
      • 3.2 布隆过滤器的作用
      • 3.3 布隆过滤器的基本原理
    • 4.布隆过滤器的实现Guava和Redisson
      • 4.1 实现思路
      • 4.2 SpringBoot实现这些操作Bitmap,guava,redisson布隆过滤器

1.背景

最近公司对所有请求响应的数据进行推送kafka到大数据,然后落入数仓进行数据分析, 但是要解决请求重复推送的问题,可以容忍重复存在但是不能太多,所以就想到了bitmap和已经成型的布隆过滤器

2.什么是Bitmap

操作String数据结构的key所存储的字符串指定偏移量上的,返回原位置的值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YXQ8sGx6-1670483948742)(D:\学习乐园\文档总结\杂记\picture\bitmap.png)]
优点

  • 节省空间:通过一个bit位来表示某个元素对应的值或者状态,其中key就是对应元素的值。实际上8个bit可以组成一个Byte,所以是及其节省空间的。
  • 效率高:setbitgetbit的时间复杂度都是O(1),其他位运算效率也高。

缺点

  • 本质上只有01的区别,所以用做业务数据记录,就不需要在意value的值。

使用场景

  1. 可作为简单的布尔过滤器来判断用户是否执行过某些操作;

  2. 可以计算用户日活、月活、留存率的统计;

  3. 可以统计用户在线状态和人数;

3.布隆过滤器

3.1 什么是布隆过滤器

介绍布隆过滤器之前,先介绍一下哈希函数,我们在Java中的HashMap,HashSet也接触过hashcode()这个函数。

哈希函数指将哈希表中元素的关键键值通过一定的函数关系映射为元素存储位置的函数。

哈希函数的特点

  • 如果根据同一个哈希函数得到的哈希值不同,那么这两个哈希值的原始输入值肯定不同
  • 如果根据同一个哈希函数得到的两个哈希值相等,两个哈希值的原始输入值有可能相等,有可能不相等

布隆过滤器实际上是一个非常长的二进制向量(bitmap)和一系列随机哈希函数。

布隆过滤器(英语:Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。 Bloom Filter(BF)是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集合。

它是一个判断元素是否存在集合的快速的概率算法。Bloom Filter有可能会出现错误判断,但不会漏掉判断。也就是Bloom Filter判断元素不再集合,那肯定不在。如果判断元素存在集合中,有一定的概率判断错误。因此,Bloom Filter”不适合那些“零错误的应用场合。而在能容忍低错误率的应用场合下,Bloom Filter比其他常见的算法(如hash,折半查找)极大节省了空间。

优点

  • 布隆过滤器存储空间和插入/查询时间都是常数
  • Hash函数相互之间没有关系,方便由硬件并行实现
  • 布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势
  • 布隆过滤器可以表示全集,其它任何数据结构都不能

缺点

  • 有一定的误判率

    常见的补救办法是建立一个小的白名单,存储那些可能被误判的元素。但是如果元素数量太少,使用散列表足矣。

  • 一般情况下不能从布隆过滤器中删除元素。

    我们很容易想到把位列阵变成整数数组,每插入一个元素相应的计数器加1, 这样删除元素时将计数器减掉就可以了。然而要保证安全的删除元素并非如此简单。首先我们必须保证删除的元素的确在布隆过滤器里面,这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题。

3.2 布隆过滤器的作用

  • 解决Redis缓存穿透
  • 邮件过滤,使用布隆过滤器来做邮件黑名单过滤
  • 解决视频推荐过的不再推荐

3.3 布隆过滤器的基本原理

  • 布隆过滤器的原理是,当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。
  • 检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。
  • Bloom Filter跟单哈希函数Bit-Map不同之处在于:Bloom Filter使用了k个哈希函数,每个字符串跟k个bit对应。从而降低了冲突的概率。
    在这里插入图片描述
    步骤
  1. 首先,建立一个二进制向量,并将所有位设置为0。
  2. 然后,选定K个散列函数,用于对元素进行K次散列,计算向量的位下标。
  3. 添加元素:当添加一个元素到集合中时,通过K个散列函数分别作用于元素,生成K个值作为下标,并将向量的相应位设置为1。
  4. 检查元素:如果要检查一个元素是否存在集合中,用同样的散列方法,生成K个下标,并检查向量的相应位是否全部是1。如果全为1,则该元素很可能在集合中;否则(只要有1个或以上的位为0),该元素肯定不在集合中。

4.布隆过滤器的实现Guava和Redisson

4.1 实现思路

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KzrbZgG3-1670483948745)(D:\学习乐园\文档总结\杂记\picture\布隆过滤器设计流程.png)]

4.2 SpringBoot实现这些操作Bitmap,guava,redisson布隆过滤器

引入依赖


4.0.0org.springframework.bootspring-boot-starter-parent2.2.2.RELEASE com.exampleRedisBitMapProject0.0.1-SNAPSHOTRedisBitMapProjectRedisBitMapProject1.8org.springframework.bootspring-boot-starterorg.springframework.bootspring-boot-starter-testtestorg.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-data-redisorg.apache.commonscommons-pool2com.google.guavaguava23.0org.projectlomboklombokcom.alibabafastjson2.0.20org.redissonredisson-spring-boot-starter3.18.0org.springframework.bootspring-boot-maven-plugin

yml配置

server:port: 20010#redis????
spring:redis:host: localhost             #redis??port: 6379                  #redis??database: 0                 #redis??(0-15,???0)timeout: 1000               #redis??????lettuce:                    #??lettuce???pool:max-active: 20          #????????(??????????)max-wait: -1            #???????????(??????????)min-idle: 0             #???????????max-idle: 10            #???????????

guava布隆过滤器配置类

package com.example.bitmap.config;import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.nio.charset.Charset;/*** @Author Emperor Kang* @ClassName BloomFilterConfig* @Description guava布隆过滤器配置类* @Date 2022/12/7 15:37* @Version 1.0* @Motto 让营地比你来时更干净*/
@Configuration
public class GuavaBloomFilterConfig {@Beanpublic BloomFilter guavaBloomFilter(){//expectedInsertions:容量期望大小//fpp:期望的误判率,期望的误判率越低,布隆过滤器计算时间越长BloomFilter bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.forName("UTF-8")), 1000, 0.000001);return bloomFilter;}}

redisson布隆过滤器配置类

package com.example.bitmap.config;import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @Author Emperor Kang* @ClassName RedissonBloomFilterConfig* @Description RedissonBloomFilterConfig* @Date 2022/12/7 17:39* @Version 1.0* @Motto 让营地比你来时更干净*/
@Configuration
public class RedissonBloomFilterConfig {@Autowiredprivate RedissonClient redissonClient;@Beanpublic RBloomFilter redissonBloomFilter(){RBloomFilter redissonBloomFilter = redissonClient.getBloomFilter("redissonBloomFilter");//初始化布隆过滤器:预计元素为1000L,误差率为0.00001redissonBloomFilter.tryInit(1000L,0.00001);return redissonBloomFilter;}
}

redisson配置

package com.example.bitmap.config;import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;/*** @Author Emperor Kang* @ClassName RedissonConfig* @Description redisson配置类* @Date 2022/12/7 17:19* @Version 1.0* @Motto 让营地比你来时更干净*/
@Component
@Configuration
@Slf4j
public class RedissonConfig {@Value("${spring.redis.host}")private String host;@Value("${spring.redis.port}")private String port;@Beanpublic RedissonClient redissonClient(){Config config = new Config();config.useSingleServer().setAddress("redis://"+host+":"+port);return Redisson.create(config);}
}

redis配置类

package com.example.bitmap.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;@Configuration
public class RedisConfig {@Autowiredprivate StringRedisTemplate redisTemplate;/*** 实例化RedisAtomicLong* @return*/@Beanpublic RedisAtomicLong redisAtomicLong() {RedisAtomicLong redisAtomicLong = new RedisAtomicLong("bitMapIncreaseKey", redisTemplate.getConnectionFactory());return redisAtomicLong;}}

utils

package com.example.bitmap.utils;import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Component;/*** @Author Emperor Kang* @ClassName RedisUtil* @Description redis工具类* @Date 2022/12/7 10:19* @Version 1.0* @Motto 让营地比你来时更干净*/
@Component
@Slf4j
public class RedisUtil {@Autowiredprivate RedisAtomicLong redisAtomicLong;/*** 生成自增的偏移量序号* @return*/public Long bitMapOffsetSequence(){long sequence = 0L;try {if(redisAtomicLong.get() == 0){redisAtomicLong.getAndSet(0L);}sequence = redisAtomicLong.incrementAndGet();log.info("当前序号为:{}",sequence);} catch (Exception e) {log.error("RedisUtil.bitMapOffsetSequence生成自增偏移量时发生异常",e);throw e;}return sequence;}}

controller

package com.example.bitmap.controller;import com.alibaba.fastjson.JSON;
import com.example.bitmap.vo.BloomFilterRequestVo;
import com.google.common.hash.Funnels;
import com.google.common.hash.Hashing;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.nio.charset.Charset;/*** @Author Emperor Kang* @ClassName BitMapController* @Description TODO* @Date 2022/12/8 10:36* @Version 1.0* @Motto 让营地比你来时更干净*/
@RestController
@RequestMapping("/bitmap")
@Slf4j
public class BitMapController {@Autowiredprivate StringRedisTemplate redisTemplate;/*** bitmap去重操作* @param requestVo* @return*/@RequestMapping("/distinct")public Object distinctData(@RequestBody BloomFilterRequestVo requestVo){log.info("bitmap-requestVo:{}", JSON.toJSONString(requestVo));String key = buildKey(requestVo);long offset = hash(key);log.info("当前的offset:{}",offset);//判断是否可能存在该keyif(redisTemplate.opsForValue().getBit(key,offset)){log.info("该key:{}可能已经存在,所以不再推送kafka",key);}else{log.info("该key:{}不存在,开始推送kafka,将该key写入布隆过滤器",key);redisTemplate.opsForValue().setBit(key,offset,true);}return true;}private String buildKey(BloomFilterRequestVo requestVo) {return requestVo.getApplySeq() + "_" + requestVo.getInterfaceCode();}/*** guava依赖获取hash值。*/private long hash(String key) {Charset charset = Charset.forName("UTF-8");return Math.abs(Hashing.murmur3_128().hashObject(key, Funnels.stringFunnel(charset)).asInt());}}
package com.example.bitmap.controller;import com.alibaba.fastjson.JSON;
import com.example.bitmap.vo.BloomFilterRequestVo;
import com.google.common.hash.BloomFilter;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** @Author Emperor Kang* @ClassName BloomFilterController* @Description 布隆过滤器* @Date 2022/12/7 15:44* @Version 1.0* @Motto 让营地比你来时更干净*/
@RestController
@RequestMapping("bloomFilter")
@Slf4j
public class BloomFilterController {@Autowired@Qualifier("guavaBloomFilter")private BloomFilter guavaBloomFilter;@Autowired@Qualifier("redissonBloomFilter")private RBloomFilter redissonBloomFilter;/*** guava布隆过滤器* @param requestVo* @return*/@PostMapping("/guava")public Object guavaBloomFilter(@RequestBody BloomFilterRequestVo requestVo){log.info("guava-requestVo:{}", JSON.toJSONString(requestVo));String key = buildKey(requestVo);//判断是否可能存在该keyif(guavaBloomFilter.mightContain(key)){log.info("该key:{}可能已经存在,所以不再推送kafka",key);}else{log.info("该key:{}不存在,开始推送kafka,将该key写入布隆过滤器",key);guavaBloomFilter.put(key);}return true;}/*** guava布隆过滤器* @param requestVo* @return*/@PostMapping("/redisson")public Object redissonBloomFilter(@RequestBody BloomFilterRequestVo requestVo){log.info("redisson-requestVo:{}", JSON.toJSONString(requestVo));String key = buildKey(requestVo);//判断是否可能存在该keyif(redissonBloomFilter.contains(key)){log.info("该key:{}可能已经存在,所以不再推送kafka",key);}else{log.info("该key:{}不存在,开始推送kafka,将该key写入布隆过滤器",key);redissonBloomFilter.add(key);}return true;}private String buildKey(BloomFilterRequestVo requestVo) {return requestVo.getApplySeq() + "_" + requestVo.getInterfaceCode();}
}
package com.example.bitmap.controller;import com.example.bitmap.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;/*** @Author Emperor Kang* @ClassName RedisController* @Description TODO* @Date 2022/12/7 10:21* @Version 1.0* @Motto 让营地比你来时更干净*/
@RestController
@RequestMapping("/redis")
@Slf4j
public class RedisController {public static final List list = Collections.synchronizedList(new ArrayList<>());@Autowiredprivate RedisUtil redisUtil;/*** 自增主键ID* @return*/@RequestMapping("incrId")public Object generateIncrId(){Long offsetSequence = redisUtil.bitMapOffsetSequence();list.add(offsetSequence);return true;}/*** 查看生成的主键ID是否有重复*/@RequestMapping("/distinct")public void getDis(){log.info("当前集合size:{}",list.size());List collect = list.stream().collect(Collectors.toMap(e -> e, e -> 1, Integer::sum)).entrySet().stream().filter(e -> e.getValue() > 1).map(Map.Entry::getKey).collect(Collectors.toList());log.info("重复的数据为{}",collect);}
}

jmeter进行调起测试
在这里插入图片描述
结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DJi8JObQ-1670483948746)(D:\学习乐园\文档总结\杂记\picture\image-20221208151649700.png)]

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...