缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
相比较来看,显然Redis宕机带来的影响更大
解决方案:
- 给不同的Key的TTL添加随机值
- 利用Redis集群提高服务的可用性
- 给缓存业务添加降级限流策略
- 给业务添加多级缓存(例如在反向代理服务器nginx再添加一层缓存、在jvm内部添加一层缓存等)
反向代理服务器nginx添加一层缓存,nginx缓存未命中在查询redis,redis未命中到达jvm,在jvm内部添加一层缓存,最后才会查询数据库
案例
在单机的系统中,通常使用给不同的Key的TTL添加随机值的方案来解决缓存雪崩的问题
public Shop queryWithPassThrough(Long id) {
String key = "cache:shop:" + id;
// 1. 从redis查询商铺缓存
String shopJson = stringRedisTemplate.opsForValue().get(key);
// 2. 判断是否存在
if (StrUtil.isNotBlank(shopJson)) {
// 3. 存在,转成Java对象,直接返回
return JSONUtil.toBean(shopJson, Shop.class);
}
// 处理缓存穿透(空值占位)
if (shopJson != null) {
return null;
}
// 4. 不存在,根据id查询数据库
Shop shop = getById(id);
// 5. 不存在,返回错误
if (shop == null) {
// 写入空值并设置随机TTL(避免缓存穿透)
stringRedisTemplate.opsForValue().set(
key,
"",
generateRandomTTL(60, 120), // 随机60~120秒
TimeUnit.SECONDS
);
return null;
}
// 6. 存在,写入redis并设置随机TTL
stringRedisTemplate.opsForValue().set(
key,
JSONUtil.toJsonStr(shop),
generateRandomTTL(1800, 600), // 随机25~35分钟(基础30分钟±5分钟)
TimeUnit.SECONDS
);
// 7. 返回
return shop;
}
// 生成随机TTL的方法(基础值±偏移量)
private long generateRandomTTL(int baseSeconds, int offsetSeconds) {
// 使用ThreadLocalRandom生成安全的随机数
long randomOffset = ThreadLocalRandom.current().nextLong(-offsetSeconds, offsetSeconds + 1);
// 确保TTL不小于最小值(如10秒)
return Math.max(10, baseSeconds + randomOffset);
}