缓存击穿、穿透、雪崩详解
一、缓存穿透(Cache Penetration)
1. 是什么?
缓存穿透是指查询不存在的数据,导致请求绕过缓存直接访问数据库。例如查询数据库不存在ID=-1的数据。
2. 解决什么问题
防止恶意攻击或异常请求频繁访问数据库,导致数据库压力过大甚至崩溃。
3. 应用场景
- 用户输入非法ID(如负数、超长字符)
- 爬虫恶意遍历数据ID
4. 解决方案
二、缓存击穿(Cache Breakdown)
1. 是什么?
缓存击穿是指热点数据过期瞬间,大量请求直接穿透到数据库,导致数据库瞬时压力激增。
2. 解决什么问题
避免热点数据失效时,高并发请求压垮数据库。
3. 应用场景
- 明星离婚新闻的缓存过期
- 电商秒杀商品信息缓存失效
4. 解决方案
三、缓存雪崩(Cache Avalanche)
1. 是什么?
缓存雪崩是指大量缓存同时过期或缓存服务宕机,导致所有请求涌向数据库。
2. 解决什么问题
防止因大规模缓存失效引发的数据库级联故障。
3. 应用场景
- 缓存集群宕机
- 批量缓存设置相同过期时间
4. 解决方案
解决方案对比表
问题类型 | 核心特征 | 关键技术方案 | Java实现示例 |
---|---|---|---|
穿透 | 查询不存在的数据 | 1. 布隆过滤器 2. 缓存空值 | RedisBloom + setIfAbsent("null", 5min) |
击穿 | 热点数据失效 | 1. 互斥锁 2. 逻辑过期 | RedissonLock.lock() + CompletableFuture |
雪崩 | 批量缓存失效 | 1. 随机过期时间 2. 服务熔断 | Hystrix + ThreadPoolExecutor |
Java代码示例(JDK8+)
1. 缓存穿透解决方案(布隆过滤器 + Guava)
java
// 布隆过滤器初始化
BloomFilter<String> bloomFilter = BloomFilter.create(
Funnels.stringFunnel(), 1000000, 0.01);
// 查询逻辑
public Object getData(String key) {
// 1. 布隆过滤器校验
if (!bloomFilter.mightContain(key)) {
return null;
}
// 2. 查询缓存
Object data = redis.get(key);
if (data == null) {
// 3. 双重检查锁
synchronized (this) {
data = redis.get(key);
if (data == null) {
data = db.query(key);
redis.setex(key, 300, data); // 缓存5分钟
}
}
}
return data;
}
2. 缓存击穿解决方案(Redisson分布式锁)
java
public Object getHotData(String key) {
Object data = redis.get(key);
if (data == null) {
RLock lock = redisson.getLock(key + "_LOCK");
try {
if (lock.tryLock(10, TimeUnit.SECONDS)) { // 尝试获取锁
data = db.query(key); // 查数据库
redis.setex(key, 60, data); // 重建缓存
} else {
Thread.sleep(100); // 未获锁则等待
return getHotData(key); // 重试
}
} finally {
lock.unlock();
}
}
return data;
}
3. 缓存雪崩解决方案(Hystrix熔断 + 随机过期)
java
// 设置缓存时添加随机过期时间
public void setCache(String key, Object value) {
int expire = 3600 + new Random().nextInt(600); // 1小时±10分钟
redis.setex(key, expire, value);
}
// 使用Hystrix熔断保护数据库
@HystrixCommand(fallbackMethod = "fallbackQuery")
public Object queryWithProtection(String key) {
return db.query(key);
}
public Object fallbackQuery(String key) {
return "系统繁忙,请重试"; // 降级响应
}
重要注意事项
- 布隆过滤器需预热加载所有合法key,有1%误判率
- 分布式锁要设置超时时间,避免死锁(推荐Redisson看门狗机制)
- 空值缓存的TTL应较短(如2-5分钟),防止存储垃圾数据
- 熔断阈值需动态调整(如失败率>50%触发熔断)
- 缓存重建使用异步线程池(如
CompletableFuture.supplyAsync()
)
总结
问题 | 本质原因 | 核心防御思路 |
---|---|---|
穿透 | 恶意访问不存在数据 | 前置过滤 + 缓存空值 |
击穿 | 热点数据瞬时失效 | 并发控制 + 异步重建 |
雪崩 | 批量缓存失效/服务宕机 | 分散风险 + 熔断降级 |
最佳实践:结合
Redis
+布隆过滤器
+分布式锁
+熔断器
,并通过CompletableFuture
实现异步处理。JDK8+的StampedLock
可优化本地锁性能,ForkJoinPool
适用于批量重建缓存场景。