布隆过滤器(解决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.ADD、BF.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
内存 极小 取决于最大值
相关推荐
许彰午6 小时前
01-Oracle数据库简介与版本选择
数据库·oracle
菜鸟~noob2336 小时前
【电子战】第三季预告—波武器与电子系统强力毁伤
开发语言·数据库·matlab·电子战
Seraphina368 小时前
SQL注入(二)盲注
网络·数据库·经验分享·笔记·sql
盟接之桥9 小时前
AI助手:你的7×24小时智能管家——让异常预警无处不在
大数据·网络·数据库·人工智能·制造·ai编程
waoooqwe9 小时前
问卷样本真实吗
大数据·数据库·算法·数据分析
可乐ea10 小时前
Agent 评测换判据:用终态数据库状态判定任务是否完成
数据库·ui·oracle·mcp工具·agent评测·终态断言·智能体可靠性
l1t10 小时前
DeepSeek总结的一个不含数据的 DuckDB 数据库
服务器·数据库·duckdb
ly768910 小时前
Redis 大 Key 与热点 Key 生产治理:发现、拆分、限流与本地缓存的组合策略
redis·限流·本地缓存·大key·热点key
PaperData11 小时前
2007-2020年税收调查面板数据
数据库
程序员清风11 小时前
CSV、Excel 与数据库数据读取实践
数据库·oracle·excel