如何使用 Redis 快速实现布隆过滤器?
- 标签:后端、Redis
🏛 业务架构师
布隆过滤器(Bloom Filter)是用位数组 + 多个哈希函数判断"某元素是否可能在集合中" 的概率结构:插入时多个哈希位置置 1;查询时任一位是 0 则肯定不存在,全部是 1 则可能存在(哈希冲突导致误判)。特点:不误判不存在、可能误判存在、不支持删除(删除会污染其他元素,要删得用计数变体)。
业务场景我按价值排:
- 缓存穿透防护(最经典):恶意请求大量不存在的 ID,每次都打到 DB。启动时把全量合法 ID 装入布隆过滤器(Redis 的
BF.ADD/BF.EXISTS或 Guava 本地实现),请求先过布隆——不存在直接拒绝,DB 流量砍掉 99%。代价是"极小概率把合法数据误判为不存在",要评估业务可否接受(商品查询可以,资金查询不行); - 推荐/内容已读去重:海量内容 ID 判"是否推过",比 Set 省 99% 内存——用可控的误判率(可能漏推少量内容)换内存成本;
- 爬虫 URL 去重、新用户判重、LSMT 的 SSTable 查询前置过滤。
成本算一笔:1 亿个 URL,Set 要几个 GB,布隆过滤器 1% 误判率只要约 120MB——这是典型的用数学换资源的交易。
工程提醒:容量要预估并留余量(元素超预期误判率飙升,重建成本高);全量重建机制要预先设计(业务数据每天全量刷新的场景最合适)。凡是"判重 + 容忍小概率误判 + 数据量大"的业务,布隆过滤器都是第一候选。
🏗 系统架构师
原生数据结构没有布隆过滤器,但有两种实现路径,我常用的是 RedisBloom 模块:
方案一:RedisBloom 模块(生产首选):加载 BF.ADD / BF.EXISTS / BF.RESERVE 命令,参数 error_rate 与 capacity,底层是带计数变体的布隆过滤器,可自动扩容(BF.SCANDUMP 可持久化迁移)。
方案二:Bitmap + 多个哈希函数手工实现:SETBIT/GETBIT 原语,写入时对 value 做 k 次哈希置位,查询时 k 个位全为 1 才可能存在。自研要处理好哈希函数选择(MurmurHash 等低碰撞)、误判率与容量公式:位数 m ≈ -n·ln(p)/(ln2)²,1% 误判约每元素 9.6 bit,1 亿元素约 120MB,成本远低于存全量 Set。
典型落地(缓存穿透防御):商品/用户库全量 ID 预热进布隆过滤器,请求先过 BF——不存在直接返回,穿透流量在 Redis 层前被拦截,DB 免遭恶意不存在 key 的打击。也用于推荐去重、爬虫 URL 判断。
特性必须讲清:有一定误判(不存在→可能判存在,放行后 DB 兜底)但不存在漏判(存在→一定不漏);标准 BF 不支持删除,要删除得上计数布隆或定期重建;数据变更要同步更新 BF,全量重建用定时任务双 buffer 切换。
💻 全栈开发专家
两种落地方式:
方式一:Redisson(无需 Redis 模块,推荐)
java
RBloomFilter<String> filter = redisson.getBloomFilter("bf:invite:valid");
// 预期插入 100 万,误判率 1% → 自动计算位数组大小和 hash 次数
filter.tryInit(1_000_000L, 0.01);
filter.add("user:1001");
boolean maybe = filter.contains("user:1001"); // true:可能存在
boolean no = filter.contains("user:9999"); // false:一定不存在方式二:RedisBloom 模块:BF.ADD key item / BF.EXISTS key item,支持 BF.RESERVE 预设容量,需服务端装模块。
原理一句话:一个位数组 + k 个 hash 函数,插入时把 k 个位置置 1;查询时 k 个位置全 1 才"可能存在",任一为 0 则"一定不存在"。特性:无假阴性、有假阳性(说不存在就是真不存在)。
我用它的典型场景——缓存穿透防护:
java
if (!bloom.contains(userId)) return null; // 一定不存在,直接挡掉
Product p = cache Aside 流程继续...工程坑:
- 容量要预估:塞超预期数量误判率飙升,且无法缩容,业务持续增长就按 2~3 倍预估或做定期重建(双 filter 切换);
- 不支持删除:误判的"脏位"清不掉,要删除能力换 Counting Bloom Filter 或 Cuckoo Filter;
- 数据先落库后要把 id 补进 filter,顺序别反;
- 它是前置粗筛,不能当精确数据源。