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

布隆过滤器+缓存穿透

完全解读布隆过滤器

一.简介

布隆过滤器(Bloom Filter)是由布隆于1970年提出的一种空间效率极高的概率型数据结构,可以快速判断一个元素是否属于某个集合。

它的核心优势在于:高效、低内存、支持大数据量场景,因此在反垃圾邮件、爬虫去重、缓存穿透防护等领域被广泛使用。

二.优缺点

✅ 优点

  • 存储与查询时间复杂度为 O(k)(k为哈希函数数量)

  • 节省空间,只存储 hash 映射结果,而不保存原始数据

  • 支持大规模数据快速判断

❌ 缺点

  • 存在误判:可能会误判“存在”,但不会误判“不存在”

  • 无法删除元素:由于多个元素可能映射到同一个位置,删除会影响其他数据

  • 误判率随数据量增加而上升

三.工作原理

  1. 数据结构:布隆过滤器底层是一个长度为 m二进制位数组(初始全为 0),配合 k独立哈希函数

  2. 添加元素流程:

    • 对要添加的元素,使用多个哈希函数计算出多个下标

    • 将这些下标位置的值置为 1

  3. 查询元素流程:

    • 对查询元素使用相同哈希函数生成多个下标

    • 如果所有下标位置值都为 1,则判断“可能存在”

    • 如果有任意一位为 0,则判断“一定不存在“

  4. 流程图:

 

四.特点总结

  • 判断如果某个元素存在,由于存在误判,这个元素不一定是存在的

  • 判断如果某个元素不存在,那这个元素一定不存在

五.案例

1.添加依赖

  • 如果是不依赖Redis而使用布隆过滤器:

<dependency>
     <groupId>com.google.guava</groupId>
     <artifactId>guava</artifactId>
     <version>30.1-jre</version>
 </dependency>

  • 依赖Redis,使用Redisson中的布隆过滤器:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.17.4</version>
</dependency>

2. 结构配置

  • 不依赖Redis而使用布隆过滤器:
@Configurationpublic class BloomFilterConfig {@Beanpublic BloomFilter<String> bloomFilter() {//100000:布隆过滤器容量0.01为误判率return BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()),100000, 0.01);}}
  • 依赖Redis,使用Redisson中的布隆过滤器:

        属性配置

@Data
@ConfigurationProperties(prefix = BloomFilterProperties.PREFIX)
public class BloomFilterProperties {/*** 配置文件前缀 bloom-filter*/public static final String PREFIX = "bloom-filter";/*** 布隆过滤器名字*/private String name;/*** 布隆过滤器的容量*/private Long expectedInsertions = 20000L;/*** 布隆过滤器碰撞率*/private Double falseProbability = 0.01D;
}

        布隆过滤器操作

 /*** 布隆过滤器操作工具类* 用于对 Redisson 提供的 RBloomFilter 进行封装,简化使用流程。*/
public class BloomFilterHandler {/*** 布隆过滤器实例,用于存储和判断数据是否存在*/private final RBloomFilter<String> cachePenetrationBloomFilter;/*** 构造函数,初始化布隆过滤器* * @param redissonClient Redisson 客户端,用于连接 Redis* @param bloomFilterProperties 布隆过滤器配置(名称、容量、误判率)*/public BloomFilterHandler(RedissonClient redissonClient, BloomFilterProperties bloomFilterProperties){// 获取指定名称的布隆过滤器实例RBloomFilter<String> cachePenetrationBloomFilter = redissonClient.getBloomFilter(bloomFilterProperties.getName());// 初始化布隆过滤器:预期元素数量 + 误判率(仅首次有效)
cachePenetrationBloomFilter.tryInit(bloomFilterProperties.getExpectedInsertions(), bloomFilterProperties.getFalseProbability());this.cachePenetrationBloomFilter = cachePenetrationBloomFilter;}/*** 添加元素到布隆过滤器* @param data 待添加的数据* @return 添加是否成功*/public boolean add(String data) {return cachePenetrationBloomFilter.add(data);}/*** 判断元素是否可能存在* @param data 要判断的数据* @return true 表示可能存在,false 表示一定不存在*/public boolean contains(String data) {return cachePenetrationBloomFilter.contains(data);}/*** 获取设置的预期插入数量*/public long getExpectedInsertions() {return cachePenetrationBloomFilter.getExpectedInsertions();}/*** 获取设置的误判率*/public double getFalseProbability() {return cachePenetrationBloomFilter.getFalseProbability();}/*** 获取布隆过滤器位数组的大小(bit 数)*/public long getSize() {return cachePenetrationBloomFilter.getSize();}/*** 获取布隆过滤器使用的哈希函数数量*/public int getHashIterations() {return cachePenetrationBloomFilter.getHashIterations();}/*** 获取布隆过滤器中当前元素数量(估算值)*/public long count() {return cachePenetrationBloomFilter.count();}
}

3.YML文件配置

Redis配置

spring:
  data:
    redis:
      database: 0              # Redis 使用的逻辑数据库(0 ~ 15),默认使用第 0 个
      host: 127.0.0.1          # Redis 服务器地址,本地为 127.0.0.1(本机)
      port: 6379               # Redis 服务端口,默认是 6379
      password: redis          # Redis 登录密码
      timeout: 3000            # 连接超时时间,单位为毫秒,3000ms = 3秒

 布隆过滤器配置

bloom-filter:
  name: user-register-bloom-filter       # 布隆过滤器的逻辑名称(Redis中作为Key)
  expectedInsertions: 1000               # 预计将插入的元素数量(n)
  falseProbability: 0.01                 # 误判率(p),这里是1%,可接受的“存在误判”概率

4.测试使用 

判断用户手机号在布隆过滤器中是否存在。

如果判断存在的话,由于布隆过滤器有碰撞率,则需要在数据库中再次判断。

public void doExist(String mobile) {// 第一步:使用布隆过滤器判断手机号是否可能存在boolean contains = bloomFilterHandler.contains(mobile);if (contains) {// 第二步:布隆过滤器判断可能存在(有一定误判概率),再查数据库做二次确认LambdaQueryWrapper<UserMobile> queryWrapper = Wrappers.lambdaQuery(UserMobile.class).eq(UserMobile::getMobile, mobile); // 查询数据库UserMobile userMobile = userMobileMapper.selectOne(queryWrapper); // 第三步:如果数据库中确实存在该手机号,抛出异常if (Objects.nonNull(userMobile)) {throw new DaMaiFrameException(BaseCode.USER_EXIST);  // 自定义异常,提示“用户已存在”}}// 如果布隆过滤器判断为“不存在”,则直接通过(一定不存在,无需查库)// 如果布隆过滤器判断为“存在”,但数据库不存在,则视为新手机号,可继续注册
}

如何解决缓存穿透

一.定义

缓存穿透是指查询的数据在缓存和数据库中都不存在,导致每次查询这条数据都会穿透过缓存,直接去查询数据库,相当于没有缓存一样。

二.危害

一般存在缓存是为了缓解数据库的压力,如果短时间内发生了大量的请求并缓存穿透,就会试数据库的压力猛增,数据库的抗压能力比Redis要差的多得多,完全不是一个级别,所以如果是高并发的缓存穿透,极有可能造成系统宕机。

三.解决方案

  1. 缓存空对象

    当查询的数据在缓存中和数据库中都不存在时,就缓存一个空结果,比如null,并将这个空结果返回给前端,并设置一个过期时间,避免消耗太多的内存。

    拿用户注册的逻辑来说:

    • 用户1注册用户使用自己的手机号,查询缓存和数据库都不存在,接着在缓存中设置一个空值,过期时间30s

    • 用户2注册用户使用自己的手机号,查询缓存和数据库都不存在,接着在缓存中设置一个空值,过期时间30s

    存在的问题

    当短时间内大量用户来注册,每个用户都是用自己的手机号,缓存空值没有得到复用,除非手机号重复了,但这也不可能。所以还是穿透了缓存,请求都落到了数据库上,所以这种方案适合缓存空值能复用的场景。对于用户注册业务来说,不太适合。

  2. 分布式锁

    分布式锁是防止并发问题最常用的解决方案了,核心就是加一把锁,每次只有一个请求能获得到锁,没有获得锁的请求等待获得锁的请求执行完后释放锁,然后再次竞争。所以解决缓存穿透也是可以的。

    存在的问题

    所谓分布锁,就是让请求变得串行化。每次只有一个请求执行,其他请求只能等待,这样会降低项目的并发量,对于并大量不高的项目来说,这种方案是可以的,但是对于大麦网高并发的项目来说,短时间的大量用户请求需要一个一个的执行,非常的影响用户体验,所以这种方案也不是很适合。

  3. 布隆过滤器

    采用布隆过滤器,虽然在判断对象存在的时候会存在误判,这是由于存在hash碰撞而产生的。但是判断对象不存在则是一定不存在的。

    使用布隆过滤器,存在的对象再去数据库进行查询,这样就解决的误判的问题。而对于不存在的对象直接返回结果即可。

 

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

相关文章:

  • 智能推荐社交分享小程序(websocket即时通讯、协同过滤算法、时间衰减因子模型、热度得分算法)
  • 【论文阅读】Improving the Diffusability of Autoencoders
  • Word2Vec模型详解:CBOW与Skip-gram
  • 结构化数据格式解析:JSON 与 XML 的技术应用与实践
  • Serverless 数据库来了?无服务器数据库 vs 传统数据库有何不同?
  • MySQL索引面试问题梳理
  • 华为eNSP防火墙实验(包含详细步骤)
  • Spring AI:检索增强生成(RAG)
  • SystemVerilog 断言重复操作符和序列操作符
  • 用 Spring Boot + Redis 实现哔哩哔哩弹幕系统(上篇博客改进版)
  • 2025年INS SCI2区,灵活交叉变异灰狼算法GWO_C/M+集群任务调度,深度解析+性能实测
  • 短视频电商APP源码开发技术栈解析:音视频、商品链路与互动设计
  • Web前端:not(否定伪类选择器)
  • 高效学习之一篇搞定分布式管理系统Git !
  • 编译安装Python 3.9(Linux Centos 7)
  • 淘宝直播与开源链动2+1模式AI智能名片S2B2C商城小程序的融合发展研究
  • Spring中Bean的实例化(xml)
  • 【docker】linux CentOS docker 安装流程
  • CSS知识复习5
  • CKS认证 | Day5 供应链安全 Trivy、kubesec、Webhook
  • 【Linux】基础开发工具(3)
  • 云归子批量混剪软件批量剪辑软件批量分割视频更新记录
  • 关于 scrapy框架 详解
  • Spring AI 基本组件详解 —— ChatClient、Prompt、Memory
  • 装修水电改造需要注意什么?水电改造有哪些注意事项?
  • C++ 的 copy and swap 惯用法
  • 05每日简报20250708
  • Kafka消息倾斜
  • 机器学习(西瓜书) 第三章 线性模型
  • Java 面向对象三大特性详解:封装、继承与多态,掌握OOP核心思想