Redis缓存穿透 缓存击穿 解析
先解析一下Redis中什么叫做 缓存穿透 和 缓存击穿:
缓存穿透:首先我们要明确概念,缓存穿透是 在查询数据时 查询的数据在 redis 和 DB中都没有的 叫做缓穿透,解决方案,如果有这样的情况下 可以在第一次从DB中查询 判断结果是否为空 若为空 则直接 在redis中对应key的value存一个null 并且设置很短的过期时间
缓存击穿:Redis中没有 但DB中有;正常情况下我们查询同一条数据会先到Redis缓存中查取,如果没有再到数据库中查询,如果同一时间几千个用户查询同一条数据,在第一个线程到数据库中查询到了还未来得及更新到Redis里时剩下的线程也同时到数据库中查询,就无法体现Redis缓存的目的,这样被称为 Redis缓存击穿,大批量请求直接到数据库中查询,很容易导致数据库直接奔溃。
我们看一下会导致 缓存击穿 的代码:
# redisConfig是我封装的一个类
# 请求到了这个service 先去的redis查询,查询不到才会去数据库查询,但是没有添加锁 故导致了 缓存击穿
@Override
public Student findStudent(String key) {
long l1 = System.currentTimeMillis();
log.info("先去redis里面去取...");
Student student = (Student) redisConfig.get("student-"+key);
if (null == student){
log.info("redis中没有查到,到数据库中去取...");
student = studentDao.getStudent(Integer.valueOf(key));
redisConfig.set("student-"+student.getId(),student);
long l2 = System.currentTimeMillis();
log.info("l2-l1:{}",l2-l1);
}
long l3 = System.currentTimeMillis();
log.info("l3-l1:{}",l3-l1);
return student;
}
# 我用Jmeter测试了一个高并发场景 从截图中可以看到 多个线程 多次到数据库中查询了
我们再看一下加了锁之后的代码:
# 也就再方法上加了一个 synchronized
@Override
public synchronized Student findStudent(String key) {
long l1 = System.currentTimeMillis();
log.info("先去redis里面去取...");
Student student = (Student) redisConfig.get("student-"+key);
if (null == student){
log.info("redis中没有查到,到数据库中去取...");
student = studentDao.getStudent(Integer.valueOf(key));
redisConfig.set("student-"+student.getId(),student);
long l2 = System.currentTimeMillis();
log.info("l2-l1:{}",l2-l1);
}
long l3 = System.currentTimeMillis();
log.info("l3-l1:{}",l3-l1);
return student;
}
# 如截图所示 只有第一次去数据库中查询 剩下的线程都到Redis中查询 从实测的效果来看 缺点就是性能太差
我们再看一下双层检测锁的代码:
# 在方法内部加 synchronized 在 synchronized 内部再查一下redis
@Override
public Student findStudent(String key) {
long l1 = System.currentTimeMillis();
log.info("先去redis里面去取...");
Student student = redisConfig.get("student-"+key);
if (null == student){
//双层检测锁
synchronized (this){
student = redisConfig.get("student-" + key);
if (null == student){
log.info("redis中没有查到,到数据库中去取...");
student = studentDao.getStudent(Integer.valueOf(key));
redisConfig.set("student-"+student.getId(),student);
long l2 = System.currentTimeMillis();
log.info("l2-l1:{}",l2-l1);
}
}
}
long l3 = System.currentTimeMillis();
log.info("l3-l1:{}",l3-l1);
return student;
}
# 同样也解决了Redis 缓存击穿 的情况 性能会比在方法上添加 synchronized 好很多
总结:
从如上代码中可以看到 如果要解决Redis 缓存击穿的问题 在方法内添加 synchronized 即可,建议使用双层检测锁 这样可以更大程度的保证性能;