真实场景:防止缓存穿透 —— 使用 Redisson 布隆过滤器

🌐 真实场景:防止缓存穿透 ------ 使用 Redisson 布隆过滤器


一、问题背景:什么是缓存穿透?

缓存穿透 是指:

  • 用户(或恶意请求)频繁查询一个数据库中根本不存在的数据 ,比如商品 ID 为 -1、9999999 这种根本不存在的数据。
  • 由于缓存中没有这个 key,请求会直接打到数据库。
  • 如果这样的请求量非常大,数据库就会承受大量无效查询,可能导致 数据库压力过大甚至宕机。

二、传统解决办法有哪些不足?

常见的解决办法有:

  1. 缓存空对象: 即使数据库查不到,也把空结果(比如 null)缓存起来,并设置较短的过期时间。

    • ✅ 能挡一部分,但依然有无效查询,且对于大量不存在的 key,缓存也无效。
    • ❌ 对于恶意构造的大量不存在 key 仍然无效,比如攻击者不断请求随机 ID。
  2. 接口限流、黑白名单:

    • 可以挡一部分恶意流量,但无法从根本上解决大量正常业务中存在的无效查询问题。

三、更好的方案:布隆过滤器 + 缓存

我们可以在缓存层之前加一道 布隆过滤器 的屏障,它的作用是:

在查询缓存或数据库之前,先判断这个请求的 ID(比如商品ID、用户ID)是否可能存在于我们的系统中。如果布隆过滤器告诉我们 "这个 ID 一定不存在",那就直接返回,不去查缓存,也不查数据库!


✅ 具体真实案例:电商系统商品查询防穿透

场景描述:

假设你有一个 电商系统,用户可以通过商品 ID 查询商品详情,比如:

复制代码
GET /product/{productId}
  • 正常的商品 ID 是 1、2、3... 100000(存储在数据库中)。
  • 但是有一些用户(或爬虫/攻击者)会尝试访问一些 根本不存在的商品 ID,比如 99999999、-1、随机字符串数字等。
  • 这些请求会穿过缓存,直接打到数据库,影响性能。

解决方案:使用 Redisson 布隆过滤器进行前置拦截

步骤 1:系统启动时,将所有合法的商品 ID 加入布隆过滤器

比如系统中有 10 万件商品,商品 ID 是从 1 到 100000(或者是数据库中的真实 ID)。

你可以在 系统启动时 (比如 Spring Boot 的 CommandLineRunner 或 @PostConstruct),将所有有效的商品 ID 加入布隆过滤器:

java 复制代码
// 假设你从数据库中查询出了所有有效的商品 ID 列表
List<Long> validProductIds = productService.getAllValidProductIds(); // [1, 2, 3, ..., 100000]

// 初始化 Redisson 客户端
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);

// 获取布隆过滤器,命名为 "productBloomFilter"
RBloomFilter<Long> productBloomFilter = redisson.getBloomFilter("productBloomFilter");

// 预期插入 10 万条,可接受误判率 0.01(1%)
productBloomFilter.tryInit(100000L, 0.01);

// 将所有合法的商品 ID 加入布隆过滤器
for (Long productId : validProductIds) {
    productBloomFilter.add(productId);
}

⚠️ 注意:你也可以不一次性加载所有 ID,而是随着商品创建/导入时,实时往布隆过滤器里添加。但务必保证 所有有效 ID 都加入到了布隆过滤器中。


步骤 2:在查询商品接口处,先经过布隆过滤器拦截

当用户请求 /product/{productId} 时,后端逻辑如下:

java 复制代码
@GetMapping("/product/{productId}")
public ResponseEntity<Product> getProduct(@PathVariable Long productId) {
    RBloomFilter<Long> productBloomFilter = redissonClient.getBloomFilter("productBloomFilter");

    // 第一步:先判断该商品 ID 是否可能存在于布隆过滤器中
    if (!productBloomFilter.contains(productId)) {
        // 布隆过滤器说:"这个商品 ID 一定不存在!"
        log.warn("请求了不存在的商品 ID: {}", productId);
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
    }

    // 第二步:布隆过滤器说"可能存在",继续查询缓存或数据库
    Product product = productService.getProductFromCacheOrDB(productId);

    if (product == null) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
    }

    return ResponseEntity.ok(product);
}

四、这样做有什么好处?

优点 说明
✅ 阻挡大量无效请求 对于那些 根本不存在的商品 ID ,布隆过滤器能够快速判断并拦截,避免查询缓存和数据库,极大减轻后端压力。
✅ 性能极高 布隆过滤器是基于 Redis 的位操作,判断是否存在的时间复杂度是 O(1),非常快。
✅ 分布式支持 多个服务实例共享同一个 Redis 中的布隆过滤器,所有服务都能统一判断 ID 是否合法,避免各自为政。
✅ 内存占用小 相比缓存所有不存在的 key,布隆过滤器用极小的内存开销,就能管理上百万级别的数据存在性。
✅ 防恶意攻击 对于爬虫、恶意用户发起的大量随机 ID 请求,可以有效防御。

五、布隆过滤器的误判怎么办?

布隆过滤器是 概率型 的,它可能出现 误判(false positive),即:

布隆过滤器说 "这个商品 ID 可能存在",但实际上它不存在。

但请注意:

  • 它绝不会漏判(false negative):如果布隆过滤器说 "不存在",那这个 ID 100% 不存在。
  • 所以我们的策略是:
    • 如果布隆过滤器判断 "不存在" → 直接拦截,不查缓存/DB
    • 如果布隆过滤器判断 "可能存在" → 继续走正常流程:查缓存 → 查 DB

即使有 少量误判(比如误认为 1000 个不存在的 ID 可能存在) ,也只是会导致 多查一次缓存/DB,但不会对系统造成大的影响,而且误判率可以控制得非常低(比如 1% 甚至更低)。


🧠 总结:这个真实例子教会我们什么?

场景 解决方案 工具/技术
大量用户请求不存在的数据(缓存穿透) 在查询缓存/DB 前,先用布隆过滤器判断 ID 是否可能存在 Redisson Bloom Filter + Redis
目的 避免无效查询,保护数据库,提高系统稳定性 前置过滤器机制
优势 高性能、低内存、分布式一致、易集成 布隆过滤器 + Redis + Redisson
相关推荐
2301_32241428043 小时前
基于SpringBoot+Vue的休闲零食超市管理系统的设计与实现59004-计算机毕设 原创(免费领源码+部署教程+开发环境)
java·vue.js·spring boot·redis·mysql·决策树·推荐算法
catoop5 小时前
API Key 设计与安全生成方案:结构拆解、数据库设计与 Java 实现
java·数据库·安全
Web极客码5 小时前
我用 Claude 分析 Redis 哨兵日志,实现跨机房数据不一致自动对齐
数据库·redis·php
库库的数据库笔记6 小时前
国产信创数据库的MySQL兼容性深度对比解析
大数据·数据库·mysql·oceanbase·政务
microrain6 小时前
SagooIoT 的缓存换后端只改一行配置,文件后端连 Redis 都不需要
物联网·缓存·golang·sagooiot
姚远Oracle ACE6 小时前
为什么无法连接到 PostgreSQL?
数据库·postgresql
ss2736 小时前
AI时代首先数据库-PostgreSQL实战笔记 | 22 PostgreSQL实例与集群
数据库·笔记·postgresql
ss2737 小时前
AI时代首先数据库-PostgreSQL实战笔记 | 21 PostgreSQL进程与内存架构介绍
数据库·笔记·postgresql
Henry-SAP8 小时前
SAP PP模块全景概览
数据库