登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  Redis

Redis布隆过滤器容量增长为什么会增加内存

来源:17golang原创

时间:2026-10-13 18:52:56 107浏览 收藏

我第一次把 Redis 布隆过滤器接到一批持续增长的 ID 黑名单时,最先看到的不是命中率变化,而是 Redis 内存曲线在某些时间点向上跳。原因通常不是每个元素保存了一份字符串,而是过滤器超过初始 capacity 后,Redis 为它增加了新的子过滤器。每个子过滤器都需要自己的位数组和哈希函数信息,所以总占用会继续增长。

官方地址:https://redis.io/docs/latest/commands/bf.reserve/

Redis 布隆过滤器的容量不是一个“永远封顶”的总容量。默认配置允许扩展;超过初始容量后会创建子过滤器,BF.INFO 中的 FILTERS 和 SIZE 可以帮助判断内存增长是否来自这条路径。

容量超过后,Redis 实际增加了什么

BF.RESERVE 创建过滤器时,Redis 会先按给定的误判率和初始容量准备一个子过滤器。新增元素数量达到这一段容量后,如果过滤器允许扩展,就再创建一个子过滤器。查询时需要按规则检查这些子过滤器,写入与查询的工作量也会随子过滤器数量增加。

Redis布隆过滤器从初始子过滤器扩展为多个子过滤器的关系示意图
图1:布隆过滤器超过初始容量后增加子过滤器,SIZE 与 FILTERS 随之增长;这是静态说明图,不是运行截图。

因此,“容量增长导致内存变大”更准确的解释是:容量增长让过滤器需要更多位来表示更多元素;如果增长跨过了初始容量边界,Redis 还会保留并维护多个子过滤器,而不是把原来的位数组简单改成一个更大的数组。

BF.RESERVE 的三个参数怎样影响内存

最小创建命令如下。这里的 error_rate 是允许的误判为存在的概率,capacity 是预计要放入的条目数。

# 预留约 100 万个条目,允许 0.1% 的误判率
BF.RESERVE user:seen 0.001 1000000

# 明确设置扩展倍率;容量用尽后按上一个子过滤器的大小扩展
BF.RESERVE user:seen 0.001 1000000 EXPANSION 2

误判率越低,每个元素所需的位数通常越多,因此同样的容量下内存也会增加。官方文档给出的近似关系是:所需位数与 -ln(error_rate) / ln(2)^2 成正比;实际占用还会受到模块实现和内存分配的影响,不能把公式结果当成精确的 Redis 进程内存。

EXPANSION 决定后续子过滤器相对前一个子过滤器的容量倍率。倍率为 2 时,子过滤器更少,适合容量难以预测的场景;倍率为 1 时,扩容颗粒更小,可能减少一次性多分配的内存,但更容易积累多个子过滤器。默认扩展倍率是 2。

用 BF.INFO 把内存增长拆开看

不要只看 Redis 的总内存曲线。对布隆过滤器本身,先读取容量、占用大小、子过滤器数量和已插入条目数:

# 读取过滤器的容量、字节数、子过滤器数量和扩展倍率
BF.INFO user:seen

# 单独读取四个关键观察项,便于写入监控采集
BF.INFO user:seen SIZE
BF.INFO user:seen FILTERS
BF.INFO user:seen CAPACITY
BF.INFO user:seen ITEMS

如果 FILTERS 从 1 变成 2 或更多,同时 SIZE 上升,基本可以把这段增长归因到扩展子过滤器。CAPACITY 表示在继续扩展前当前子过滤器还能接收的独立条目数量,ITEMS 则是被识别为新条目的数量;重复添加同一个元素不能简单等同于新增容量消耗。

这些信息适合组成一个轻量的自动化巡检回路:写入端按固定间隔采集 FILTERS、SIZE 和 ITEMS,门禁规则在子过滤器数量异常增长时发出通知,复盘时再对照实际业务容量是否已经超出创建时的估计。

容量怎么规划,才能少付扩容代价

Redis布隆过滤器容量规划与EXPANSION和NONSCALING参数取舍示意图
图2:预估容量、扩展倍率与 NONSCALING 的关系示意;这是静态说明图,不是运行截图。

我会先按业务保留期内的去重条目数估算容量,再决定是否允许扩展。下面这张表更适合作为上线前的参数速查:

场景参数思路代价
容量可准确预估把 capacity 设在预计值附近初始内存更可控,减少多子过滤器查询
容量未知且会持续增长允许扩展,必要时使用 EXPANSION 2 或更高减少子过滤器数量,但单次扩展可能申请更多内存
希望扩展颗粒更细使用 EXPANSION 1新增子过滤器更小,但长期可能有更多过滤器
超容量必须显式失败使用 NONSCALING内存更容易封顶,但达到容量后写入会报错

NONSCALING 会禁止创建额外子过滤器。它的内存通常略低于可扩展过滤器,但代价是超过初始容量后不能继续静默接收数据。对于黑名单、去重标记这类可以提前切换新 key 的业务,显式失败有时比无边界扩展更容易运营。

扩容后的失败处理和通知回路

如果选择了自动扩展,应用层仍要把容量告警当成正常的运营信号,而不是等 Redis 到达 maxmemory 才处理。可以把处理分成四步:

  1. 定期读取 BF.INFO key SIZE、FILTERS 和 ITEMS,保存时间序列。
  2. 当 FILTERS 增长或 ITEMS 接近业务预计上限时,通知负责缓存容量的人。
  3. 重新估算保留期、误判率和容量,决定新建更合适容量的过滤器,还是接受继续扩展。
  4. 切换 key 时保留读旧过滤器的过渡期,避免刚迁移就把仍在使用的数据判成“不存在”。

不要把 SIZE 当成业务数据量的精确线性函数。误判率、扩展倍率、子过滤器数量和模块版本都会改变实际表现;更可靠的做法是用 BF.INFO 记录趋势,再结合应用保留策略做决策。

几个容易混淆的问题

capacity 是总容量还是第一段容量?

在允许扩展的情况下,它首先描述初始子过滤器的容量。过滤器可以通过新子过滤器继续接收条目,但子过滤器越多,内存和查询开销越高。

为什么不直接把 capacity 设得很大?

设大可以减少扩展次数,但会把内存预算提前交给过滤器;如果预计条目量不稳定,还需要在空置内存和后续扩展之间取舍。

EXPANSION 越大越省内存吗?

不能这样理解。较大的倍率通常减少子过滤器数量,但新子过滤器也更大;它优化的是扩展次数与长期结构,不保证任意时刻的总内存都最低。

总结

Redis 布隆过滤器容量增长后内存增加,有两层原因:更多条目需要更多位;超过初始 capacity 后,默认扩展会创建新的子过滤器,而每个子过滤器都需要额外的位数组和哈希信息。先用 BF.INFO 看 FILTERS 与 SIZE,再依据容量可预测程度选择预估容量、EXPANSION 或 NONSCALING,比只盯着 Redis 总内存更容易做出正确判断。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>