文章目录

Redis的布隆过滤器与缓存穿透防护

发布于 2026-06-01 20:11:20 · 浏览 23 次 · 评论 0 条

标题:Redis的布隆过滤器与缓存穿透防护


理解问题:什么是缓存穿透?

理解 缓存穿透的定义。当应用系统请求一个在缓存(如Redis)和数据库中都不存在的数据时,这个请求会直接“穿透”缓存层,每次都打到数据库。如果这种请求量很大,会对数据库造成巨大压力,甚至导致服务崩溃。

想象 这个场景:系统缓存了热门商品信息,但恶意用户或程序不断用不存在的商品ID(如 999999)发起查询。缓存查不到,所有请求全部转发至数据库,数据库反复查询后发现数据不存在,返回空结果,且这个结果也不会被缓存(因为缓存空值有额外开销和时效问题)。

认识 其核心是:缓存对“不存在”的数据束手无策


解决方案:布隆过滤器(Bloom Filter)

引入 一个高效的概率型数据结构——布隆过滤器。它能快速判断一个元素是否“可能存在于”一个集合中,或“肯定不存在”于集合中

理解 其工作原理:

  1. 初始化:创建一个全为0的位数组(比如长度为m),并确定k个不同的哈希函数。
  2. 添加元素:当向过滤器中添加一个元素时,使用这k个哈希函数分别对该元素进行计算,得到k个索引位置,并将位数组中这些位置的值全部置为1
  3. 查询元素:当查询一个元素是否存在时,同样使用这k个哈希函数计算索引,并检查位数组中这些位置是否全部为1
    • 如果有任何一个位置是0,则该元素肯定不存在
    • 如果所有位置都是1,则该元素可能存在(因为有“误判”的可能,即不同的元素经过哈希计算后可能恰好都映射到了相同的1位上)。

应用 到缓存穿透防护:在查询数据库之前,先用布隆过滤器判断这个数据ID是否可能存在。如果过滤器判断“肯定不存在”,则直接返回空结果,阻止请求到达数据库。


实战:在Redis中使用布隆过滤器

Redis通过模块的形式提供了布隆过滤器功能,最常见的模块是 RedisBloom

第一步:确保加载了RedisBloom模块。
检查 你的Redis服务是否已加载该模块。在 redis-cli 中执行以下命令:

redis-cli> MODULE LIST

如果输出中包含 bf,则表示模块已加载。否则,你需要先安装并配置Redis加载此模块。

第二步:创建布隆过滤器。
使用 BF.RESERVE 命令创建一个新的过滤器。你需要预设两个关键参数:

  • 错误率(error rate):允许的误判概率,例如 0.001(0.1%)。
  • 容量(capacity):预计存储的元素数量。
# 创建一个错误率为0.001,预计容量为100万的商品ID过滤器
redis-cli> BF.RESERVE product:bloom_filter 0.001 1000000

第三步:向过滤器中添加元素。
使用 BF.ADD 命令添加单个元素,或 BF.MADD 命令添加多个元素。

# 添加单个商品ID
redis-cli> BF.ADD product:bloom_filter "100001"

# 批量添加多个商品ID
redis-cli> BF.MADD product:bloom_filter "100002" "100003" "100004"

第四步:在查询缓存前,用过滤器进行检查。
编写 业务逻辑。在典型的查询流程中集成布隆过滤器检查。下面用伪代码描述:

def get_product_info(product_id):
    # 1. 先查询缓存
    cache_key = f"product:{product_id}"
    cached_data = redis.get(cache_key)
    if cached_data:
        return cached_data

    # 2. 查询布隆过滤器,判断ID是否可能存在于数据库
    # 使用 BF.EXISTS 命令检查
    is_probably_exist = redis.execute_command("BF.EXISTS", "product:bloom_filter", product_id)

    if not is_probably_exist:
        # 3. 布隆过滤器判断“肯定不存在”,直接返回,避免查询数据库
        return None

    # 4. 布隆过滤器判断“可能存在”,才去查询数据库
    db_data = database.query(f"SELECT * FROM products WHERE id = {product_id}")

    if db_data:
        # 5. 将数据存入缓存,并设置过期时间
        redis.setex(cache_key, 3600, db_data)

    return db_data

关键考量与最佳实践

理解 布隆过滤器的核心特性:它只能告诉你“可能存在”或“肯定不存在”。使用它必须接受一定的“假阳性”(False Positive)误判,即把不存在的元素误判为存在。这种情况下,请求还是会到达数据库,但相比缓存穿透,概率已大大降低。

计算 误判率与空间、哈希函数数量的关系。以下公式描述了在最优情况下(给定容量n,位数组大小m,哈希函数数量k),误判率 $p$ 的近似值:
$$ p \approx (1 - e^{-kn/m})^k $$
其中,$e$ 是自然对数的底。为了达到预期的错误率,在设计过滤器时,需要根据预计元素数量 n 和目标错误率 p,来估算需要的位数组大小 m 和哈希函数数量 kBF.RESERVE 命令会自动完成这些计算。

规划 过滤器的生命周期。

  • 元素只增不删:标准的布隆过滤器不支持删除元素。如果业务数据会物理删除,你需要定期用新数据重建过滤器(例如,每天凌晨用全量有效的商品ID集合重建一个新的 product:bloom_filter_v2,然后进行热切换)。
  • 容量预估:务必准确预估数据规模(capacity)。如果实际元素数量远超预设容量,误判率会急剧上升,失去防护意义。

选择 合适的错误率。

  • 错误率设置得越低(如 0.00001),需要的存储空间越大,哈希函数也越多,查询性能会略有下降
  • 对于缓存穿透防护,通常 0.001 (0.1%) 到 0.01 (1%) 是一个常见的权衡范围。这意味着每1000到100次误判,只有一次会放行请求到数据库,已能有效抵御大规模的恶意攻击。

进阶:使用Redisson客户端(Java示例)

使用 更高级的客户端库可以简化操作。以Java的Redisson为例,它提供了与业务对象更自然的集成方式。

RBloomFilter<String> bloomFilter = redisson.getBloomFilter("product:bloom_filter");
// 初始化:预计元素数量100万,误判率0.001
bloomFilter.tryInit(1000000L, 0.001);

// 添加数据
bloomFilter.add("100001");

// 在业务逻辑中检查
String productId = "999999";
if (bloomFilter.contains(productId)) {
    // 可能存在,继续后续流程(查缓存/DB)
} else {
    // 肯定不存在,直接返回
    return null;
}

注意:Redisson的 BloomFilter 接口底层依然是对Redis命令的封装,其核心原理与直接使用 BF.* 命令一致。使用客户端库主要提升了开发的便捷性和代码可读性。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文