第十节 缓存击穿
一、缓存击穿概念
缓存击穿是大量的请求同时查询同一个缓存,但是此时缓存突然失效了,那么顺理成章的,这些大量的请求就会去查询数据库。这样的后果是,短时间内数据库会出现大量的查询请求,有可能会让数据库压力过大而宕机。
二、实例代码
解决办法一:缓存击穿的一大必要条件就是热点数据失效。因此解决办法之一就是把热点数据设置为永不失效。
解决办法二:添加互斥锁。什么意思呢?就是查询同一个热点数据的多组请求,只能有一个请求走数据库,并将查询结果存放到缓存中。接下来的其他的请求会在缓存中查询到此热点数据。
互斥锁的核心命令就是SETNX命令、expire命令、delete命令。
(1)生成一个随机数作为锁。获取到锁的线程进入查询数据库的操作。将查询到的数据加入缓存。
(2)需要设置锁的过期时间,防止不释放锁。
(3)释放锁的时候,要判断锁是不是当前线程创建的锁,否则很可能释放其他线程创建的锁。
if (stringRedisService.exists(key)) {
LOGGER.info("返回缓存数据");
} else {
LOGGER.info("随机生产一个锁");
final String value = RandomStringUtils.randomAlphanumeric(10);
ValueOperations<String,String> valueOperations =
redisTemplate.opsForValue();
try{
LOGGER.info("通过Redis的SETNX命令,查看这个锁是否已经被占用");
LOGGER.info("如果返回false,说明已经有其他线程进入了,那么此线程放弃操作");
LOGGER.info("其他线程会在下面的代码中把数据加入到缓存中");
LOGGER.info("接下来的其他线程会直接走缓存,而不需要走数据库");
//setNx
Boolean lock =
valueOperations.setIfAbsent(Constant.USER_RANDOM_KEY,value);
//如果获取到锁
if(lock){
//设置锁的超时间时间
redisTemplate.expire(Constant.USER_RANDOM_KEY,10, TimeUnit.SECONDS);
LOGGER.info("从数据库中查询User");
LOGGER.info("将数据库中查询到的User加入缓存中");
LOGGER.info("因此,接下来的其他的大量的请求就会查询缓存,而不用查询数据库");
}
}finally {
LOGGER.info("释放锁");
String currentValue = valueOperations.get(Constant.USER_RANDOM_KEY);
LOGGER.info("判断是不是当前线程创建的锁");
if(StringUtils.isNotBlank(currentValue) && currentValue.equals(value)){
redisTemplate.delete(Constant.USER_RANDOM_KEY);
}
}
}
}