1.缓存穿透
概念
缓存穿透就是访问数据库中不存在的数据,高并发情况下或有人恶意的不停的访问该数据,导致请求打到数据库,直至数据库崩溃. 比如我们在写项目的时候,商品的主键id很少为负数.那么可能有人就会不停的访问id为-1的商品.而这个商品又不存在,导致我们的数据库崩溃.
解决方案
这里我通常的解决方案是在一个请求打到数据库返回null值时,在redis中给这个商品的key存入一个empty数据.使后边的请求直接访问redis而不是再去访问数据库.
jedis
.setex(key
,60,"empty");
但是这并不是绝对安全的解决方案,如果你想了解更多,可以查看我的另一篇文章布隆过滤器
2.缓存雪崩
概念
缓存雪崩就是我们在设置缓存数据时,设置的有效期相同,导致在同一时刻,大部分的缓存同时到期,这时候,所有的请求在redis中拿不到数据,都去访问数据库,数据库就受到了所有请求的压力,在高并发的情况下,成千上万个请求同时到达数据库,导致数据库崩溃.
解决方案
对于缓存雪崩,我们通过概念可以发现,之所以会雪崩,就是因为多个缓存同时到期,那我们让他不同时到期就可以了,实现方式就是利用setex命令和Random类生成随机数的方式为缓存设置随机有效时间.这样就基本上不会出现多个缓存同时到期的现象了,也就是基本上不会出现缓存雪崩的现象了.
int random
=new Random().nextInt(60*10);
jedis
.setex(key
,random
,"empty");
3.缓存击穿
概念
缓存击穿像是缓存穿透和缓存雪崩的结合,意思是一个数据太热点,在高并发情况下,这个数据的缓存到期,成千上万的请求又绕过了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
);
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
));
}
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
;
}