1.缓存击穿
缓存击穿是指某个热点数据key在缓存中失效,大量的请求同时涌向后台数据库,对数据库造成很大的压力。
解决方案:
- 逻辑过期:在缓存数据中存储逻辑过期时间(如字段
expireTime),实际缓存不设物理过期。当数据过期时,异步线程更新缓存,其他线程仍返回旧数据。 - 互斥锁:在过期后,通过加锁控制对数据库的访问,从而保证只有一个请求能够去查询数据库并更新缓存。

/**
* 逻辑过期方案解决缓存击穿问题
*
* @param id
* @return
*/
public Shop queryWithLogicalExpire(Long id) {
//1.从redis中查询商铺缓存
String shopJson = stringRedisTemplate.opsForValue().get("cache:shop:" + id);
//2.判断是否存在
if (StrUtil.isBlank(shopJson)) {
//3.不存在,直接返回
return null;
}
//4.命中,需要先把json反序列化为对象
RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
JSONObject data = (JSONObject) redisData.getData();
Shop shop = JSONUtil.toBean(data, Shop.class);
LocalDateTime expireTime = redisData.getExpireTime();
//5.判断是否过期
if (expireTime.isAfter(LocalDateTime.now())) {
//5.1未过期,直接返回店铺信息
return shop;
}
//5.2已过期,需要缓存重建
//6缓存重建
//6.1获取互斥锁
String lockKey = LOCK_SHOP_KEY + id;
boolean isLock = tryLock(lockKey);
//6.2判断是否获取锁成功
if (isLock) {
//获取互斥锁成功,再次对redis缓存做DoubleCheck
shopJson = stringRedisTemplate.opsForValue().get("cache:shop:" + id);
redisData = JSONUtil.toBean(shopJson, RedisData.class);
data = (JSONObject) redisData.getData();
shop = JSONUtil.toBean(data, Shop.class);
expireTime = redisData.getExpireTime();
if (expireTime.isAfter(LocalDateTime.now())) {
//6.2.1未过期,直接返回店铺信息
unLock(lockKey);
return shop;
}
//6.2.2已过期,开启独立线程,实现缓存重建
CACHE_REBUILD_EXECUTOR.submit(() -> {
try {
this.saveShop2Redis(id, 20L);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
unLock(lockKey);
}
});
}
return shop;
}
要点:在缓存重建时需要双重检查
2.缓存穿透
缓存穿透是指用户请求的数据在缓存和数据库中都不存在,从而导致每次请求都落到数据库上。
解决方案:
- 缓存空对象:缓存空结果,但要设置短的过期时间。
- 布隆过滤器:在访问缓存之前进行一次快速判断,过滤掉不可能存在的数据请求。由于哈希碰撞,会存在一定的误判率,但是这个误判率非常低。
/**
* 缓存穿透
*
* @param id
* @return
*/
public Shop queryWithPassThrough(Long id) {
//1.从redis中查询商铺缓存
String shopJson = stringRedisTemplate.opsForValue().get("cache:shop:" + id);
//2.判断是否存在
if (StrUtil.isNotBlank(shopJson)) {
//3.存在,直接返回
return JSONUtil.toBean(shopJson, Shop.class);
}
//判断命中的是否是空值
if (shopJson != null) {
return null;
}
//4.不存在,根据id查询数据库
Shop shop = getById(id);
//5.数据库也不存在,返回错误
if (shop == null) {
//将空值写入redis
stringRedisTemplate.opsForValue().set("cache:shop:" + id, "", 2L, TimeUnit.MINUTES);
return null;
}
//6.存在,写入redis
stringRedisTemplate.opsForValue().set("cache:shop:" + id, JSONUtil.toJsonStr(shop), 30L, TimeUnit.MINUTES);
//7.返回
return shop;
}
3.缓存雪崩
缓存雪崩:是指在某一时刻,大量缓存数据共同过期,导致瞬时大流量涌向数据库。或者由于分布式缓存节点故障导致缓存失效。
解决方案:
- 过期时间设置为随机:避免大量缓存同一时间过期
- 多级缓存架构:采用本地缓存(如Caffeine)+分布式缓存(如Redis)组合,本地缓存作为二级缓冲层。