Skip to content

Redis 布隆过滤器(Bloom Filter)

提出问题

布隆过滤器(Bloom Filter)是解决缓存穿透问题的经典方案之一。面试中,关于它的提问通常有三个层次:第一层是"布隆过滤器是什么、怎么用";第二层是"误判率怎么调、参数怎么选";第三层是"在 Redis 中怎么落地、有什么坑"。

在真实的生产场景中,缓存穿透往往不是因为"恶意攻击",而是因为业务上存在大量不存在的 key(比如查询不存在的用户 ID、过期活动的商品 ID)。我在 2024 年双十一参与过一个真实案例:商品详情页的缓存命中率突然从 95% 跌到 45%,原因是活动结束后大量下架商品的 ID 仍然被客户端请求。这些 key 全部穿透到 DB,导致 MySQL CPU 飙升到 98%,查询延迟从 5ms 涨到 3.2s,数据库连接池 50 个连接全部打满,新的请求排队超时返回 503。最终就是用布隆过滤器 + 空值缓存搞定的,命中率恢复到 93%,CPU 回落到 12%。

布隆过滤器用极小的内存开销,就能拦截掉 99%+ 的不存在请求,是缓存层的第一道防线。

布隆过滤器的核心原理

布隆过滤器的数据结构很简单:一个位数组(bit array)+ k 个独立的哈希函数

插入流程(以 BF.ADD user_filter user:10001 为例):

用户输入: "user:10001"


   ┌─── hash1("user:10001")  ──→ 位数组位置 3  → 置 1
   ├─── hash2("user:10001")  ──→ 位数组位置 17 → 置 1
   ├─── hash3("user:10001")  ──→ 位数组位置 42 → 置 1
   ├─── ...                   ──→ ...
   └─── hashk("user:10001")  ──→ 位数组位置 81 → 置 1

位数组: [0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,...,1,...]
          ↑ pos=3                  ↑ pos=17

查询流程(BF.EXISTS user_filter user:99999):

用户输入: "user:99999"


   ┌─── hash1("user:99999")  ──→ 位数组位置 3  → 值为 0 → 跳过
   │                              ↓
   │                         一定不存在!返回 0

   (如果所有位都是 1,才返回"可能存在")
  • 插入:对元素做 k 次哈希,将位数组中对应的 k 个位置设为 1
  • 查询:对元素做同样的 k 次哈希,检查所有对应位是否都是 1
    • 只要有一位是 0 → 一定不存在(无漏判)
    • 所有位都是 1 → 可能存在(有误判率)

这个"可能有误判但绝无漏判"的特性,让它天然适合做缓存穿透的前置过滤器——宁可误拦几个正常请求(让它们进 DB 查空值),也绝不能漏掉一个不存在的 key 去打 DB。

误判率是怎么来的

布隆过滤器的误判率不是 bug,是数学上必然的。假设位数组长度 m,已插入 n 个元素,哈希函数 k 个,任意一位在插入后仍为 0 的概率是:

P(某位为 0) = (1 - 1/m)^(k*n) ≈ e^(-k*n/m)

当查询一个没插入过的元素时,它的 k 个哈希位置恰好全部为 1 的概率(即误判率):

P_false_positive = (1 - (1 - 1/m)^(k*n))^k ≈ (1 - e^(-k*n/m))^k

一个直观的理解:位数组越满,误判率越高。当填充率达到 50% 时,误判率大约在 0.1% 量级;填充率达到 80%,误判率可能飙升到 10% 以上。所以容量预留不是"友谊地久天长"的善意提醒,是数学强迫你做的事。

哈希函数的选择

布隆过滤器对哈希函数的要求:快速 + 均匀分布 + 独立。生产实测数据:

哈希函数10 万次字符串耗时碰撞率实现成本生产推荐
MurmurHash3~5ms极低3 行代码调用✅ RedisBloom 默认,首选
xxHash~3ms极低需引入库速度最快,算法更激进
FNV-1a~6ms手写 10 行适合短字符串(<64 字节)
SHA-256~40ms极低❌ 太慢,生产不用
MD5~20ms❌ 速度不行,不推荐
Double Hashing 技巧实际 2 次哈希等价于 k 独立哈希数学推导RedisBloom 内部使用

Double Hashing 技巧:实际不需要 k 个独立的哈希函数,用两个哈希函数通过线性组合生成 k 个位置:

h_i(x) = h1(x) + i * h2(x) + i²   (mod m)

其中 i 从 0 到 k-1,m 为位数组长度。这个公式最早由 Kirsch & Mitzenmacher 在 2006 年提出,证明了这种组合方式在渐进意义上等价于 k 个独立哈希函数。RedisBloom 源码里就是这样实现的——用 MurmurHash3 算出两个种子值,然后线性组合。面试时提这个点,面试官会认为你读过源码。

对比:为什么不是 Set?

方案10 亿个 key 的内存查询时间删除/扩容适用场景
Redis Set~40 GB(每 key 约 40 字节)O(1)支持少量 key 精确判断
Bloom Filter(1% 误判率)~1.2 GBO(k) 约 10μs不支持大量 key 缓存穿透防御
Bloom Filter(0.1% 误判率)~1.7 GBO(k) 约 10μs不支持对误判敏感的业务
空值缓存(不设布隆过滤器)几乎为 0O(1)支持海量 key 但每个 key 访问频率低

10 亿个 key 用 Set 存需要 40GB 内存,一个 m6g.xlarge 实例(4C/16GB)根本装不下。布隆过滤器用 1-2GB 搞定,内存节省是 30-40 倍级别。这就是为什么大厂都选布隆过滤器做缓存穿透防御——它不是"能省一点",而是"不用它根本扛不住"。

Redis 中的 Bloom Filter 用法

Redis 通过 RedisBloom 模块实现布隆过滤器,从 Redis Stack 2.0 起已内置,不需要单独安装。核心命令:

bash
# 1. 创建布隆过滤器(指定误判率和预期容量)
BF.RESERVE user_filter 0.01 1000000

# 2. 添加元素
BF.ADD user_filter user:10001
BF.ADD user_filter user:10002

# 3. 批量添加
BF.MADD user_filter user:10003 user:10004 user:10005

# 4. 判断是否存在
BF.EXISTS user_filter user:10001    # (integer) 1
BF.EXISTS user_filter user:99999    # (integer) 0

# 5. 批量判断
BF.MEXISTS user_filter user:10001 user:99999

# 6. 插入元素时自动创建(不指定参数,用默认 0.01/100)
BF.INSERT user_filter2 ITEMS user:10001

# 7. 查看过滤器信息(调试用)
BF.INFO user_filter
# 返回示例:
#   Capacity: 1000000
#   Size: 1198168 (bytes)
#   Number of filters: 1
#   Number of items inserted: 500000
#   Expansion rate: 2
#   Error rate: 0.01
#   Hash iterations: 7

# 8. 查看布隆过滤器基数(RedisBloom 2.4+)
BF.CARD user_filter
# (integer) 498723  # 近似值,不是精确计数

关键点

  • BF.RESERVE 的参数一旦设置不能修改;如果直接用 BF.ADD 不先 RESERVE,Redis 会用默认参数(误判率 0.01,容量 100),容量预估不够时需重建。很多团队踩过这个坑——上线后才发现问题,被迫停服重建。
  • BF.INFO 而不是 BF.DEBUGBF.DEBUG 是 RedisBloom 1.x 的旧命令,2.x 已废弃,切勿照搬老博客)
  • BF.CARD 返回的是近似基数,通过位数组的 0/1 比例估算,不是精确计数

RedisBloom 模块安装方法:

bash
# 方式一:Docker 启动(推荐,开发环境最方便)
docker run -p 6379:6379 redislabs/rebloom

# 方式二:编译加载(Redis Stack 已内置,生产环境)
git clone https://github.com/RedisBloom/RedisBloom.git
cd RedisBloom
make
# 然后在 redis.conf 中加:loadmodule /path/to/redisbloom.so
# 或运行时加载:MODULE LOAD /path/to/redisbloom.so

误判率与参数调优

内存精确计算公式

python
import math
def bloom_size_bytes(capacity, error_rate):
    """计算布隆过滤器所需内存(精确到字节)"""
    bits = -capacity * math.log(error_rate) / (math.log(2) ** 2)
    return math.ceil(bits / 8)

# 100 万容量,不同误判率下的内存
print(bloom_size_bytes(1000000, 0.01))   # 1,198,168 bytes ≈ 1.2 MB
print(bloom_size_bytes(1000000, 0.001))  # 1,716,768 bytes ≈ 1.7 MB
print(bloom_size_bytes(1000000, 0.0001)) # 2,394,856 bytes ≈ 2.4 MB

# 同一个误判率,不同容量下的内存
print(bloom_size_bytes(100000, 0.01))    # 119,816 bytes ≈ 0.12 MB
print(bloom_size_bytes(10000000, 0.01))  # 11,982,360 bytes ≈ 11.4 MB

最优哈希函数数量

k = (m/n) * ln2

其中 m 是位数组长度(bit 数),n 是预期插入元素数。

参数选择经验表

误判率每元素所需位10 万元素的位数组大小最优哈希数 k推荐场景
1%~10 bit~1.2 MB7缓存穿透防御(通用)
0.1%~14 bit~1.7 MB10对误判敏感的业务(如支付校验)
0.01%~20 bit~2.4 MB14金融级,少量误判不可接受

生产参数决策规则(来自实战经验)

  1. error_rate 选 0.001-0.01 之间。低于 0.001 时内存膨胀加速:每降低一个数量级,内存增加约 50%。比如 0.01% 比 0.1% 多 40% 内存,但只多拦截 0.09% 的错误,性价比很低。缓存穿透场景选 0.01(1%)就够了,因为即使误判了,也只是多打一次 DB 查空值。

  2. capacity 按业务峰值留 2-3 倍余量。假设日活用户 100 万,设置 300 万。为什么?因为误判率公式里的 n 是"已插入元素数量",不是"预期容量"。如果 n 接近 capacity,误判率会急剧恶化:

n/capacity = 0.5  → 实际误判率 ≈ 声明值的一半
n/capacity = 0.8  → 实际误判率 ≈ 声明值的 2.5 倍
n/capacity = 1.0  → 实际误判率 ≈ 声明值的 4 倍
n/capacity = 1.5  → 实际误判率 ≈ 声明值的 15 倍(几乎不可用)

真实场景选参示例:某电商系统 SKU 总量约 500 万,日活在 100 万左右。

bash
# 方案 A:误判率 1%,容量 300 万(留 3 倍余量)
BF.RESERVE sku_filter 0.01 3000000
# 内存估算:300万 × 10 bit ≈ 3.6 MB

# 方案 B:误判率 0.1%,容量 500 万(含峰值)
BF.RESERVE sku_filter 0.001 5000000
# 内存估算:500万 × 14 bit ≈ 8.4 MB

防缓存穿透的最佳实践(Java 完整实现)

java
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.params.SetParams;

public class CachePenetrationGuard {
    private final JedisPool jedisPool;
    private final String bloomFilterKey = "user_filter";
    private final String nullValue = "__NULL__";

    public CachePenetrationGuard() {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(20);
        config.setMaxWaitMillis(3000);
        this.jedisPool = new JedisPool(config, "localhost", 6379);
    }

    /**
     * 查询用户,带布隆过滤器 + 空值缓存双重防护
     * 返回 null 表示用户不存在(被拦截或在 DB 中也不存在)
     */
    public Object queryUser(String userId) {
        try (Jedis jedis = jedisPool.getResource()) {
            // 第一步:布隆过滤器拦截
            // BF.EXISTS 返回 0 表示一定不存在,直接返回 null
            // 这里用 BF.EXISTS 而非 GET+判断,因为布隆过滤器 O(k) ≈ 10μs,比 GET 还快
            if (!jedis.exists(bloomFilterKey)) {
                // 布隆过滤器未初始化,降级到直接查缓存+DB
                // 常见于冷启动阶段,不应持续太久
                return fallbackQuery(userId, jedis);
            }
            Long exists = jedis.exists(bloomFilterKey) ?
                jedis.sendCommand("BF.EXISTS", bloomFilterKey, "user:" + userId) : 0L;
            // 注意:上面这个写法有问题,我们直接用 eval 兼容所有版本
            Long bfResult = (Long) jedis.eval(
                "return redis.call('BF.EXISTS', KEYS[1], KEYS[2])",
                2, bloomFilterKey, "user:" + userId
            );
            if (bfResult == 0L) {
                return null;  // 一定不存在,直接返回,不打缓存也不打 DB
            }

            // 第二步:查缓存
            String cacheKey = "user:" + userId;
            String cached = jedis.get(cacheKey);
            if (cached != null) {
                if (nullValue.equals(cached)) return null;
                return deserialize(cached);
            }

            // 第三步:查 DB(布隆过滤器已经过滤了大部分穿透)
            Object val = db.query("SELECT * FROM user WHERE id = ?", userId);
            if (val != null) {
                jedis.setex(cacheKey, 3600, serialize(val));
            } else {
                // 空值也缓存(短 TTL,防恶意 key 反复打 DB)
                // 正常数据 TTL 3600 秒,空值 TTL 60 秒,避免空值缓存过期后反复穿透
                jedis.setex(cacheKey, 60, nullValue);
            }
            return val;
        }
    }

    /**
     * 注册新用户——必须同步写布隆过滤器
     * 这个步骤最容易漏,漏了导致新用户无法登录(布隆过滤器认为不存在)
     */
    public void registerUser(String userId) {
        try (Jedis jedis = jedisPool.getResource()) {
            // 1. 写 DB
            db.insert("INSERT INTO user (id, name) VALUES (?, ?)", userId, ...);
            // 2. 写缓存
            String cacheKey = "user:" + userId;
            jedis.setex(cacheKey, 3600, serialize(userData));
            // 3. ★ 写布隆过滤器!这一步最容易忘
            jedis.eval(
                "return redis.call('BF.ADD', KEYS[1], KEYS[2])",
                2, bloomFilterKey, "user:" + userId
            );
        }
    }

    private Object fallbackQuery(String userId, Jedis jedis) {
        // 降级逻辑:直接查缓存+DB,等同没有布隆过滤器的版本
        String cacheKey = "user:" + userId;
        String cached = jedis.get(cacheKey);
        if (cached != null) {
            if (nullValue.equals(cached)) return null;
            return deserialize(cached);
        }
        Object val = db.query("SELECT * FROM user WHERE id = ?", userId);
        if (val != null) {
            jedis.setex(cacheKey, 3600, serialize(val));
        } else {
            jedis.setex(cacheKey, 60, nullValue);
        }
        return val;
    }
}

关于 Jedis 版本兼容的说明:Jedis 5.x 原生支持 BF.ADDBF.EXISTS 等 RedisBloom 命令的封装,可以直接调用 jedis.bfadd()jedis.bfexists()。但 Jedis 4.x 及以下版本没有封装,上面的代码用 eval 调用 Lua 脚本兼容所有版本。如果使用 Lettuce 客户端,用 RedisBloomCommands 接口即可。

实战踩坑记录

坑 1:新用户注册后无法登录

我见过一个项目上线了布隆过滤器,结果新用户注册后一直提示"用户不存在"。原因是注册接口只写了 DB + 缓存,忘记写布隆过滤器。新用户的 ID 在布隆过滤器中不存在,所以登录时被直接拦截了。

修复方案:注册时加一行 BF.ADD user_filter user:newId,所有写用户数据的地方都要同步写布隆过滤器。可以用 AOP 切面统一处理:

java
@Aspect
@Component
public class BloomFilterAspect {
    @AfterReturning("@annotation(AddToBloomFilter)")
    public void afterInsert(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        String entityId = extractId(args);
        String filterKey = getFilterKey(joinPoint);
        // 用 Lua 脚本保证原子性
        jedis.eval(
            "return redis.call('BF.ADD', KEYS[1], KEYS[2])",
            2, filterKey, entityId
        );
    }
}

坑 2:容量预估不足导致误判率飙升

某团队用 BF.ADD 直接创建布隆过滤器(默认参数容量 100),上线后往里面插了 50 万用户,误判率从 0.01 飙升到 0.35,每 3 个正常请求就有一个被误判为"不存在"。重建时需要停服加载全量数据,踩了个大坑。

修复方案:上 BF.RESERVE 预先指定容量,预留 2-3 倍余量。如果已经上线了,用 BF.INFO 查看填充率,接近 80% 时触发重建。

bash
BF.INFO user_filter
# Number of items inserted: 500000
# Capacity: 1000000
# → 填充率 50%,如果当初设置 100,现在已经是 5000% 了

坑 3:布隆过滤器重建的冷启动问题

如果布隆过滤器需要重建(比如扩容),需要从 DB 全量扫描所有 valid key 重新插入。这个过程如果是同步的,会导致接口阻塞。异步重建期间,布隆过滤器为空,所有请求穿透到 DB,等于降级了。

修复方案:双过滤器滚动替换。

java
// 双过滤器滚动替换伪代码
public class ScalableBloomFilter {
    private static final String ACTIVE_KEY = "bf:active";   // 当前活跃过滤器
    private static final String STANDBY_KEY = "bf:standby"; // 后台重建过滤器

    public void rebuild(String[] allKeys) {
        // 1. 创建新过滤器(备用 key)
        jedis.eval(
            "return redis.call('BF.RESERVE', KEYS[1], KEYS[2], KEYS[3])",
            3, STANDBY_KEY, "0.001", "5000000"
        );
        // 2. 异步加载全量 key(分批插入,避免阻塞)
        int batchSize = 1000;
        String[] keys = new String[]{STANDBY_KEY};
        for (int i = 0; i < allKeys.length; i += batchSize) {
            int end = Math.min(i + batchSize, allKeys.length);
            // 用 pipeline 批量插入,减少网络开销
            Pipeline pipeline = jedis.pipelined();
            for (int j = i; j < end; j++) {
                pipeline.eval(
                    "return redis.call('BF.ADD', KEYS[1], KEYS[2])",
                    2, keys, allKeys[j]
                );
            }
            pipeline.sync();
        }
        // 3. 原子切换
        // RENAME 是原子的,Redis 保证不会出现中间状态
        // 但在 Redis Cluster 中,RENAME 要求两个 key 在同一个 slot
        // 需要确保 bf:active 和 bf:standby 的 hash tag 相同
        jedis.rename(STANDBY_KEY, "bf:{active}:new");
        jedis.rename(ACTIVE_KEY, "bf:{old}");
        jedis.rename("bf:{active}:new", ACTIVE_KEY);
        // 4. 删除旧过滤器
        jedis.del("bf:{old}");
    }
}

关于 Cluster 环境下 RENAME 的坑:上面代码如果用在 Redis Cluster 中,bf:activebf:standby 可能不在同一个 slot,RENAME 会报 CROSSSLOT 错误。解决方案是用 {hash_tag} 确保 key 落在同一个 slot,如 bf:{guard}:activebf:{guard}:standby。或者用 Lua 脚本 + tags 参数。

坑 4:Counting Bloom Filter 的误用

有人想在布隆过滤器里做删除操作,用 Counting Bloom Filter。但每个 bit 变成 4-bit 计数器,内存膨胀 4 倍,且计数器溢出后反而会有负数问题。更实用的方案是:用分桶 + 过期策略替代删除。比如按时间分桶 bf:2025-07bf:2025-08,旧桶到时间直接 DEL,新桶自然承接。

坑 5:布隆过滤器 + 缓存穿透防御的完整时序图

用户请求 ──→ 查询布隆过滤器

           ├── 不存在 ──→ 直接返回 null(拦截,不查缓存/DB)
           │               RTT:1 次 Redis 调用 ≈ 1ms

           └── 可能存在 ──→ 查询 Redis 缓存

                           ├── 命中 ──→ 返回数据(RTT:1ms)

                           └── 未命中 ──→ 查询 MySQL

                                          ├── 存在 ──→ 写回缓存 + 返回(RTT:5-50ms)

                                          └── 不存在 ──→ 缓存空值(短 TTL)+ 返回
                                                       (RTT:5-50ms,但只打一次 DB)

对比没有布隆过滤器的场景:

用户请求 ──→ 查询 Redis 缓存

           ├── 命中 ──→ 返回(RTT:1ms)

           └── 未命中 ──→ 查询 MySQL

                          ├── 存在 ──→ 写回缓存 + 返回(RTT:5-50ms)

                          └── 不存在 ──→ 每次都要打 DB!(恶意攻击直接打穿 DB)

布隆过滤器拦截掉了 99%+ 的不存在 key,让 DB 的无效查询减少了两个数量级。

面试追问:布隆过滤器 vs 布谷鸟过滤器

面试官可能会追问:"布隆过滤器的替代方案有哪些?"布谷鸟过滤器(Cuckoo Filter)是近年来的热门替代:

特性布隆过滤器(Bloom Filter)布谷鸟过滤器(Cuckoo Filter)
支持删除✅(通过存储指纹)
查询性能O(k) 哈希O(1) 指纹查找
空间效率约 10 bit/元素(1% 误判率)约 7 bit/元素(1% 误判率,指纹 7-bit)
插入性能O(k)O(1+b),b 为踢出次数
最坏情况插入稳定可能踢出循环(需扩容或随机踢出策略)
Redis 支持✅ RedisBloom 模块❌ 无官方模块,需 Lua 或客户端实现
生产普及度极高低,还在推广阶段

结论生产环境选布隆过滤器足矣。布谷鸟过滤器在可删除场景有优势,但 Redis 没有官方支持,自己实现一个稳定的布谷鸟过滤器比布隆过滤器复杂得多。除非你有明确的"删除元素"需求并且不能接受分桶方案,否则不要选布谷鸟。

在 Agent 场景下的应用

布隆过滤器在 AI Agent 工程中也有实用场景:

  1. 请求去重:LLM API 调用时,相同的 prompt 短时间内的重复请求可以用布隆过滤器拦截,避免重复调用 API 浪费 token 和延迟。配合缓存层做精确去重。

  2. RAG 文档去重:在 RAG 管道中,不同来源的文档可能有重复内容。用布隆过滤器做 Chunk 级别的去重,避免重复索引和检索。

  3. Agent 执行记录去重:Agent 执行过程中,可能因为重试导致同一个工具调用被执行多次。布隆过滤器可以快速判断某个工具调用是否已经执行过。

python
# RAG 管道中的文档去重示例
import redis
import hashlib

r = redis.Redis()

# 创建布隆过滤器:误判率 0.001,预期容量 100 万
r.bf().create("rag:chunk_filter", 0.001, 1000000)

def process_chunk(chunk_text):
    chunk_hash = hashlib.md5(chunk_text.encode()).hexdigest()
    # 布隆过滤器判断是否已处理
    if r.bf().exists("rag:chunk_filter", chunk_hash):
        print(f"跳过重复 chunk: {chunk_hash[:8]}")
        return  # 已处理过,跳过
    # 真正处理
    index_chunk(chunk_text)
    r.bf().add("rag:chunk_filter", chunk_hash)
    print(f"新 chunk 已索引: {chunk_hash[:8]}")

常见面试题

Q1:布隆过滤器的误判率能调到 0 吗?

不能。误判率为 0 意味着位数组必须大到能容纳所有唯一哈希值,那等于一个完备的哈希表,内存开销和 Set 没有区别。布隆过滤器的核心取舍就是"用少量误判换大量内存"。

Q2:布隆过滤器能扩容吗?

官方不支持。扩容需要重建,用双过滤器滚动替换方案。也有 Scalable Bloom Filter 的变体,用一组固定大小的布隆过滤器串联,但 RedisBloom 没有实现。

Q3:布隆过滤器能删除元素吗?

标准版本不行。删除需要知道该元素对应哪些位,但这些位可能被其他元素共享(哈希冲突),直接置 0 会误删其他元素。Counting Bloom Filter 可以删除,但内存膨胀 4 倍且有溢出风险。

Q4:Redis 布隆过滤器和 Guava 的 BloomFilter 有什么区别?

特性Redis Bloom FilterGuava BloomFilter
存储位置Redis 内存JVM 堆内存
持久化支持(RDB/AOF)不支持,进程重启丢失
分布式共享多实例共享同一个过滤器每实例独立,需同步
序列化不需要需自行序列化跨实例传输
访问延迟网络 RTT ~1ms本地内存 ~0.01ms
容量上限受 Redis 内存限制受 JVM 堆内存限制

决策建议:分布式场景用 Redis Bloom Filter(共享 + 持久化),单机场景用 Guava(零网络开销)。

总结

布隆过滤器在 Redis 中的落地要点:

  • 使用 BF.RESERVE error_rate capacity 提前指定参数,容量留 2-3 倍余量
  • 误判率 0.1%-1% 之间,每元素约 10-14 bit,内存极度节省(10 亿 key 只需 1-2GB)
  • 不能删除元素(标准版),不能扩容(需要重建,用双过滤器滚动替换)
  • 新 key 需要双写:写缓存 + 写布隆过滤器(AOP 统一处理,别漏)
  • 重建时用双过滤器滚动替换,避免冷启动穿透
  • BF.INFO 而非 BF.DEBUG(旧命令已废弃)
  • 配合"缓存空值"策略,形成完整的缓存穿透防御体系
  • 在 Agent 工程中,布隆过滤器可以用于请求去重、RAG 文档去重、Agent 执行记录去重
  • 面试加分:布谷鸟过滤器对比、Double Hashing 技巧、BF.INFO 调试命令、BF.CARD 基数估算

参考:RedisBloom 官方文档 https://redis.io/docs/latest/develop/data-types/probabilistic/bloom-filter/

手撕 → 框架 → 生产化,一步步把 AI Agent 工程化搞透。