缓存穿透 / 击穿 / 雪崩
TIP
使用 Redis 作为缓存时,需要了解并防范缓存穿透、击穿和雪崩这三大常见问题。
缓存穿透
请求缓存和数据库中都不存在的数据,导致请求直接打到数据库。
解决方式
java
// 1. 缓存空值
public User getUser(Long id) {
User user = redis.get("user:" + id);
if (user != null) {
return user;
}
user = userDao.findById(id);
if (user == null) {
redis.set("user:" + id, "", 60); // 缓存空值,过期时间短
} else {
redis.set("user:" + id, user, 3600);
}
return user;
}
// 2. 布隆过滤器(Bloom Filter)
// 将所有可能存在的数据哈希到一个足够大的 bitmap 中
if (!bloomFilter.mightContain(id)) {
return null; // 布隆过滤器说不在,一定不存在
}缓存击穿
某一个热点 key 过期瞬间,大量并发请求直接打到数据库。
解决方式
java
// 1. 互斥锁(分布式锁)
public User getUser(Long id) {
User user = redis.get("user:" + id);
if (user == null) {
String lockKey = "lock:user:" + id;
String lockId = UUID.randomUUID().toString();
if (redis.setnx(lockKey, lockId, 10)) { // 获取锁
try {
user = userDao.findById(id);
redis.set("user:" + id, user, 3600);
} finally {
if (lockId.equals(redis.get(lockKey))) {
redis.del(lockKey); // 释放锁
}
}
} else {
Thread.sleep(100);
return getUser(id); // 重试
}
}
return user;
}
// 2. 逻辑过期(预先设置逻辑过期时间,后台异步更新)缓存雪崩
大量缓存同时过期,或 Redis 宕机,导致请求全部打到数据库。
解决方式
java
// 1. 过期时间加随机值
int baseTtl = 3600;
int randomTtl = baseTtl + new Random().nextInt(600);
redis.set("user:" + id, user, randomTtl);
// 2. 多级缓存
// 本地缓存(Caffeine)→ Redis → 数据库
// 3. Redis 高可用
// 主从 + Sentinel / Cluster
// 4. 限流降级
// 使用 Sentinel 等服务进行流量控制对比总结
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 穿透 | 数据不存在 | 布隆过滤器 / 缓存空值 |
| 击穿 | 热点 key 过期 | 互斥锁 / 逻辑过期 |
| 雪崩 | 大量 key 同时过期或宕机 | 随机过期时间 / 多级缓存 / 高可用 |