本文目录导读:

Java缓存失效问题(如缓存雪崩、缓存击穿、缓存穿透)是高并发场景下的经典坑点,下面从问题现象、根本原因到规避方案进行系统梳理,并给出可落地的代码示例。
三大经典缓存失效场景
缓存雪崩(大规模同时失效)
现象:大量缓存Key在同一时间过期,请求全部打到数据库,导致DB压力激增甚至宕机。
典型原因:
- 所有缓存设置了相同的过期时间(如统一30分钟)
- 缓存服务器宕机(如Redis集群故障)
规避方案:
✅ 过期时间加随机偏移
// 原代码:set("user:1", user, 3600)
// 改进:在基础过期时间上增加随机值
int base = 3600;
int random = new Random().nextInt(600); // 0~600秒随机
redisTemplate.opsForValue().set("user:1", user, base + random, TimeUnit.SECONDS);
✅ 多级缓存(本地+远程)
- 本地缓存(Caffeine/Guava)设置较短过期时间
- Redis设置较长过期时间
- DB作为最终保底
✅ 服务熔断/降级
- 当DB负载过高时,直接返回默认值或空数据,避免连锁反应
缓存击穿(热点Key突然失效)
现象:一个极其热门的Key(如秒杀商品、热搜)在失效瞬间,大量并发请求同时穿透到DB。
典型原因:
- 热点Key过期,且重建缓存耗时较长(如复杂SQL查询)
规避方案:
✅ 互斥锁(分布式锁)
public String getData(String key) {
String value = redis.get(key);
if (value == null) {
// 尝试获取分布式锁,防止并发重建
String lockKey = "lock:" + key;
if (redis.setnx(lockKey, "1", 3, TimeUnit.SECONDS)) {
try {
// 二次检查,防止重复查询(double-check)
value = redis.get(key);
if (value == null) {
value = queryFromDB(key); // 耗时操作
redis.set(key, value, 3600);
}
} finally {
redis.del(lockKey);
}
} else {
// 未获取到锁,等待后重试或直接返回默认值
Thread.sleep(100);
return getData(key);
}
}
return value;
}
✅ 主动续期(热Key永不过期)
- 不设置物理过期时间,改为逻辑过期
- 后台线程定期检查并异步更新缓存
- 查询时发现逻辑过期,返回旧数据同时触发异步更新
缓存穿透(请求不存在的数据)
现象:缓存和DB中都没有的数据(如非法ID查询),每次请求都穿透到DB。
典型原因:
- 恶意攻击或程序bug,不断请求不存在的数据
规避方案:
✅ 缓存空值(布隆过滤器兜底)
public String getData(String key) {
// 方案A:缓存空对象(设置较短过期时间)
String value = redis.get(key);
if (value == null) {
value = queryFromDB(key);
if (value == null) {
// 缓存空值,避免反复穿透
redis.set(key, "null", 60); // 空值过期时间短
return null;
}
redis.set(key, value, 3600);
}
// 注意:空值标记不能直接返回,需特殊处理
return "null".equals(value) ? null : value;
}
✅ 布隆过滤器(预过滤)
// 初始化布隆过滤器(所有合法ID的集合)
BloomFilter<String> bloomFilter = BloomFilter.create(
Funnels.stringFunnels(),
expectedInsertions,
falsePositiveRate);
// 查询前先判断
if (!bloomFilter.mightContain(key)) {
return null; // 一定不存在,直接返回
}
// 再走缓存-数据库流程
注意:布隆过滤器有误判率(可能判断存在但实际不存在),需配合空值缓存兜底。
综合实战:一个健壮的缓存模板
@Service
public class CacheService {
@Autowired
private RedisTemplate<String, Object> redis;
// 10分钟内,同一个Key最多只能重建一次
private static final long REBUILD_LOCK_TIMEOUT = 10 * 60 * 1000L;
// 缓存基础过期时间:30分钟
private static final long CACHE_BASE_TTL = 30 * 60L;
public <T> T getWithProtection(String key, Class<T> type,
Supplier<T> dbLoader) {
// 1. 从缓存读取
Object value = redis.opsForValue().get(key);
if (value != null) {
return handleNullValue((String) value, type);
}
// 2. 缓存不存在,尝试重建(分布式锁防击穿)
String lockKey = "lock:rebuild:" + key;
boolean locked = redis.opsForValue()
.setIfAbsent(lockKey, "1", REBUILD_LOCK_TIMEOUT, TimeUnit.MILLISECONDS);
if (locked) {
try {
// 3. double check:可能其他线程已经重建完
value = redis.opsForValue().get(key);
if (value != null) {
return handleNullValue((String) value, type);
}
// 4. 从数据库加载
T dbResult = dbLoader.get();
// 5. 策略:空值缓存+随机过期时间,避免雪崩
String cacheValue = (dbResult == null) ? "NULL_MARK" : JsonUtil.toJson(dbResult);
long ttl = CACHE_BASE_TTL + new Random().nextInt(600); // 0~10分钟随机偏移
redis.opsForValue().set(key, cacheValue, ttl, TimeUnit.SECONDS);
return dbResult;
} finally {
redis.delete(lockKey);
}
} else {
// 6. 未获取到锁,等待其他线程完成
try {
Thread.sleep(50);
return getWithProtection(key, type, dbLoader);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
return null;
}
private <T> T handleNullValue(String value, Class<T> type) {
if ("NULL_MARK".equals(value)) {
return null;
}
return JsonUtil.fromJson(value, type);
}
}
避坑清单(面试/开发自查)
| 问题类型 | 常见错误做法 | 正确做法 |
|---|---|---|
| 雪崩 | 统一过期时间 | 基础时间 + 随机偏移 / 多级缓存 |
| 击穿 | 不加锁,直接重建 | 分布式锁 + double-check / 逻辑过期+异步更新 |
| 穿透 | 不判断null直接查DB | 布隆过滤器 + 空值缓存(短TTL) |
| 热点Key | 单节点缓存 | 本地缓存(Caffeine)+ Redis + 冗余节点 |
| 大Key | 直接缓存整个列表/对象 | 拆分为多个小Key / 压缩 |
| 缓存与DB一致性 | 先删缓存再更新DB | 先更新DB,再删缓存(延迟双删) |
关键原则:
- 先更新数据库,后删除缓存(避免并发写脏数据)
- 缓存设置过期时间(即使逻辑正确,也要有兜底TTL)
- 监控告警:Redis命中率、DB QPS异常波动要实时感知
如果需要针对具体业务场景(如秒杀、排行榜)的缓存优化策略,可以进一步细化说明。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播