缓存穿透就是访问数据库中不存在的数据,高并发情况下或有人恶意的不停的访问该数据,导致请求打到数据库,直至数据库崩溃.
比如我们在写项目的时候,商品的主键id很少为负数.那么可能有人就会不停的访问id为-1的商品.而这个商品又不存在,导致我们的数据库崩溃.
这里我通常的解决方案是在一个请求打到数据库返回null值时,在redis中给这个商品的key存入一个empty数据.使后边的请求直接访问redis而不是再去访问数据库.
jedis.setex(key,60,"empty");
但是这并不是绝对安全的解决方案,如果你想了解更多,可以查看我的另一篇文章布隆过滤器
缓存雪崩就是我们在设置缓存数据时,设置的有效期相同,导致在同一时刻,大部分的缓存同时到期,这时候,所有的请求在redis中拿不到数据,都去访问数据库,数据库就受到了所有请求的压力,在高并发的情况下,成千上万个请求同时到达数据库,导致数据库崩溃.
对于缓存雪崩,我们通过概念可以发现,之所以会雪崩,就是因为多个缓存同时到期,那我们让他不同时到期就可以了,实现方式就是利用setex命令和Random类生成随机数的方式为缓存设置随机有效时间.这样就基本上不会出现多个缓存同时到期的现象了,也就是基本上不会出现缓存雪崩的现象了.
int random=new Random().nextInt(60*10);
jedis.setex(key,random,"empty");
缓存击穿像
是缓存穿透和缓存雪崩的结合,意思是一个数据太热点,在高并发情况下,这个数据的缓存到期,成千上万的请求又绕过了redis直接打到了数据库,导致数据库的崩溃.
分布式锁.
分布式锁就是一把限制人流的锁,像一个保安一样,守护着进入数据库的入口.对于抢到锁的那一个请求才会放行,后面的请求只能排队.等待抢到锁的人进入数据库将数据从数据库取出来存到缓存中,才允许后面的人进来访问,此时访问的时候,前面的人已经将数据存放到缓存中,则后面的人就可以从Redis中取数据而不是直接访问数据库了.
关于分布式锁的具体实现方式及解释请参考我的这篇文章
缓存击穿和缓存雪崩都是缓存穿透,只不过是它的特殊表现形式
public Student selectStudentById(Integer id) {
String key="student-20-10-2:"+id+"-key";
String lockKey=key+":lock-"+id;
String value= UUID.randomUUID().toString();
Jedis jedis = redisUtils.getJedis();
String s = jedis.get(key);
Student student=null;
if (s==null){
System.out.println("key:["+key+"]访问数据库获取数据");
RLock lock = redissonClient.getLock(lockKey);
lock.lock(50, TimeUnit.SECONDS);
/* String set = jedis.set(lockKey, value, "NX", "EX", 50);*/
if (lock.isLocked()){
student = studentMapper.selectByPrimaryKey(id);
int random=new Random().nextInt(60*10);
if (student==null){
jedis.setex(key,random,"empty");
}else{
jedis.setex(key,random,JSON.toJSONString(student));
}
/* if (jedis.get(lockKey).equals(value)){
jedis.del(lockKey);
}*/
lock.unlock();
}else{
/*没有抢到锁,进行回调*/
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return selectStudentById(id);
}
}else{
System.out.println("key:["+key+"]从Redis中获取数据");
if (s.equals("empty")) {
student=null;
}else{
student = JSON.parseObject(s, Student.class);
}
}
return student;
}