Java教程

什么是缓存击穿,有什么解决方案

Java1000个问题 小海豚博客管理员 2020-04-07 22:44:52.0 202 0条

缓存(内存 or Memcached or Redis…..)在互联网项目中广泛应用,本篇博客将讨论下缓存击穿这一个话题,涵盖缓存击穿的现象、解决的思路、以及通过代码抽象方式来处理缓存击穿。

什么是缓存击穿?

  1. @Service
  2. public class Cachesexice {
  3. @Autowired
  4. private JedisCluster jedisCluster;
  5. @Autowired
  6. private StudentMapper sudenMaRReF;
  7. public List<Student> guerx() {
  8. String key ="studentCache";
  9. String json = jedisCluster.get (key);
  10. if(json == null ||"".equals (json) || "nul1" . equalsIgnoreCase(json)) {
  11. List<Student> studentList = studentMapper. findStudent() ;
  12. //加入缓存
  13. jedisCluster.set (keyJSON.toJSONString (studentList)) ;
  14. //设置缓存的有效期
  15. jedisCluster . expire (key, 60);
  16. return studentList ;
  17. }
  18. return JSON.parseArray (json, Student.class) ;
  19. }

上面的代码,是一个典型的写法:当查询的时候,先从Redis集群中取,如果没有,那么再从DB中查询并设置到Redis集群中。

注意,在实际开发中,我们一般在缓存中,存储的数据结构是JSON。(JDK提供的序列化方式效率稍微比JSON序列化低一些;而且JDK序列化非常严格,字段的增减,就很可能导致反序列失败,而JSON这方面兼容性较好)

假设从DB中查询需要2S,那么显然这段时间内过来的请求,在上述的代码下,会全部走DB查询,相当于缓存被直接穿透,这样的现象就称之为“缓存击穿”!

避免缓存击穿的思路分析

加synchronized?

  1. @Service
  2. public class Cachesexice {
  3. @Autowired
  4. private JedisCluster jedisCluster;
  5. @Autowired
  6. private StudentMapper sudenMaRReF;
  7. public synchronized List<Student> guerx() {
  8. String key ="studentCache";
  9. String json = jedisCluster.get (key);
  10. if(json == null ||"".equals (json) || "nul1" . equalsIgnoreCase(json)) {
  11. List<Student> studentList = studentMapper. findStudent() ;
  12. //加入缓存
  13. jedisCluster.set (keyJSON.toJSONString (studentList)) ;
  14. //设置缓存的有效期
  15. jedisCluster . expire (key, 60);
  16. return studentList ;
  17. }
  18. return JSON.parseArray (json, Student.class) ;
  19. }

如果synchronized加在方法上,使得查询请求都得排队,本来我们的本意是让并发查询走缓存。也就是现在synchronized的粒度太大了。

缩小synchronized的粒度?

  1. @Service
  2. public class Cachesexice {
  3. @Autowired
  4. private JedisCluster jedisCluster;
  5. @Autowired
  6. private StudentMapper sudenMaRReF;
  7. public List<Student> guerx() {
  8. String key ="studentCache";
  9. String json = jedisCluster.get (key);
  10. if(json == null ||"".equals (json) || "nul1" . equalsIgnoreCase(json)) {
  11. synchronized(this){
  12. List<Student> studentList = studentMapper. findStudent() ;
  13. //加入缓存
  14. jedisCluster.set (keyJSON.toJSONString (studentList)) ;
  15. //设置缓存的有效期
  16. jedisCluster . expire (key, 60);
  17. return studentList ;
  18. }
  19. }
  20. return JSON.parseArray (json, Student.class) ;
  21. }

上面代码,在缓存有数据时,让查询缓存的请求不必排队,减小了同步的粒度。但是,仍然没有解决缓存击穿的问题。

虽然,多个查询DB的请求进行排队,但是即便一个DB查询请求完成并设置到缓存中,其他查询DB的请求依然会继续查询DB!

synchronized+双重检查机制

  1. @Service
  2. public class Cachesexice {
  3. @Autowired
  4. private JedisCluster jedisCluster;
  5. @Autowired
  6. private StudentMapper sudenMaRReF;
  7. public List<Student> guerx() {
  8. String key ="studentCache";
  9. String json = jedisCluster.get (key);
  10. if(json == null ||"".equals (json) || "nul1" . equalsIgnoreCase(json)) {
  11. synchronized(this){
  12. json = jedisCluster.get (key);
  13. if(json == null ||"".equals (json) || "nul1" . equalsIgnoreCase(json)) {
  14. List<Student> studentList = studentMapper. findStudent() ;
  15. //加入缓存
  16. jedisCluster.set (keyJSON.toJSONString (studentList)) ;
  17. //设置缓存的有效期
  18. jedisCluster . expire (key, 60);
  19. return studentList ;
  20. }
  21. }
  22. }
  23. return JSON.parseArray (json, Student.class) ;
  24. }

通过synchronized+双重检查机制:

在同步块中,继续判断检查,保证不存在,才去查DB。

暗锚,解决锚点偏移

文章评论

嘿,来试试登录吧!