缓存击穿概念
缓存击穿也可以理解成热点key问题,顾名思义就是存在一个被频繁访问的热点key数据,但是这个key突然过期或者由于其他原因失效了,并且此时针对于此key的新的缓存还未建立完成,于是所有的请求直接全部打到了数据库中,导致数据库压力激增,存在宕机风险
互斥锁
互斥锁保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。最后给锁也添加过期时间。
流程大概是这样
首先我们假设有两个线程A,B同时访问服务请求一个key的数据,线程A进入查询流程,首先会去从缓存中获取数据,如果此时没有从缓存中拿取到对应的数据,会尝试去获取互斥锁,获取到互斥锁之后才会去建立数据库查询去访问数据库获取数据,获取到之后再写入缓存中,最后再释放锁。当线程A还在查询流程中的时候,此时线程B进入查询流程,也是一样先尝试从缓存中获取数据,如果没有查询出来,就尝试去获取互斥锁从数据库中查询,而此时由于线程A已经占有了互斥锁,而互斥锁只允许同一时间只有一个线程在操作,所以一般流程下,线程B会进入一段休眠,等休眠时间过去之后再去尝试获取互斥锁,进入一个休眠自旋的状态,直到获取到互斥锁为止,如果在自旋状态中缓存中已经被写入了这个key的值,那么线程B则会在自旋的第一步就直接从缓存中读取到值然后直接返回了。
代码实现
//缓存击穿实现
private String redisTest1(String userName) throws Exception {
//首先请求进来,直接从redis查询查看是否存在
String redisResult = stringRedisTemplate.opsForValue().get("stefentest");
//判断redis中是否存在
if(redisResult != null){
//如果存在,就直接返回
return "redis查询成功,result结果为"+redisResult;
}
//如果redis中数据未命中,则构建数据库查询
ReentrantLock lock = new ReentrantLock();
try{
lock.tryLock();
if(lock.tryLock()){
System.out.println("上锁,不允许其他线程构建数据库查询");
String localResult = this.selectByUsername(userName);
if(localResult != null){
//如果数据库中也没有查找到,此时就需要我们缓存一个空值进redis中,这样在缓存穿透的的时候,即使有大量请求进来
//也不会因为该值找不到而一直将海量请求打到数据库中,导致数据库宕机
stringRedisTemplate.opsForValue().set(userName,localResult);
//同时返回异常信息
return "数据库中查询到对应数据,将数据写入redis中";
}else {
//如果数据库中也没有查找到,此时就需要我们缓存一个空值进redis中,这样在缓存穿透的的时候,即使有大量请求进来
//也不会因为该值找不到而一直将海量请求打到数据库中,导致数据库宕机
stringRedisTemplate.opsForValue().set(userName,"null");
}
}else {
Thread.sleep(100);
return redisTest1(userName);
}
}catch (Exception e){
throw new Exception(e.getMessage());
}
return "";
}