布隆过滤器(解决redis缓存穿透步骤之一)

布隆过滤器详解

1、是什么

布隆过滤器(Bloom Filter):一个很长的二进制位数组 + N 个哈希函数 ,用来快速判断:某个元素一定不存在 / 可能存在

✅特点:

  • 判断不存在:100% 准确
  • 判断存在 :有误判概率(可能把不在的元素判定为存在)
  • 不能删除元素(普通布隆),只能新增、查询
  • 不存储原始数据,只存 hash 映射后的 bit 位,内存占用极小

2、工作原理

  1. 初始化:长度为 m 的 bit 数组,全部置 0
  2. 添加元素 元素经过 k 个不同哈希函数,算出 k 个下标,把数组对应下标全部置为 1

同一个位置多次置 1,还是 1。

  1. 查询元素是否存在 对元素执行同样 k 个 hash,拿到 k 个下标:
  • 只要有任意一位是 0 → 元素一定不存在
  • 全部都是 1 → 元素可能存在(存在误判)

误判来源:不同元素 hash 刚好落在同一批 bit 位上。

❌不能删除:如果把某个 bit 置 0,会影响其他共用这个 bit 的元素。

如果需要删除:使用计数布隆过滤器,bit 数组改成计数器数组。


3、核心参数

  • m:bit 数组长度(越大,误判率越低,内存越大)
  • n:预期存放元素数量
  • k:哈希函数个数
  • 误判率 p:业务上可以接受的错误概率

经验公式: 最佳哈希函数数量 k = (m/n)*ln2 m 越大、n 越小,误判率越低。


4、Java/Redis 实际使用场景

场景 1:缓存穿透(最经典面试)

大量请求查询数据库不存在的数据,直接打穿 Redis 打到 DB。

方案:把所有有效 key 放入布隆过滤器。 请求过来先过布隆:

  • 返回不存在 → 直接返回,不查 redis、不查 DB
  • 返回可能存在 → 再查缓存、数据库

注意:布隆过滤器有误判,不会 100% 拦截。

场景 2:黑名单、垃圾过滤

黑名单手机号、url,判断是否在黑名单。

场景 3:去重

比如爬虫,判断 url 是否已经爬过;大数据量去重。

❗不能做:精确判断存在、需要删除元素的业务。


5、Redis 布隆过滤器

Redis4.0 以后提供插件 RedisBloom

复制代码
# 添加
BF.ADD bloom_user 10001
# 查询
BF.EXISTS bloom_user 10001

Redis 布隆也不支持删除。

实例 1:Redis 布隆过滤器解决缓存穿透(伪业务代码,Java)

前提:Redis 安装 RedisBloom 模块,命令:BF.ADDBF.EXISTS

业务:商品查询

  1. 项目启动预热:把数据库全部合法商品 id 加载进布隆过滤器
java 复制代码
// 项目启动,把所有商品id存入布隆
List<Long> allGoodsId = goodsMapper.selectAllGoodsId();
for (Long id : allGoodsId) {
    // BF.ADD bloom:goods 1001
    redisTemplate.opsForValue().execute((RedisCallback<Object>) conn -> {
        conn.bfAdd("bloom:goods", id.toString().getBytes());
        return null;
    });
}
  1. 查询商品完整流程
java 复制代码
public Goods getGoodsById(Long goodsId){
    //①第一步:布隆过滤器判断
    Boolean mayExist = (Boolean)redisTemplate.opsForValue().execute(conn->{
        return conn.bfExists("bloom:goods",goodsId.toString().getBytes());
    });
    //布隆返回false:一定不存在,直接返回,不走redis、不走DB
    if(Boolean.FALSE.equals(mayExist)){
        return null;
    }

    //②布隆说可能存在,查询Redis缓存
    String cacheJson = redisTemplate.opsForValue().get("goods:"+goodsId);
    if(cacheJson != null){
        return JSONUtil.toBean(cacheJson,Goods.class);
    }

    //③redis未命中,查询数据库
    Goods goods = goodsMapper.selectById(goodsId);
    if(goods != null){
        //写入redis缓存
        redisTemplate.opsForValue().set("goods:"+goodsId,JSONUtil.toJsonStr(goods),30, TimeUnit.MINUTES);
    }
    return goods;
}

业务注意:新增商品,需要同步调用BF.ADD把新 id 加入布隆; ⚠️删除商品,普通布隆无法删除,会产生脏数据。

实例2:Guava 布隆过滤器(Java 本地)

java 复制代码
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>32.1.3-jre</version>
</dependency>
java 复制代码
// 预期元素10万,误判率0.01
BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8),100000,0.01);
bloomFilter.put("zhangsan");
boolean exist = bloomFilter.mightContain("zhangsan");

Guava 布隆是本地内存,多实例环境无法共享,适合单机;分布式必须用 RedisBloom。


6、高频面试考点

  1. 为什么布隆过滤器不能删除?

一个 bit 位会被多个元素共享,把 bit 置 0,会把其他元素标记成不存在。 解决:计数布隆过滤器,每个位置存计数器,删除计数器减一。

  1. 为什么会误判?

哈希冲突,不同元素映射到同一组 bit。不会漏判,只会误判。 不存在一定是真的不存在;存在不一定真的存在。

  1. 缓存穿透使用布隆过滤器有什么缺点? 1)有一定误判率; 2)元素新增后布隆要同步; 3)数据大量删除,普通布隆无法处理; 4)Redis 布隆需要安装插件;Guava 本地实例之间数据不互通。
  2. 布隆过滤器、BitMap 区别
  • BitMap:一个元素对应一个 bit,无误差,但是需要知道元素最大值,适合 id 连续场景。
  • 布隆过滤器:k 个 hash 映射多个 bit,有误差,不需要知道元素范围,适合字符串、不规则 id。

7、对比小结

表格

特性 布隆过滤器 BitMap
误差 存在误判 无误差
存储原始值 不存储 不存储
删除 普通版本不支持 支持
适用数据 字符串、离散 id 连续数字 id
内存 极小 取决于最大值
相关推荐
xrandzj1 小时前
MySQL8.0 从零通关核心操作手册(Ubuntu实战版)
数据库·mysql
我不会插花弄玉1 小时前
4.数据类型【由浅入深-MySQL】
数据库·mysql
denggun123452 小时前
yield
前端·数据库·python
ltl2 小时前
SQLite Rollback Journal:写前拷贝与提交点
数据库
ltl2 小时前
TiFlash Learner:Raft 日志如何进列存
数据库
ltl2 小时前
BM25 与 Lucene Similarity:公式如何落到 postings
数据库
ltl2 小时前
RocksDB Block Cache 与 Bloom Filter:读放大怎么裁
数据库
deli0070074 小时前
JSON 树形查看器:粘贴即解析,折叠展开一目了然
前端·数据库·json·ai编程
国科安芯4 小时前
轨道供电韧性:ASP4644四通道降压架构在低轨卫星平台电源管理中的适用性分析
运维·网络·数据库·嵌入式硬件·架构·状态模式·低轨卫星星座