欢迎光临
我们一直在努力

缓存穿透/击穿/雪崩: 三种场景一次性解决,附布隆过滤器+互斥锁代码

缓存穿透/击穿/雪崩: 三种场景一次性解决,附布隆过滤器+互斥锁代码

引言: 缓存三大灾难的完整防御体系

在面试中,缓存穿透、缓存击穿、缓存雪崩是三个必问的高频考点。很多人能说出它们的定义和区别,但当面试官追问"布隆过滤器如何实现?""互斥锁怎样防止击穿?""缓存雪崩的真实案例?"时,就捉襟见肘了。

本节将系统剖析这三种异常场景的成因、区别、解决方案,并提供可直接用于生产的布隆过滤器和互斥锁代码实现,让你一次性彻底掌握缓存三大问题的完整防御体系。

一、三大场景全景区分

1.1 问题定义与对比

#publish-mermaid-1784873931460-0{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#publish-mermaid-1784873931460-0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#publish-mermaid-1784873931460-0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#publish-mermaid-1784873931460-0 .error-icon{fill:#552222;}#publish-mermaid-1784873931460-0 .error-text{fill:#552222;stroke:#552222;}#publish-mermaid-1784873931460-0 .edge-thickness-normal{stroke-width:1px;}#publish-mermaid-1784873931460-0 .edge-thickness-thick{stroke-width:3.5px;}#publish-mermaid-1784873931460-0 .edge-pattern-solid{stroke-dasharray:0;}#publish-mermaid-1784873931460-0 .edge-thickness-invisible{stroke-width:0;fill:none;}#publish-mermaid-1784873931460-0 .edge-pattern-dashed{stroke-dasharray:3;}#publish-mermaid-1784873931460-0 .edge-pattern-dotted{stroke-dasharray:2;}#publish-mermaid-1784873931460-0 .marker{fill:#333333;stroke:#333333;}#publish-mermaid-1784873931460-0 .marker.cross{stroke:#333333;}#publish-mermaid-1784873931460-0 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#publish-mermaid-1784873931460-0 p{margin:0;}#publish-mermaid-1784873931460-0 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#publish-mermaid-1784873931460-0 .cluster-label text{fill:#333;}#publish-mermaid-1784873931460-0 .cluster-label span{color:#333;}#publish-mermaid-1784873931460-0 .cluster-label span p{background-color:transparent;}#publish-mermaid-1784873931460-0 .label text,#publish-mermaid-1784873931460-0 span{fill:#333;color:#333;}#publish-mermaid-1784873931460-0 .node rect,#publish-mermaid-1784873931460-0 .node circle,#publish-mermaid-1784873931460-0 .node ellipse,#publish-mermaid-1784873931460-0 .node polygon,#publish-mermaid-1784873931460-0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1784873931460-0 .rough-node .label text,#publish-mermaid-1784873931460-0 .node .label text,#publish-mermaid-1784873931460-0 .image-shape .label,#publish-mermaid-1784873931460-0 .icon-shape .label{text-anchor:middle;}#publish-mermaid-1784873931460-0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#publish-mermaid-1784873931460-0 .rough-node .label,#publish-mermaid-1784873931460-0 .node .label,#publish-mermaid-1784873931460-0 .image-shape .label,#publish-mermaid-1784873931460-0 .icon-shape .label{text-align:center;}#publish-mermaid-1784873931460-0 .node.clickable{cursor:pointer;}#publish-mermaid-1784873931460-0 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#publish-mermaid-1784873931460-0 .arrowheadPath{fill:#333333;}#publish-mermaid-1784873931460-0 .edgePath .path{stroke:#333333;stroke-width:1px;}#publish-mermaid-1784873931460-0 .flowchart-link{stroke:#333333;fill:none;}#publish-mermaid-1784873931460-0 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-1784873931460-0 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#publish-mermaid-1784873931460-0 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-1784873931460-0 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#publish-mermaid-1784873931460-0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#publish-mermaid-1784873931460-0 .cluster text{fill:#333;}#publish-mermaid-1784873931460-0 .cluster span{color:#333;}#publish-mermaid-1784873931460-0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#publish-mermaid-1784873931460-0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#publish-mermaid-1784873931460-0 rect.text{fill:none;stroke-width:0;}#publish-mermaid-1784873931460-0 .icon-shape,#publish-mermaid-1784873931460-0 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-1784873931460-0 .icon-shape p,#publish-mermaid-1784873931460-0 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#publish-mermaid-1784873931460-0 .icon-shape .label rect,#publish-mermaid-1784873931460-0 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-1784873931460-0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#publish-mermaid-1784873931460-0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#publish-mermaid-1784873931460-0 .node .neo-node{stroke:#9370DB;}#publish-mermaid-1784873931460-0 [data-look=\”neo\”].node rect,#publish-mermaid-1784873931460-0 [data-look=\”neo\”].cluster rect,#publish-mermaid-1784873931460-0 [data-look=\”neo\”].node polygon{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1784873931460-0 [data-look=\”neo\”].swimlane.cluster rect{filter:none;}#publish-mermaid-1784873931460-0 [data-look=\”neo\”].node path{stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1784873931460-0 [data-look=\”neo\”].node .outer-path{filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1784873931460-0 [data-look=\”neo\”].node .neo-line path{stroke:#9370DB;filter:none;}#publish-mermaid-1784873931460-0 [data-look=\”neo\”].node circle{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1784873931460-0 [data-look=\”neo\”].node circle .state-start{fill:#000000;}#publish-mermaid-1784873931460-0 [data-look=\”neo\”].icon-shape .icon{fill:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1784873931460-0 [data-look=\”neo\”].icon-shape .icon-neo path{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1784873931460-0 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}缓存雪崩 – 大量key同时过期无(大批量同时过期)

请求各种key

缓存中有?

大量请求同时打到DB,可能拖垮数据库!

缓存击穿 – 热点key过期无(刚过期)

大量请求 key='hot_item'

缓存中有?

所有请求都去查DB,瞬时高并发,DB压力剧增!

缓存穿透 – 数据不存在无无

请求 key='-1'

缓存中有?

数据库中有?

缓存和数据库都没有,请求直接打到DB,可能是恶意攻击!

/**
* 缓存三大问题的本质区别
*/
public class CacheProblemDefinitions {

public static void main(String[] args) {
System.out.println("=== 缓存三大问题对比 ===\\n");

System.out.println("┌──────────┬────────────────┬────────────────┬──────────────┐");
System.out.println("│ 问题 │ 根本原因 │ 典型场景 │ 核心方案 │");
System.out.println("├──────────┼────────────────┼────────────────┼──────────────┤");
System.out.println("│ 缓存穿透 │ 查的数据不存在 │ 恶意攻击/-1查询│ 布隆过滤器 │");
System.out.println("│ 缓存击穿 │ 热点key过期 │ 秒杀商品过期 │ 互斥锁回种 │");
System.out.println("│ 缓存雪崩 │ 大量key同时过期│ 批量导入过期 │ 过期时间随机化│");
System.out.println("└──────────┴────────────────┴────────────────┴──────────────┘\\n");

System.out.println("记忆口诀:");
System.out.println(" 穿透: 查的是不存在的数据(穿过缓存和DB)");
System.out.println(" 击穿: 单个热点key过期(一击击穿)");
System.out.println(" 雪崩: 大量key同时过期(像雪崩一样)");
}
}

1.2 三者的直观区别

缓存穿透: 请求 → 缓存(无) → 数据库(无) → 返回空
key本身就不存在,每次都要查DB

缓存击穿: 请求 → 缓存(过期) → 数据库(有) → 回写缓存
热点key过期瞬间,大量并发查同一个key

缓存雪崩: 请求 → 缓存(大量过期) → 数据库(压力巨大)
批量key同时过期,大量请求打到DB

二、缓存穿透: 布隆过滤器方案

2.1 布隆过滤器原理

#publish-mermaid-1784873931536-1{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#publish-mermaid-1784873931536-1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#publish-mermaid-1784873931536-1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#publish-mermaid-1784873931536-1 .error-icon{fill:#552222;}#publish-mermaid-1784873931536-1 .error-text{fill:#552222;stroke:#552222;}#publish-mermaid-1784873931536-1 .edge-thickness-normal{stroke-width:1px;}#publish-mermaid-1784873931536-1 .edge-thickness-thick{stroke-width:3.5px;}#publish-mermaid-1784873931536-1 .edge-pattern-solid{stroke-dasharray:0;}#publish-mermaid-1784873931536-1 .edge-thickness-invisible{stroke-width:0;fill:none;}#publish-mermaid-1784873931536-1 .edge-pattern-dashed{stroke-dasharray:3;}#publish-mermaid-1784873931536-1 .edge-pattern-dotted{stroke-dasharray:2;}#publish-mermaid-1784873931536-1 .marker{fill:#333333;stroke:#333333;}#publish-mermaid-1784873931536-1 .marker.cross{stroke:#333333;}#publish-mermaid-1784873931536-1 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#publish-mermaid-1784873931536-1 p{margin:0;}#publish-mermaid-1784873931536-1 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#publish-mermaid-1784873931536-1 .cluster-label text{fill:#333;}#publish-mermaid-1784873931536-1 .cluster-label span{color:#333;}#publish-mermaid-1784873931536-1 .cluster-label span p{background-color:transparent;}#publish-mermaid-1784873931536-1 .label text,#publish-mermaid-1784873931536-1 span{fill:#333;color:#333;}#publish-mermaid-1784873931536-1 .node rect,#publish-mermaid-1784873931536-1 .node circle,#publish-mermaid-1784873931536-1 .node ellipse,#publish-mermaid-1784873931536-1 .node polygon,#publish-mermaid-1784873931536-1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1784873931536-1 .rough-node .label text,#publish-mermaid-1784873931536-1 .node .label text,#publish-mermaid-1784873931536-1 .image-shape .label,#publish-mermaid-1784873931536-1 .icon-shape .label{text-anchor:middle;}#publish-mermaid-1784873931536-1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#publish-mermaid-1784873931536-1 .rough-node .label,#publish-mermaid-1784873931536-1 .node .label,#publish-mermaid-1784873931536-1 .image-shape .label,#publish-mermaid-1784873931536-1 .icon-shape .label{text-align:center;}#publish-mermaid-1784873931536-1 .node.clickable{cursor:pointer;}#publish-mermaid-1784873931536-1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#publish-mermaid-1784873931536-1 .arrowheadPath{fill:#333333;}#publish-mermaid-1784873931536-1 .edgePath .path{stroke:#333333;stroke-width:1px;}#publish-mermaid-1784873931536-1 .flowchart-link{stroke:#333333;fill:none;}#publish-mermaid-1784873931536-1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-1784873931536-1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#publish-mermaid-1784873931536-1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-1784873931536-1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#publish-mermaid-1784873931536-1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#publish-mermaid-1784873931536-1 .cluster text{fill:#333;}#publish-mermaid-1784873931536-1 .cluster span{color:#333;}#publish-mermaid-1784873931536-1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#publish-mermaid-1784873931536-1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#publish-mermaid-1784873931536-1 rect.text{fill:none;stroke-width:0;}#publish-mermaid-1784873931536-1 .icon-shape,#publish-mermaid-1784873931536-1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-1784873931536-1 .icon-shape p,#publish-mermaid-1784873931536-1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#publish-mermaid-1784873931536-1 .icon-shape .label rect,#publish-mermaid-1784873931536-1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-1784873931536-1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#publish-mermaid-1784873931536-1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#publish-mermaid-1784873931536-1 .node .neo-node{stroke:#9370DB;}#publish-mermaid-1784873931536-1 [data-look=\”neo\”].node rect,#publish-mermaid-1784873931536-1 [data-look=\”neo\”].cluster rect,#publish-mermaid-1784873931536-1 [data-look=\”neo\”].node polygon{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1784873931536-1 [data-look=\”neo\”].swimlane.cluster rect{filter:none;}#publish-mermaid-1784873931536-1 [data-look=\”neo\”].node path{stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1784873931536-1 [data-look=\”neo\”].node .outer-path{filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1784873931536-1 [data-look=\”neo\”].node .neo-line path{stroke:#9370DB;filter:none;}#publish-mermaid-1784873931536-1 [data-look=\”neo\”].node circle{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1784873931536-1 [data-look=\”neo\”].node circle .state-start{fill:#000000;}#publish-mermaid-1784873931536-1 [data-look=\”neo\”].icon-shape .icon{fill:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1784873931536-1 [data-look=\”neo\”].icon-shape .icon-neo path{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1784873931536-1 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}查询 key2 (一定不存在)

hash1(key2)=5 → 0

不全为1 → 一定不存在

hash2(key2)=7 → 1

hash3(key2)=13 → 0

查询 key1 (一定存在)

hash1(key1)=3 → 1

全部为1 → 可能存在

hash2(key1)=7 → 1

hash3(key1)=11 → 1

添加元素 key1

hash1(key1)=3

位3置1

hash2(key1)=7

位7置1

hash3(key1)=11

位11置1

0,0,0,1,0,0,0,1,0,0,0,1,0,0,0

布隆过滤器结构

位数组(初始全0)

0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

2.2 布隆过滤器完整实现

import java.util.BitSet;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
* 布隆过滤器实现
*
* 特性:
* – 判断不存在: 100%准确
* – 判断存在: 可能有误判(但可接受)
* – 不支持删除(Counting Bloom Filter支持)
*/
public class BloomFilter<E> {

private final BitSet bitSet; // 位数组
private final int bitSize; // 位数组大小
private final int hashFunctionCount; // 哈希函数数量
private final int expectedSize; // 预期元素数量
private final double falsePositiveRate;// 误判率

/**
* 构造函数
*
* @param expectedSize 预期存储元素数量
* @param falsePositiveRate 允许的误判率(0.01 = 1%)
*/
public BloomFilter(int expectedSize, double falsePositiveRate) {
this.expectedSize = expectedSize;
this.falsePositiveRate = falsePositiveRate;

// 计算最优位数组大小
// m = -n * ln(p) / (ln(2))^2
this.bitSize = (int) (-expectedSize * Math.log(falsePositiveRate) /
(Math.log(2) * Math.log(2)));

// 计算最优哈希函数数量
// k = (m/n) * ln(2)
this.hashFunctionCount = (int) (bitSize / expectedSize * Math.log(2));

this.bitSet = new BitSet(bitSize);

System.out.println("布隆过滤器初始化:");
System.out.println(" 预期元素数: " + expectedSize);
System.out.println(" 误判率: " + falsePositiveRate);
System.out.println(" 位数组大小: " + bitSize);
System.out.println(" 哈希函数数: " + hashFunctionCount);
}

/**
* 添加元素
*/
public void add(E element) {
int[] hashes = hash(element);
for (int hash : hashes) {
bitSet.set(Math.abs(hash % bitSize), true);
}
}

/**
* 判断元素是否可能存在
*
* @return true=可能存在, false=一定不存在
*/
public boolean mightContain(E element) {
int[] hashes = hash(element);
for (int hash : hashes) {
if (!bitSet.get(Math.abs(hash % bitSize))) {
return false; // 有一位为0 → 一定不存在
}
}
return true; // 全部为1 → 可能存在(有误判率)
}

/**
* 多重哈希函数
* 使用MD5生成128位哈希,再切分为多个int
*/
private int[] hash(E element) {
int[] result = new int[hashFunctionCount];
byte[] bytes = element.toString().getBytes(StandardCharsets.UTF_8);

try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(bytes);

// 将128位MD5切分为多个32位的int
for (int i = 0; i < hashFunctionCount; i++) {
int index = (i * 4) % digest.length;
result[i] = ((digest[index] & 0xFF) << 24) |
((digest[(index + 1) % digest.length] & 0xFF) << 16) |
((digest[(index + 2) % digest.length] & 0xFF) << 8) |
(digest[(index + 3) % digest.length] & 0xFF);
}
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("MD5 not available", e);
}

return result;
}

/**
* 获取布隆过滤器信息
*/
public String getInfo() {
long setBits = bitSet.cardinality();
return String.format(
"BloomFilter[bits=%d, setBits=%d, usage=%.2f%%, hashFuncs=%d, expected=%d, fpr=%.4f]",
bitSize, setBits, (double) setBits / bitSize * 100,
hashFunctionCount, expectedSize, falsePositiveRate
);
}
}

2.3 缓存穿透解决方案

/**
* 使用布隆过滤器 + 空值缓存防止缓存穿透
*/
@Service
public class CachePenetrationService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private ProductRepository productRepository;

// 布隆过滤器(存储所有存在的商品ID)
private BloomFilter<String> bloomFilter;

@PostConstruct
public void init() {
// 初始化布隆过滤器: 预期100万商品, 1%误判率
bloomFilter = new BloomFilter<>(1_000_000, 0.01);

// 预热: 加载所有商品ID到布隆过滤器
List<String> allProductIds = productRepository.getAllIds();
for (String id : allProductIds) {
bloomFilter.add(id);
}
System.out.println("布隆过滤器预热完成: " + bloomFilter.getInfo());
}

/**
* 查询商品 – 防穿透版本
*/
public Product getProduct(String productId) {
// ===== 第一层: 布隆过滤器 =====
if (!bloomFilter.mightContain(productId)) {
// 布隆过滤器说"一定不存在"
// 直接返回null,不需要查缓存和数据库
System.out.println("布隆过滤器拦截: " + productId);
return null;
}

// ===== 第二层: 缓存查询 =====
String cacheKey = "product:" + productId;
Product product = (Product) redisTemplate.opsForValue().get(cacheKey);
if (product != null) {
// 空对象标记(防止缓存穿透的辅助手段)
if (product.getId() == null) {
return null; // 缓存了"不存在"的标记
}
return product;
}

// ===== 第三层: 数据库查询 =====
product = productRepository.findById(productId);
if (product != null) {
// 存在: 写入缓存, TTL=30分钟
redisTemplate.opsForValue().set(cacheKey, product,
30, TimeUnit.MINUTES);
} else {
// 不存在: 写入空对象标记, TTL=1分钟(防止恶意攻击)
redisTemplate.opsForValue().set(cacheKey, new Product(),
1, TimeUnit.MINUTES);

// 可选: 异步更新布隆过滤器
// 如果商品被删除了,需要从布隆过滤器中移除
// 但标准布隆过滤器不支持删除,需要使用Counting Bloom Filter
}

return product;
}

/**
* 新增商品时更新布隆过滤器
*/
public void addProduct(Product product) {
productRepository.save(product);
bloomFilter.add(product.getId()); // 添加到布隆过滤器
}
}

三、缓存击穿: 互斥锁方案

3.1 互斥锁原理

数据库Redis分布式锁请求3请求2请求1数据库Redis分布式锁请求3请求2请求1#publish-mermaid-1784873931641-2{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#publish-mermaid-1784873931641-2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#publish-mermaid-1784873931641-2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#publish-mermaid-1784873931641-2 .error-icon{fill:#552222;}#publish-mermaid-1784873931641-2 .error-text{fill:#552222;stroke:#552222;}#publish-mermaid-1784873931641-2 .edge-thickness-normal{stroke-width:1px;}#publish-mermaid-1784873931641-2 .edge-thickness-thick{stroke-width:3.5px;}#publish-mermaid-1784873931641-2 .edge-pattern-solid{stroke-dasharray:0;}#publish-mermaid-1784873931641-2 .edge-thickness-invisible{stroke-width:0;fill:none;}#publish-mermaid-1784873931641-2 .edge-pattern-dashed{stroke-dasharray:3;}#publish-mermaid-1784873931641-2 .edge-pattern-dotted{stroke-dasharray:2;}#publish-mermaid-1784873931641-2 .marker{fill:#333333;stroke:#333333;}#publish-mermaid-1784873931641-2 .marker.cross{stroke:#333333;}#publish-mermaid-1784873931641-2 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#publish-mermaid-1784873931641-2 p{margin:0;}#publish-mermaid-1784873931641-2 .actor{stroke:#9370DB;fill:#ECECFF;stroke-width:1;}#publish-mermaid-1784873931641-2 rect.actor.outer-path[data-look=\”neo\”]{filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1784873931641-2 rect.note[data-look=\”neo\”]{stroke:#aaaa33;fill:#fff5ad;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1784873931641-2 text.actor>tspan{fill:black;stroke:none;}#publish-mermaid-1784873931641-2 .actor-line{stroke:#9370DB;}#publish-mermaid-1784873931641-2 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#publish-mermaid-1784873931641-2 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#publish-mermaid-1784873931641-2 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#publish-mermaid-1784873931641-2 [id$=\”-arrowhead\”] path{fill:#333;stroke:#333;}#publish-mermaid-1784873931641-2 .sequenceNumber{fill:white;}#publish-mermaid-1784873931641-2 [id$=\”-sequencenumber\”]{fill:#333;}#publish-mermaid-1784873931641-2 [id$=\”-crosshead\”] path{fill:#333;stroke:#333;}#publish-mermaid-1784873931641-2 .messageText{fill:#333;stroke:none;}#publish-mermaid-1784873931641-2 .labelBox{stroke:#9370DB;fill:#ECECFF;filter:none;}#publish-mermaid-1784873931641-2 .labelText,#publish-mermaid-1784873931641-2 .labelText>tspan{fill:black;stroke:none;}#publish-mermaid-1784873931641-2 .loopText,#publish-mermaid-1784873931641-2 .loopText>tspan{fill:black;stroke:none;}#publish-mermaid-1784873931641-2 .sectionTitle,#publish-mermaid-1784873931641-2 .sectionTitle>tspan{fill:black;stroke:none;}#publish-mermaid-1784873931641-2 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:#9370DB;fill:#9370DB;}#publish-mermaid-1784873931641-2 .note{stroke:#aaaa33;fill:#fff5ad;}#publish-mermaid-1784873931641-2 .noteText,#publish-mermaid-1784873931641-2 .noteText>tspan{fill:black;stroke:none;font-weight:normal;}#publish-mermaid-1784873931641-2 .activation0{fill:#f4f4f4;stroke:#666;}#publish-mermaid-1784873931641-2 .activation1{fill:#f4f4f4;stroke:#666;}#publish-mermaid-1784873931641-2 .activation2{fill:#f4f4f4;stroke:#666;}#publish-mermaid-1784873931641-2 .actorPopupMenu{position:absolute;}#publish-mermaid-1784873931641-2 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#publish-mermaid-1784873931641-2 .actor-man circle,#publish-mermaid-1784873931641-2 line{fill:#ECECFF;stroke-width:2px;}#publish-mermaid-1784873931641-2 g rect.rect{filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));stroke:#9370DB;}#publish-mermaid-1784873931641-2 .node .neo-node{stroke:#9370DB;}#publish-mermaid-1784873931641-2 [data-look=\”neo\”].node rect,#publish-mermaid-1784873931641-2 [data-look=\”neo\”].cluster rect,#publish-mermaid-1784873931641-2 [data-look=\”neo\”].node polygon{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1784873931641-2 [data-look=\”neo\”].swimlane.cluster rect{filter:none;}#publish-mermaid-1784873931641-2 [data-look=\”neo\”].node path{stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1784873931641-2 [data-look=\”neo\”].node .outer-path{filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1784873931641-2 [data-look=\”neo\”].node .neo-line path{stroke:#9370DB;filter:none;}#publish-mermaid-1784873931641-2 [data-look=\”neo\”].node circle{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1784873931641-2 [data-look=\”neo\”].node circle .state-start{fill:#000000;}#publish-mermaid-1784873931641-2 [data-look=\”neo\”].icon-shape .icon{fill:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1784873931641-2 [data-look=\”neo\”].icon-shape .icon-neo path{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1784873931641-2 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}只有请求1查了数据库!缓存未命中SET lock:key 1 NX EX 5获取锁成功缓存未命中SET lock:key 1 NX EX 5获取锁失败(已有锁)等待100ms后重试缓存未命中SET lock:key 1 NX EX 5获取锁失败(已有锁)等待100ms后重试查询数据库返回数据写入缓存DEL lock:key(释放锁)重试: 缓存命中!重试: 缓存命中!

3.2 互斥锁代码实现

/**
* 使用Redis实现互斥锁防止缓存击穿
*/
@Service
public class CacheBreakdownService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private ProductRepository productRepository;

private static final String LOCK_PREFIX = "lock:product:";
private static final String CACHE_PREFIX = "product:";
private static final long LOCK_EXPIRE = 5; // 锁过期时间(秒)
private static final long CACHE_EXPIRE = 30; // 缓存过期时间(分钟)
private static final long RETRY_INTERVAL = 100; // 重试间隔(毫秒)
private static final int MAX_RETRIES = 50; // 最大重试次数

/**
* 查询商品 – 互斥锁防击穿
*/
public Product getProduct(String productId) {
String cacheKey = CACHE_PREFIX + productId;
String lockKey = LOCK_PREFIX + productId;

// 1. 先查缓存
Product product = (Product) redisTemplate.opsForValue().get(cacheKey);
if (product != null) {
return product;
}

// 2. 缓存未命中,尝试获取互斥锁
boolean locked = tryLock(lockKey);

if (locked) {
try {
// 3. 获取锁成功,查数据库并回写缓存
// 双重检查: 可能其他线程已经回写了
product = (Product) redisTemplate.opsForValue().get(cacheKey);
if (product != null) {
return product;
}

// 查询数据库
product = productRepository.findById(productId);
if (product != null) {
// 写入缓存(带随机过期时间,避免雪崩)
long expire = CACHE_EXPIRE + (long)(Math.random() * 10);
redisTemplate.opsForValue().set(cacheKey, product,
expire, TimeUnit.MINUTES);
} else {
// 空值缓存(防穿透)
redisTemplate.opsForValue().set(cacheKey, new Product(),
1, TimeUnit.MINUTES);
}

return product;
} finally {
// 4. 释放锁
unlock(lockKey);
}
} else {
// 5. 获取锁失败,等待后重试
return retryGet(cacheKey, 0);
}
}

/**
* 尝试获取分布式锁
* 使用 SET key value NX EX seconds 命令
*/
private boolean tryLock(String lockKey) {
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", LOCK_EXPIRE, TimeUnit.SECONDS);
return Boolean.TRUE.equals(result);
}

/**
* 释放锁
* 注意: 需要验证锁的持有者(简化版省略)
*/
private void unlock(String lockKey) {
redisTemplate.delete(lockKey);
}

/**
* 获取锁失败,休眠后重试
*/
private Product retryGet(String cacheKey, int retryCount) {
if (retryCount > MAX_RETRIES) {
// 超过最大重试次数,返回null或降级数据
System.err.println("超过最大重试次数: " + cacheKey);
return null;
}

try {
Thread.sleep(RETRY_INTERVAL);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}

// 重试: 先查缓存
Product product = (Product) redisTemplate.opsForValue().get(cacheKey);
if (product != null) {
return product;
}

// 缓存仍然没有,继续重试
return retryGet(cacheKey, retryCount + 1);
}
}

3.3 Redisson分布式锁实现

/**
* 使用Redisson实现更可靠的互斥锁
*/
@Service
public class RedissonCacheBreakdownService {

@Autowired
private RedissonClient redissonClient;

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private ProductRepository productRepository;

private static final String CACHE_PREFIX = "product:";
private static final String LOCK_PREFIX = "lock:product:";

/**
* 使用Redisson获取可重入锁
*/
public Product getProduct(String productId) {
String cacheKey = CACHE_PREFIX + productId;
String lockKey = LOCK_PREFIX + productId;

// 1. 查缓存
Product product = (Product) redisTemplate.opsForValue().get(cacheKey);
if (product != null) {
return product;
}

// 2. 获取Redisson分布式锁
RLock lock = redissonClient.getLock(lockKey);

try {
// 尝试加锁: 等待5秒, 锁10秒后自动释放
if (lock.tryLock(5, 10, TimeUnit.SECONDS)) {
// 双重检查
product = (Product) redisTemplate.opsForValue().get(cacheKey);
if (product != null) {
return product;
}

// 查数据库
product = productRepository.findById(productId);
if (product != null) {
// 写入缓存(随机过期时间)
long expire = 30 + (long)(Math.random() * 10);
redisTemplate.opsForValue().set(cacheKey, product,
expire, TimeUnit.MINUTES);
}
return product;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 释放锁(只有持有者才能释放)
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}

return null;
}
}

四、缓存雪崩: 综合防御方案

4.1 雪崩的多层防御

/**
* 缓存雪崩综合防御
*
* 多层防御策略:
* 1. 过期时间随机化(基础)
* 2. 热点数据永不过期 + 异步更新(核心)
* 3. 本地缓存二级缓存(兜底)
* 4. 限流降级(最后防线)
*/
@Service
public class CacheAvalancheService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private ProductRepository productRepository;

// 本地缓存(Caffeine)作为二级缓存
private final Cache<String, Product> localCache = Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();

/**
* 策略1: 过期时间随机化
* 防止大量key在同一时间过期
*/
public void setCacheWithRandomExpire(String key, Object value,
long baseExpireMinutes) {
// 在基础过期时间上增加随机偏移(±20%)
long randomOffset = (long)(baseExpireMinutes * 0.2 * Math.random());
long expire = baseExpireMinutes + randomOffset;

redisTemplate.opsForValue().set(key, value, expire, TimeUnit.MINUTES);
}

/**
* 策略2: 热点数据永不过期 + 逻辑过期
*
* 物理永不过期,通过逻辑过期时间判断是否需要更新
*/
static class LogicalExpireData {
private Object data;
private long expireTime; // 逻辑过期时间戳

public LogicalExpireData(Object data, long expireSeconds) {
this.data = data;
this.expireTime = System.currentTimeMillis() + expireSeconds * 1000;
}

public boolean isExpired() {
return System.currentTimeMillis() > expireTime;
}
}

public Product getProductLogicalExpire(String productId) {
String cacheKey = "product:logical:" + productId;

// 1. 查缓存
LogicalExpireData cacheData = (LogicalExpireData)
redisTemplate.opsForValue().get(cacheKey);

if (cacheData == null) {
// 缓存不存在,使用互斥锁加载
return loadWithLock(productId, cacheKey);
}

if (cacheData.isExpired()) {
// 逻辑过期,异步更新(不阻塞当前请求)
asyncRefresh(productId, cacheKey);
// 当前请求仍返回旧数据
return (Product) cacheData.data;
}

// 未过期,直接返回
return (Product) cacheData.data;
}

private Product loadWithLock(String productId, String cacheKey) {
// 使用互斥锁加载数据(参考缓存击穿方案)
// …
return null;
}

private void asyncRefresh(String productId, String cacheKey) {
// 异步线程池更新缓存
CompletableFuture.runAsync(() -> {
Product product = productRepository.findById(productId);
if (product != null) {
LogicalExpireData newData = new LogicalExpireData(product, 3600);
redisTemplate.opsForValue().set(cacheKey, newData);
}
});
}

/**
* 策略3: 多级缓存(Redis + 本地缓存)
*/
public Product getProductWithMultiLevelCache(String productId) {
String cacheKey = "product:" + productId;

// Level 1: 本地缓存(Caffeine)
Product product = localCache.getIfPresent(cacheKey);
if (product != null) {
System.out.println("本地缓存命中: " + productId);
return product;
}

// Level 2: Redis缓存
product = (Product) redisTemplate.opsForValue().get(cacheKey);
if (product != null) {
System.out.println("Redis缓存命中: " + productId);
// 回写本地缓存
localCache.put(cacheKey, product);
return product;
}

// Level 3: 数据库
product = productRepository.findById(productId);
if (product != null) {
// 回写Redis + 本地缓存
long expire = 30 + (long)(Math.random() * 10);
redisTemplate.opsForValue().set(cacheKey, product,
expire, TimeUnit.MINUTES);
localCache.put(cacheKey, product);
}

return product;
}
}

4.2 综合防御架构

#publish-mermaid-1784873931685-3{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#publish-mermaid-1784873931685-3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#publish-mermaid-1784873931685-3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#publish-mermaid-1784873931685-3 .error-icon{fill:#552222;}#publish-mermaid-1784873931685-3 .error-text{fill:#552222;stroke:#552222;}#publish-mermaid-1784873931685-3 .edge-thickness-normal{stroke-width:1px;}#publish-mermaid-1784873931685-3 .edge-thickness-thick{stroke-width:3.5px;}#publish-mermaid-1784873931685-3 .edge-pattern-solid{stroke-dasharray:0;}#publish-mermaid-1784873931685-3 .edge-thickness-invisible{stroke-width:0;fill:none;}#publish-mermaid-1784873931685-3 .edge-pattern-dashed{stroke-dasharray:3;}#publish-mermaid-1784873931685-3 .edge-pattern-dotted{stroke-dasharray:2;}#publish-mermaid-1784873931685-3 .marker{fill:#333333;stroke:#333333;}#publish-mermaid-1784873931685-3 .marker.cross{stroke:#333333;}#publish-mermaid-1784873931685-3 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#publish-mermaid-1784873931685-3 p{margin:0;}#publish-mermaid-1784873931685-3 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#publish-mermaid-1784873931685-3 .cluster-label text{fill:#333;}#publish-mermaid-1784873931685-3 .cluster-label span{color:#333;}#publish-mermaid-1784873931685-3 .cluster-label span p{background-color:transparent;}#publish-mermaid-1784873931685-3 .label text,#publish-mermaid-1784873931685-3 span{fill:#333;color:#333;}#publish-mermaid-1784873931685-3 .node rect,#publish-mermaid-1784873931685-3 .node circle,#publish-mermaid-1784873931685-3 .node ellipse,#publish-mermaid-1784873931685-3 .node polygon,#publish-mermaid-1784873931685-3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1784873931685-3 .rough-node .label text,#publish-mermaid-1784873931685-3 .node .label text,#publish-mermaid-1784873931685-3 .image-shape .label,#publish-mermaid-1784873931685-3 .icon-shape .label{text-anchor:middle;}#publish-mermaid-1784873931685-3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#publish-mermaid-1784873931685-3 .rough-node .label,#publish-mermaid-1784873931685-3 .node .label,#publish-mermaid-1784873931685-3 .image-shape .label,#publish-mermaid-1784873931685-3 .icon-shape .label{text-align:center;}#publish-mermaid-1784873931685-3 .node.clickable{cursor:pointer;}#publish-mermaid-1784873931685-3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#publish-mermaid-1784873931685-3 .arrowheadPath{fill:#333333;}#publish-mermaid-1784873931685-3 .edgePath .path{stroke:#333333;stroke-width:1px;}#publish-mermaid-1784873931685-3 .flowchart-link{stroke:#333333;fill:none;}#publish-mermaid-1784873931685-3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-1784873931685-3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#publish-mermaid-1784873931685-3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-1784873931685-3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#publish-mermaid-1784873931685-3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#publish-mermaid-1784873931685-3 .cluster text{fill:#333;}#publish-mermaid-1784873931685-3 .cluster span{color:#333;}#publish-mermaid-1784873931685-3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#publish-mermaid-1784873931685-3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#publish-mermaid-1784873931685-3 rect.text{fill:none;stroke-width:0;}#publish-mermaid-1784873931685-3 .icon-shape,#publish-mermaid-1784873931685-3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#publish-mermaid-1784873931685-3 .icon-shape p,#publish-mermaid-1784873931685-3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#publish-mermaid-1784873931685-3 .icon-shape .label rect,#publish-mermaid-1784873931685-3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#publish-mermaid-1784873931685-3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#publish-mermaid-1784873931685-3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#publish-mermaid-1784873931685-3 .node .neo-node{stroke:#9370DB;}#publish-mermaid-1784873931685-3 [data-look=\”neo\”].node rect,#publish-mermaid-1784873931685-3 [data-look=\”neo\”].cluster rect,#publish-mermaid-1784873931685-3 [data-look=\”neo\”].node polygon{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1784873931685-3 [data-look=\”neo\”].swimlane.cluster rect{filter:none;}#publish-mermaid-1784873931685-3 [data-look=\”neo\”].node path{stroke:#9370DB;stroke-width:1px;}#publish-mermaid-1784873931685-3 [data-look=\”neo\”].node .outer-path{filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1784873931685-3 [data-look=\”neo\”].node .neo-line path{stroke:#9370DB;filter:none;}#publish-mermaid-1784873931685-3 [data-look=\”neo\”].node circle{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1784873931685-3 [data-look=\”neo\”].node circle .state-start{fill:#000000;}#publish-mermaid-1784873931685-3 [data-look=\”neo\”].icon-shape .icon{fill:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1784873931685-3 [data-look=\”neo\”].icon-shape .icon-neo path{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#publish-mermaid-1784873931685-3 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}是否是否一定不存在可能存在成功失败

请求到达

本地缓存命中?

返回数据

Redis缓存命中?

布隆过滤器判断?

返回空/降级

获取互斥锁

查询数据库

等待重试

回写Redis随机过期时间

回写本地缓存

释放锁

五、完整防御体系代码

5.1 综合缓存服务

/**
* 综合缓存服务: 包含所有防御措施
*/
@Service
public class ComprehensiveCacheService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private ProductRepository productRepository;

@Autowired
private RedissonClient redissonClient;

// 布隆过滤器
private BloomFilter<String> bloomFilter;

// 本地缓存
private final Cache<String, Product> localCache = Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterWrite(1, TimeUnit.MINUTES)
.build();

@PostConstruct
public void init() {
bloomFilter = new BloomFilter<>(1_000_000, 0.01);
}

/**
* 完整的防穿透+击穿+雪崩的查询方法
*/
public Product getProductSafe(String productId) {
// ===== 1. 本地缓存(L1) =====
Product product = localCache.getIfPresent(productId);
if (product != null) return product;

// ===== 2. 布隆过滤器(防穿透) =====
if (!bloomFilter.mightContain(productId)) {
return null; // 一定不存在
}

// ===== 3. Redis缓存(L2) =====
String cacheKey = "product:" + productId;
product = (Product) redisTemplate.opsForValue().get(cacheKey);
if (product != null) {
localCache.put(productId, product);
return product;
}

// ===== 4. 互斥锁(防击穿) =====
String lockKey = "lock:" + cacheKey;
RLock lock = redissonClient.getLock(lockKey);

try {
if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
// 双重检查
product = (Product) redisTemplate.opsForValue().get(cacheKey);
if (product != null) {
localCache.put(productId, product);
return product;
}

// 查数据库
product = productRepository.findById(productId);
if (product != null) {
// 回写缓存(随机过期时间,防雪崩)
long expire = 30 + (long)(Math.random() * 10);
redisTemplate.opsForValue().set(cacheKey, product,
expire, TimeUnit.MINUTES);
localCache.put(productId, product);
} else {
// 空值缓存(防穿透)
redisTemplate.opsForValue().set(cacheKey, new Product(),
1, TimeUnit.MINUTES);
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}

return product;
}
}

六、总结

6.1 三大问题解决方案速查

| 问题 | 方案 | 核心技术 | 代码复杂度 |

|——|——|———|———–|

| 穿透 | 布隆过滤器 + 空值缓存 | 哈希函数 + BitSet | 中 |

| 击穿 | 互斥锁 + 逻辑过期 | Redis SET NX EX | 低 |

| 雪崩 | 随机过期 + 多级缓存 + 限流 | 组合策略 | 高 |

6.2 面试应答模板

问: 如何解决缓存穿透、击穿、雪崩?

答:
1. 穿透(数据不存在):
布隆过滤器在缓存前拦截,将存在的key预先加载到布隆过滤器。
对于不存在的key直接返回,不查DB。
辅助: 缓存空对象(短期TTL)。

2. 击穿(热点key过期):
互斥锁(SET NX),只有一个线程去查DB回写缓存,
其他线程等待后重试读缓存。

3. 雪崩(大量key同时过期):
– 过期时间加随机偏移(±20%)
– 热点数据物理永不过期 + 逻辑过期
– 多级缓存(本地Caffeine + Redis)
– 限流降级(最后防线)

完整方案: 布隆过滤器 + 互斥锁 + 随机过期 + 多级缓存

赞(0)
未经允许不得转载:171主机测评 » 缓存穿透/击穿/雪崩: 三种场景一次性解决,附布隆过滤器+互斥锁代码
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址