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
英文地址,請注明出處:http://en.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;把中文或英文句子變成…