缓存击穿
在某些 Key 属于极端热点数据,且并发量很大的情况下,如果这个 Key 过期,可能会在某个瞬间出现大量的并发请求同时回源,相当于大量的并发请求直接打到了数据库。这种情况,就是我们常说的缓存击穿或缓存并发问题。
*** 现象 ** :数据库的访问压力瞬间激增,Redis正常运行
*** 解决办法**
-
业务允许下,设置缓存永不过期
- 启动一个后台线程 30 秒一次定时把所有数据更新到缓存,而且通过适当的休眠,控制从数据库更新数据的频率,降低数据库压力
-
使用互斥锁
- 缓存失效的时候(判断拿出来的值为空),不是立即去加载数据库。
- 先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX)去set一个mutex key。
- 当操作返回成功时,再进行加载数据库的操作,并回设缓存,最后删除mutex key。
- 当操作返回失败,证明有线程在加载数据库,当前线程睡眠一段时间再重试整个get缓存的方法。
@Autowired
private RedissonClient redissonClient;
@GetMapping("mutex")
public String mutex() {
String data = stringRedisTemplate.opsForValue().get("hot_key");
if (StringUtils.isEmpty(data)) {
RLock locker = redissonClient.getLock("mutex_locker");
//获取分布式锁
if (locker.tryLock()) {
try {
data = stringRedisTemplate.opsForValue().get("hot_key");
//双重检查,因为可能已经有一个B线程过了第一次判断,在等锁,然后A线程已经把数据写入了Redis中
if (StringUtils.isEmpty(data)) {
//回源到数据库查询
data = getDatabaseData();
stringRedisTemplate.opsForValue().set("hot_key", data, 5, TimeUnit.SECONDS);
}
} finally {
//别忘记释放,另外注意写法,获取锁后整段代码try+finally,确保unlock万无一失
locker.unlock();
}
}
}
return data;
}
缓存穿透和缓存击穿的区别:
- 缓存穿透是指,缓存没有起到压力缓冲的作用;
- 而缓存击穿是指,缓存失效时瞬时的并发打到数据库。