当前位置: 首页 > news >正文

Spring Boot + Redis + 布隆过滤器防止缓存穿透

✅ 项目概述

在高并发系统中,缓存穿透 是一个经典问题:当恶意请求或业务逻辑查询一个数据库中不存在的 Key,由于缓存中也没有,请求会直接打到数据库,导致数据库压力激增,甚至宕机。

本项目使用 Spring Boot + Redis + Guava 布隆过滤器 实现一个完整的解决方案,有效防止缓存穿透,提升系统稳定性与性能。


📌 项目结构

bloom-filter-demo/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/bloomfilterdemo/
│   │   │       ├── controller/
│   │   │       │   └── ProductController.java
│   │   │       ├── service/
│   │   │       │   └── ProductService.java
│   │   │       ├── config/
│   │   │       │   └── RedisBloomFilterConfig.java
│   │   │       └── BloomFilterDemoApplication.java
│   │   └── resources/
│   │       ├── application.yml
│   │       └── data/products.csv  # 模拟商品数据
├── pom.xml
└── README.md

在这里插入图片描述


📌 第一步:添加 Maven 依赖

<!-- pom.xml -->
<dependencies><!-- Spring Boot Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Spring Boot Data Redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- Guava(提供 BloomFilter) --><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>32.1.3-jre</version></dependency><!-- Lombok(简化代码) --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency><!-- CSV 解析(用于初始化数据) --><dependency><groupId>com.opencsv</groupId><artifactId>opencsv</artifactId><version>5.7.1</version></dependency>
</dependencies>

📌 第二步:配置文件(application.yml)

server:port: 8080spring:redis:host: localhostport: 6379password: lettuce:pool:max-active: 8max-idle: 8timeout: 5s# Redis 序列化配置(可选)cache:type: redis

📌 第三步:创建商品实体类

// src/main/java/com/example/bloomfilterdemo/entity/Product.java
package com.example.bloomfilterdemo.entity;import lombok.Data;@Data
public class Product {private String id;private String name;private Double price;private String category;
}

📌 第四步:配置布隆过滤器与 Redis

// src/main/java/com/example/bloomfilterdemo/config/RedisBloomFilterConfig.java
package com.example.bloomfilterdemo.config;import com.google.common.hash.Funnels;
import com.google.common.util.concurrent.Uninterruptibles;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import com.google.common.hash.BloomFilter;import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;@Configuration
public class RedisBloomFilterConfig {@Autowiredprivate StringRedisTemplate stringRedisTemplate;// 布隆过滤器(存储所有存在的商品ID)private BloomFilter<String> bloomFilter;// Redis Keyprivate static final String BLOOM_FILTER_KEY = "bloom:products";@Beanpublic BloomFilter<String> bloomFilter() {// 预估元素数量:10万,误判率:0.01%this.bloomFilter = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8), 100000, 0.0001);return bloomFilter;}/*** 项目启动时初始化布隆过滤器* 实际项目中可从数据库或缓存中加载所有存在的 ID*/@PostConstructpublic void initBloomFilter() {// 模拟:从数据库加载所有商品IDfor (int i = 1; i <= 10000; i++) {String productId = "P" + i;bloomFilter.put(productId);// 同时将真实数据存入 Redis(模拟缓存)stringRedisTemplate.opsForValue().set("product:" + productId, "Product Data " + productId);}System.out.println("✅ 布隆过滤器初始化完成,加载 10000 个商品ID");}/*** 手动添加新商品到布隆过滤器(可选)*/public void addProductToBloom(String productId) {bloomFilter.put(productId);// 异步更新 Redis(或持久化到 DB)stringRedisTemplate.opsForValue().set("product:" + productId, "New Product Data");}
}

📌 第五步:商品服务层

// src/main/java/com/example/bloomfilterdemo/service/ProductService.java
package com.example.bloomfilterdemo.service;import com.example.bloomfilterdemo.config.RedisBloomFilterConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;@Service
public class ProductService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Autowiredprivate RedisBloomFilterConfig bloomFilterConfig;/*** 查询商品信息(带布隆过滤器防护)* @param productId* @return 商品信息或 null*/public String getProduct(String productId) {// 1. 先通过布隆过滤器判断是否存在if (!bloomFilterConfig.bloomFilter.mightContain(productId)) {System.out.println("❌ 布隆过滤器判定:商品ID " + productId + " 不存在(可能误判)");return null; // 直接返回,避免查缓存和数据库}// 2. 布隆过滤器认为可能存在,查 Redis 缓存String cacheKey = "product:" + productId;String productData = stringRedisTemplate.opsForValue().get(cacheKey);if (productData != null) {System.out.println("✅ Redis 缓存命中:" + productId);return productData;}// 3. 缓存未命中,查数据库(此处模拟)String dbData = queryFromDatabase(productId);if (dbData != null) {// 4. 写入缓存(设置过期时间)stringRedisTemplate.opsForValue().set(cacheKey, dbData, 30, java.util.concurrent.TimeUnit.MINUTES);System.out.println("📦 数据库查询并写入缓存:" + productId);return dbData;} else {// 5. 数据库也不存在,可选择缓存空值(防缓存穿透二次攻击)// stringRedisTemplate.opsForValue().set(cacheKey, "", 1, TimeUnit.MINUTES);System.out.println("❌ 数据库查询失败:商品ID " + productId + " 不存在");return null;}}/*** 模拟数据库查询*/private String queryFromDatabase(String productId) {// 模拟:只有 P1 ~ P10000 存在try {Thread.sleep(10); // 模拟数据库延迟} catch (InterruptedException e) {Thread.currentThread().interrupt();}if (productId.matches("P\\d{1,5}") && Integer.parseInt(productId.substring(1)) <= 10000) {return "【数据库】商品详情 - " + productId;}return null;}
}

📌 第六步:控制器层

// src/main/java/com/example/bloomfilterdemo/controller/ProductController.java
package com.example.bloomfilterdemo.controller;import com.example.bloomfilterdemo.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;@RestController
public class ProductController {@Autowiredprivate ProductService productService;/*** 查询商品信息* 测试正常请求:http://localhost:8080/product/P123* 测试穿透请求:http://localhost:8080/product/P999999*/@GetMapping("/product/{id}")public String getProduct(@PathVariable String id) {long start = System.currentTimeMillis();String result = productService.getProduct(id);long cost = System.currentTimeMillis() - start;if (result == null) {return "商品不存在,耗时:" + cost + "ms";}return result + "(耗时:" + cost + "ms)";}
}

📌 第七步:启动类

// src/main/java/com/example/bloomfilterdemo/BloomFilterDemoApplication.java
package com.example.bloomfilterdemo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class BloomFilterDemoApplication {public static void main(String[] args) {SpringApplication.run(BloomFilterDemoApplication.class, args);System.out.println("🚀 Spring Boot + Redis + 布隆过滤器 项目启动成功!");System.out.println("🎯 访问测试:http://localhost:8080/product/P123");System.out.println("🎯 穿透测试:http://localhost:8080/product/P999999");}
}

📌 第八步:测试与验证

1. 启动项目

确保 Redis 服务已运行,然后启动 Spring Boot 项目。

2. 正常请求(缓存命中)

http://localhost:8080/product/P123

输出

【数据库】商品详情 - P123(耗时:15ms)
# 第二次请求
商品详情 - P123(耗时:2ms) # Redis 缓存命中

3. 缓存穿透请求(布隆过滤器拦截)

http://localhost:8080/product/P999999

输出

商品不存在,耗时:1ms

关键点:该请求未进入缓存查询,也未访问数据库,直接被布隆过滤器拦截,耗时极低。


✅ 方案优势总结

优势说明
高效拦截不存在的 Key 被布隆过滤器快速拦截,避免查缓存和数据库
💾 内存友好布隆过滤器空间效率高,10万数据仅需几十 KB
🛡️ 高并发防护有效防止恶意刷不存在的 Key 导致数据库雪崩
🔄 可扩展支持动态添加新数据(如新增商品)

📚 注意事项与优化建议

  1. 误判率权衡:布隆过滤器有误判率(False Positive),但不会漏判。可根据业务调整大小和误判率。
  2. 数据一致性:当数据库新增数据时,需同步更新布隆过滤器。
  3. 替代方案:也可使用 Redis 自带的 RedisBloom 模块(需编译安装),支持 BF.ADDBF.EXISTS 等命令。
  4. 缓存空值:对于高频但不存在的 Key,可结合“缓存空值 + 短 TTL”进一步优化。

📚 推荐阅读

  • Guava BloomFilter 官方文档
http://www.xdnf.cn/news/1297351.html

相关文章:

  • 带root权限_贝尔RG020ET-CA融合终端S905L处理器当贝纯净版刷机教程
  • 分布式系统架构设计模式:从微服务到云原生
  • pycharm远程连接服务器跑实验详细操作
  • Go语言实战案例:简易图像验证码生成
  • Java 设计模式-组合模式
  • Vscode的wsl环境开发ESP32S3的一些问题总结
  • 在 Windows 系统中解决 Git 推送时出现的 Permission denied (publickey) 错误,请按照以下详细步骤操作:
  • 宋红康 JVM 笔记 Day01|JVM介绍
  • [工具]vscode 使用AI 优化代码
  • 使用EvalScope对GPT-OSS-20B进行推理性能压测实战
  • 【完整源码+数据集+部署教程】肾脏病变实例分割系统源码和数据集:改进yolo11-CARAFE
  • 自动化运维实验(二)---自动识别设备,并导出配置
  • AM32电调学习-使用Keil编译uboot
  • 搭建局域网yum源仓库全流程
  • 华为实验 链路聚合
  • GoLand 项目从 0 到 1:第八天 ——GORM 命名策略陷阱与 Go 项目启动慢问题攻坚
  • 更新pip及Python软件包的完整指南
  • STM32HAL 快速入门(七):GPIO 输入之光敏传感器控制蜂鸣器
  • 第3节 深度学习避坑指南:从过拟合到玄学优化
  • 92、23种设计模式-单例模式
  • 【软考架构】信息安全基础知识
  • 考研408《计算机组成原理》复习笔记,第五章(1)——CPU功能和结构
  • 云原生存储架构设计与性能优化
  • 【深度学习计算性能】04:硬件
  • CTFSHOW | nodejs题解 web334 - web344
  • 主进程如何将客户端连接分配到房间进程
  • 数巅中标中建科技AI知识库项目,开启建筑业数智化新篇章
  • 项目日志框架与jar中日志框架冲突 解决
  • MFC的使用——使用ChartCtrl绘制曲线
  • DataHub IoT Gateway:工业现场设备与云端平台安全互联的高效解决方案