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.pswp.cn/bicheng/93336.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/93336.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电子电路学习日记

这里的 K 表示 千欧&#xff08;kilo-ohm&#xff09;&#xff0c;而 F 在很多国产 EDA 软件&#xff08;比如立创EDA、Altium 的一些中文封装库&#xff09;里用来标注精度&#xff08;公差&#xff09;&#xff0c; F 代表 1% 精度&#xff08;英文 Fine tolerance&#xff0…

oracle 怎么实现读一致性

​ Oracle 数据块读一致性判断流程&#xff08;正确版&#xff09; 假设&#xff1a;Query SCN 查询开始的 SCN&#xff08;Query SCN&#xff09; lastSubbmit SCN 行中最新的提交scn Row SCN 行最后修改的 SCN&#xff08;存储在行头&#xff0c;通过 ITL 推导&#xff09…

ISTA为什么要加上软阈值激活函数?r若没有L1 正则化也要加其他激活函数吗?

一、加上软阈值函数&#xff08;Soft-thresholding&#xff09;是因为 LISTA&#xff08;以及它的前身 ISTA&#xff09;本质上是在求解一个 带 L1 正则化的稀疏优化问题&#xff1a; min⁡x12∥y−Ax∥22λ∥x∥1 \min_x \frac{1}{2} \|y - Ax\|_2^2 \lambda \|x\|_1 xmin​2…

线程P4 | 线程安全问题及解决方法

何为线程安全&#xff1f;要谈及何为线程安全&#xff0c;总得说来&#xff0c;我们可以用一句话来概况&#xff1a;如果在多线程环境下代码运行结果和我们预期是相符的&#xff0c;即和单线程环境下的运行结果相同&#xff0c;那么我们就称这个程序是线程安全的&#xff0c;反…

水印消失术!JavaAI深度学习去水印技术深度剖析

一、飞算JavaAI平台概述1.1 飞算JavaAI定位与技术特色 飞算JavaAI是国内领先的智能化Java开发平台&#xff0c;通过AI技术赋能软件开发全流程&#xff0c;特别针对小程序、Web应用等轻量级开发场景提供*零基础编程→高质量交**的一站式解决方案。其核心优势体现在&#xff1a; …

醋酸钆:医学影像与科技创新中的重要角色

醋酸钆是一种由钆元素和醋酸根离子组成的化合物。钆是稀土金属之一&#xff0c;常常用于医学影像、核磁共振成像&#xff08;MRI&#xff09;以及某些工业应用。醋酸钆作为钆的盐之一&#xff0c;具有许多独特的性质&#xff0c;尤其在医学和科学研究领域表现突出。一、醋酸钆的…

插入排序专栏

插入排序&#xff08;Insertion Sort&#xff09;是一种简单直观的排序算法&#xff0c;其思想源于我们日常生活中整理扑克牌的方式。本文将详细解析插入排序的工作原理&#xff0c;通过 Java 实现代码进行分析&#xff0c;深入探讨其时间复杂度的计算过程&#xff0c;并阐述其…

高效Unicode字符表示:一种创新的词表构建策略分析

在自然语言处理中&#xff0c;处理多语言和特殊字符的表示始终是一项挑战。本文将分析一种创新的词表构建策略&#xff0c;该策略通过数学优化和双token机制&#xff0c;在保持词表紧凑的同时实现了对Unicode字符的全面覆盖。 词表构建的核心逻辑 该策略包含四个关键步骤&#…

python与物联网基础知识

软件准备&#xff1a;软件&#xff1a;thonny-4.0.1-windows-portable(win10,11系统64位)驱动&#xff1a;CP210x_Windows_Drivers固件&#xff1a;esp8266-1m-20220618-v1.19.1.bin物料准备&#xff1a;面包板、开发板、电源线一、安装与调试&#xff1a;1.在软件文件中找到th…

SVN提交服务器拒绝访问的问题

SVN提交服务器拒绝访问的问题 介绍 分析 1.服务器的SVN没有开启 2.服务器的网络端口除了问题没有开放端口 3.客户端的SVN配置除了问题刷新一下数据 4.客户端的SVN重装 找原因 1.初步以为是**防火墙**的问题 2.网络运营商的问题 总结 介绍 SVN相信大家都用过,今天反馈一个比较…

【Linux】库制作与原理

前言 本篇博客我们来认识下库方面的知识 &#x1f493; 个人主页&#xff1a;zkf ⏩ 文章专栏&#xff1a;Linux 若有问题 评论区见&#x1f4dd; &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐文章 目录 1.什么是库 2.静态库 2.1静态库的生成 2.2静态库的使用 3.动态库 …

Android ADB 常用指令全解析

ADB&#xff08;Android Debug Bridge&#xff09;是 Android 开发和测试不可或缺的调试工具&#xff0c;它建立了电脑与 Android 设备之间的通信桥梁&#xff0c;通过命令行指令可实现对设备的全方位控制。掌握 ADB 指令能大幅提升开发效率&#xff0c;解决各类调试难题。本文…

使用 Rust 创建 32 位 DLL 的完整指南

使用 Rust 创建 32 位 DLL 的完整指南 在 Rust 中创建 32 位 DLL 需要特定的工具链配置和编译选项。以下是详细步骤和最佳实践&#xff1a; 环境准备 1. 安装 Rust 工具链 # 安装 Rust curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh# 安装 32 位目标 rustu…

算法基础 第3章 数据结构

1.单调栈 1.什么是单调栈 单调栈&#xff0c;即具有单调性的栈。 实现 #include <iostream> #include <stack> using namespace std; const int N 3e6 10; int a[N], n; void test1() {stack<int> st; // 维护⼀个单调递增的栈for(int i 1; i < n; i…

[机器学习]08-基于逻辑回归模型的鸢尾花数据集分类

使用sklearn的LogisticRegression多分类模型程序代码&#xff1a;import numpy as np from sklearn.linear_model import LogisticRegression import matplotlib.pyplot as plt import matplotlib as mpl from sklearn import datasets from sklearn import preprocessing impo…

【STM32入门教程】stm32简介

一、STM32简介二、ARM三、stm32f103c8t6四、命名规则五、系统结构六、引脚定义七、启动配置一般情况下&#xff0c;都是在flash开始程序&#xff0c;而启动程序也可以进行配置在其他地方启动程序&#xff0c;通过配置boot0和boot1来进行配置八、最小系统电路

SAE J2716多协议网关的硬件架构与实时协议转换机制解析

本文解析符合SAE J2716标准的工业级协议转换设备技术架构&#xff0c;通过拆解其四路双向SENT通道与多总线&#xff08;CANFD/Ethernet/USB&#xff09;的实时交互机制、MicroSD独立日志系统设计及模拟量动态映射方案&#xff0c;为汽车电子与工业通信开发者提供可复用的技术参…

VS2022+QT5.15.2+OCCT7.9.1的开发环境搭建流程

以下是VS2022 QT5.15.2 OCCT7.9.1开发环境搭建的完整流程&#xff1a; 一、安装Visual Studio 2022 下载安装程序 访问VS官网下载Community版安装组件 选择"使用C的桌面开发"工作负载勾选&#xff1a; MSVC v143 - VS 2022 C x64/x86生成工具Windows 10 SDK (建议…

数据库访问模式详解

数据库访问模式详解数据库访问模式是软件架构中数据访问层&#xff08;Data Access Layer&#xff09;设计的核心&#xff0c;它定义了应用程序如何与数据库进行交互的策略和方法。选择合适的访问模式对于系统的性能、可维护性、可扩展性、事务一致性和开发效率至关重要。不同的…

BGE向量算法

一、是什么 什么是BGE向量算法&#xff1f;先说说网上的概念吧。本文不讲解太深的算法知识&#xff0c;主要讲解如何用&#xff01; BGE&#xff08;BAAI General Embedding&#xff09;是北京智源研究院开源的“通用语义向量模型”。一句话&#xff1a;把中文或英文句子变成…