什么是缓存穿透?
当大量并发访问时,首批并发会在没有查询到缓存的情况下集体访问数据库,造成缓存暂时性无效。
话不多说,直接上代码,先创建一个线程池
//创建一个线程池 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){ } 锁住了谁?
如果把第二次缓存查询去掉,结果会怎样?