什么是缓存穿透?
当大量并发访问时,首批并发会在没有查询到缓存的情况下集体访问数据库,造成缓存暂时性无效。
话不多说,直接上代码,先创建一个线程池
//创建一个线程池
ExecutorService executorService = Executors.newFixedThreadPool(4 * 2);
for (int i=0; i<5000; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
studentService.getStudentById(1);
}
});
}
调用方法
public Student getStudentById(Integer id) {
redisTemplate.setKeySerializer(new StringRedisSerializer());
//查询缓存
Student student = (Student) redisTemplate.opsForValue().get("studentKey");
//判断缓存是否为空
if (null == student) {
System.out.println("查询了数据库......");
//查询数据库
student = studentMapper.selectByPrimaryKey(id);
//放入缓存
redisTemplate.opsForValue().set("studentKey", student);
} else {
System.out.println("查询了缓存......");
}
return student;
}
结果:

显而易见,在第一批并发下的第一个查询还没存入redis的时候,后面几个线程已经去找数据库的要完数据了。如果第一批并发体量很大,数据库就有可能崩溃。
怎么解决哪?
第一个解决方案:
在方法上加synchronized,让他们排队访问。

运行输出:

这个解决方案是有效的,但是这个解决方案存在明显的弊端,效率慢到姥姥家了。5w个并发请求,跑了好几分钟。
尝试第二个解决方案:
public /*synchronized*/ Student getStudentById(Integer id) {
redisTemplate.setKeySerializer(new StringRedisSerializer());
//查询缓存
Student student = (Student) redisTemplate.opsForValue().get("studentKey");
//判断缓存是否为空
if (null == student) {
//双重检测锁实现
synchronized (this) {
student = (Student) redisTemplate.opsForValue().get("studentKey");
if (null == student) {
System.out.println("查询了数据库......");
//查询数据库
student = studentMapper.selectByPrimaryKey(id);
//放入缓存
redisTemplate.opsForValue().set("studentKey", student);
}
}
} else {
System.out.println("查询了缓存......");
}
return student;
}
运行结果:

效率大大的提升上来了,这就是双重检测机制,那么问题来了。
问问各位小伙伴:
这个synchornzied(this){ } 锁住了谁?
如果把第二次缓存查询去掉,结果会怎样?