欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot+Redis+布隆過濾器防止緩存穿透

 更新時(shí)間:2025年09月11日 09:50:18   作者:白侖色  
本文介紹了一個(gè)基于Spring Boot、Redis和Guava布隆過濾器的高并發(fā)系統(tǒng)緩存穿透解決方案,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

? 項(xiàng)目概述

在高并發(fā)系統(tǒng)中,緩存穿透 是一個(gè)經(jīng)典問題:當(dāng)惡意請(qǐng)求或業(yè)務(wù)邏輯查詢一個(gè)數(shù)據(jù)庫(kù)中不存在的 Key,由于緩存中也沒有,請(qǐng)求會(huì)直接打到數(shù)據(jù)庫(kù),導(dǎo)致數(shù)據(jù)庫(kù)壓力激增,甚至宕機(jī)。

本項(xiàng)目使用 Spring Boot + Redis + Guava 布隆過濾器 實(shí)現(xiàn)一個(gè)完整的解決方案,有效防止緩存穿透,提升系統(tǒng)穩(wěn)定性與性能。

?? 項(xiàng)目結(jié)構(gòu)

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  # 模擬商品數(shù)據(jù)
├── 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(簡(jiǎn)化代碼) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>

    <!-- CSV 解析(用于初始化數(shù)據(jù)) -->
    <dependency>
        <groupId>com.opencsv</groupId>
        <artifactId>opencsv</artifactId>
        <version>5.7.1</version>
    </dependency>
</dependencies>

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

server:
  port: 8080

spring:
  redis:
    host: localhost
    port: 6379
    password: 
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
    timeout: 5s

  # Redis 序列化配置(可選)
  cache:
    type: redis

?? 第三步:創(chuàng)建商品實(shí)體類

// 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 {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    // 布隆過濾器(存儲(chǔ)所有存在的商品ID)
    private BloomFilter<String> bloomFilter;

    // Redis Key
    private static final String BLOOM_FILTER_KEY = "bloom:products";

    @Bean
    public BloomFilter<String> bloomFilter() {
        // 預(yù)估元素?cái)?shù)量:10萬(wàn),誤判率:0.01%
        this.bloomFilter = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8), 100000, 0.0001);
        return bloomFilter;
    }

    /**
     * 項(xiàng)目啟動(dòng)時(shí)初始化布隆過濾器
     * 實(shí)際項(xiàng)目中可從數(shù)據(jù)庫(kù)或緩存中加載所有存在的 ID
     */
    @PostConstruct
    public void initBloomFilter() {
        // 模擬:從數(shù)據(jù)庫(kù)加載所有商品ID
        for (int i = 1; i <= 10000; i++) {
            String productId = "P" + i;
            bloomFilter.put(productId);
            // 同時(shí)將真實(shí)數(shù)據(jù)存入 Redis(模擬緩存)
            stringRedisTemplate.opsForValue().set("product:" + productId, "Product Data " + productId);
        }
        System.out.println("? 布隆過濾器初始化完成,加載 10000 個(gè)商品ID");
    }

    /**
     * 手動(dòng)添加新商品到布隆過濾器(可選)
     */
    public void addProductToBloom(String productId) {
        bloomFilter.put(productId);
        // 異步更新 Redis(或持久化到 DB)
        stringRedisTemplate.opsForValue().set("product:" + productId, "New Product Data");
    }
}

?? 第五步:商品服務(wù)層

// 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 {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedisBloomFilterConfig bloomFilterConfig;

    /**
     * 查詢商品信息(帶布隆過濾器防護(hù))
     * @param productId
     * @return 商品信息或 null
     */
    public String getProduct(String productId) {
        // 1. 先通過布隆過濾器判斷是否存在
        if (!bloomFilterConfig.bloomFilter.mightContain(productId)) {
            System.out.println("? 布隆過濾器判定:商品ID " + productId + " 不存在(可能誤判)");
            return null; // 直接返回,避免查緩存和數(shù)據(jù)庫(kù)
        }

        // 2. 布隆過濾器認(rèn)為可能存在,查 Redis 緩存
        String cacheKey = "product:" + productId;
        String productData = stringRedisTemplate.opsForValue().get(cacheKey);
        if (productData != null) {
            System.out.println("? Redis 緩存命中:" + productId);
            return productData;
        }

        // 3. 緩存未命中,查數(shù)據(jù)庫(kù)(此處模擬)
        String dbData = queryFromDatabase(productId);
        if (dbData != null) {
            // 4. 寫入緩存(設(shè)置過期時(shí)間)
            stringRedisTemplate.opsForValue().set(cacheKey, dbData, 30, java.util.concurrent.TimeUnit.MINUTES);
            System.out.println("?? 數(shù)據(jù)庫(kù)查詢并寫入緩存:" + productId);
            return dbData;
        } else {
            // 5. 數(shù)據(jù)庫(kù)也不存在,可選擇緩存空值(防緩存穿透二次攻擊)
            // stringRedisTemplate.opsForValue().set(cacheKey, "", 1, TimeUnit.MINUTES);
            System.out.println("? 數(shù)據(jù)庫(kù)查詢失?。荷唐稩D " + productId + " 不存在");
            return null;
        }
    }

    /**
     * 模擬數(shù)據(jù)庫(kù)查詢
     */
    private String queryFromDatabase(String productId) {
        // 模擬:只有 P1 ~ P10000 存在
        try {
            Thread.sleep(10); // 模擬數(shù)據(jù)庫(kù)延遲
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        if (productId.matches("P\\d{1,5}") && Integer.parseInt(productId.substring(1)) <= 10000) {
            return "【數(shù)據(jù)庫(kù)】商品詳情 - " + 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 {

    @Autowired
    private ProductService productService;

    /**
     * 查詢商品信息
     * 測(cè)試正常請(qǐng)求:http://localhost:8080/product/P123
     * 測(cè)試穿透請(qǐng)求: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 "商品不存在,耗時(shí):" + cost + "ms";
        }
        return result + "(耗時(shí):" + cost + "ms)";
    }
}

?? 第七步:?jiǎn)?dòng)類

// 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 + 布隆過濾器 項(xiàng)目啟動(dòng)成功!");
        System.out.println("?? 訪問測(cè)試:http://localhost:8080/product/P123");
        System.out.println("?? 穿透測(cè)試:http://localhost:8080/product/P999999");
    }
}

?? 第八步:測(cè)試與驗(yàn)證

1. 啟動(dòng)項(xiàng)目

確保 Redis 服務(wù)已運(yùn)行,然后啟動(dòng) Spring Boot 項(xiàng)目。

2. 正常請(qǐng)求(緩存命中)

http://localhost:8080/product/P123

輸出

【數(shù)據(jù)庫(kù)】商品詳情 - P123(耗時(shí):15ms)
# 第二次請(qǐng)求
商品詳情 - P123(耗時(shí):2ms) # Redis 緩存命中

3. 緩存穿透請(qǐng)求(布隆過濾器攔截)

http://localhost:8080/product/P999999

輸出

商品不存在,耗時(shí):1ms

? 關(guān)鍵點(diǎn):該請(qǐng)求未進(jìn)入緩存查詢,也未訪問數(shù)據(jù)庫(kù),直接被布隆過濾器攔截,耗時(shí)極低。

? 方案優(yōu)勢(shì)總結(jié)

優(yōu)勢(shì)說明
? 高效攔截不存在的 Key 被布隆過濾器快速攔截,避免查緩存和數(shù)據(jù)庫(kù)
?? 內(nèi)存友好布隆過濾器空間效率高,10萬(wàn)數(shù)據(jù)僅需幾十 KB
??? 高并發(fā)防護(hù)有效防止惡意刷不存在的 Key 導(dǎo)致數(shù)據(jù)庫(kù)雪崩
?? 可擴(kuò)展支持動(dòng)態(tài)添加新數(shù)據(jù)(如新增商品)

?? 注意事項(xiàng)與優(yōu)化建議

  1. 誤判率權(quán)衡:布隆過濾器有誤判率(False Positive),但不會(huì)漏判??筛鶕?jù)業(yè)務(wù)調(diào)整大小和誤判率。
  2. 數(shù)據(jù)一致性:當(dāng)數(shù)據(jù)庫(kù)新增數(shù)據(jù)時(shí),需同步更新布隆過濾器。
  3. 替代方案:也可使用 Redis 自帶的 RedisBloom 模塊(需編譯安裝),支持 BF.ADD、BF.EXISTS 等命令。
  4. 緩存空值:對(duì)于高頻但不存在的 Key,可結(jié)合“緩存空值 + 短 TTL”進(jìn)一步優(yōu)化。

?? 推薦閱讀

到此這篇關(guān)于SpringBoot+Redis+布隆過濾器防止緩存穿透的文章就介紹到這了,更多相關(guān)SpringBoot Redis 布隆過濾器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家! 

相關(guān)文章

最新評(píng)論