redis击穿,穿透,雪崩以及解决方案

  • 作者: 凯哥Java(公众号:凯哥Java)
  • Redis
  • 时间:2020-07-05 23:00
  • 4138人已阅读
简介 1击穿:指的是单个key在缓存中查不到,去数据库查询,这样如果数据量不大或者并发不大的话是没有什么问题的。  如果数据库数据量大并且是高并发的情况下那么就可能会造成数据库压力过大而崩溃注意:这里指的是单个key发生高并发!!!解决方案: 1)通过synchronized+双重检查机制:某个key只让一个线程查询,阻塞其它线程   在同步块

🔔🔔🔔好消息!好消息!🔔🔔🔔

有需要的朋友👉:联系凯哥 微信号 kaigejava2022

1 击穿: 指的是单个key在缓存中查不到,去数据库查询,这样如果数据量不大或者并发不大的话是没有什么问题的。


   如果数据库数据量大并且是高并发的情况下那么就可能会造成数据库压力过大而崩溃


注意: 这里指的是单个key发生高并发!!!


解决方案:


  1) 通过synchronized+双重检查机制:某个key只让一个线程查询,阻塞其它线程


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


  例如:    


  private static volaite Object lockHelp=new Object();


   public String getValue(String key){


     String value=redis.get(key,String.class);


     if(value=="null"||value==null||StringUtils.isBlank(value){


         synchronized(lockHelp){


                value=redis.get(key,String.class);


                 if(value=="null"||value==null||StringUtils.isBlank(value){


                     value=db.query(key);


                      redis.set(key,value,1000);


                  }


            }


           }    


        return value;


   }


缺点: 会阻塞其它线程


   2) 设置value永不过期


       这种方式可以说是最可靠的,最安全的但是占空间,内存消耗大,并且不能保持数据最新 这个需要根据具体的业务逻辑来做 


     个人觉得如果要保持数据最新不放这么试试,仅供参考:


      起个定时任务或者利用TimerTask 做定时,每个一段时间多这些值进行数据库查询更新一次缓存,当然前提时不会给数据库造成压力过大(这个很重要)


   3) 使用互斥锁(mutex key)


    业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。


SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。在redis2.6.1之前版本未实现setnx的过期时间,所以这里给出两种版本代码参考:


public String get(key) {

      String value = redis.get(key);

      if (value == null) { //代表缓存值过期

          //设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db

          if (redis.setnx(key_mutex, 1, 3 * 60) == 1) {  //代表设置成功

               value = db.get(key);

                      redis.set(key, value, expire_secs);

                      redis.del(key_mutex);


                     return value;

              } else {  //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可

                      sleep(10);

                      get(key);  //重试

              }

          } else {

              return value;      

          }


}


缺点:


1. 代码复杂度增大


2. 存在死锁的风险


3. 存在线程池阻塞的风险


 


2 雪崩


雪崩指的是多个key查询并且出现高并发,缓存中失效或者查不到,然后都去db查询,从而导致db压力突然飙升,从而崩溃。


出现原因: 1 key同时失效


                 2 redis本身崩溃了


方案:


在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。(跟击穿的第一个方案类似,但是这样是避免不了其它key去查数据库,只能减少查询的次数)

可以通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存

不同的key,设置不同的过期时间,具体值可以根据业务决定,让缓存失效的时间点尽量均匀

做二级缓存,或者双缓存策略。A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。(这种方式复杂点)

3 击透


   一般是出现这种情况是因为恶意频繁查询才会对系统造成很大的问题: key缓存并且数据库不存在,所以每次查询都会查询数据库从而导致数据库崩溃。


解决方案:


          1) 使用布隆过滤器: 热点数据等场景(具体看使用场景)


                


布隆过滤器是什么?


布隆过滤器可以理解为一个不怎么精确的 set 结构,当你使用它的 contains 方法判断某个对象是否存在时,它可能会误判。但是布隆过滤器也不是特别不精确,只要参数设置的合理,它的精确度可以控制的相对足够精确,只会有小小的误判概率。


当布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在。打个比方,当它说不认识你时,肯定就不认识;当它说见过你时,可能根本就没见过面,不过因为你的脸跟它认识的人中某脸比较相似 (某些熟脸的系数组合),所以误判以前见过你。


缺点: 1 会存在一定的误判率


          2  对新增加的数据无法进行布隆过滤


          3 数据的key不会频繁的更改


   google 的 gauva 中有布隆过滤的实现

fac17e99879bee7263c9eb3eca3b9769.png



     BloomFilter的关键在于hash算法的设定和bit数组的大小确定,通过权衡得到一个错误概率可以接受的结果。


     我们设置的容错率越小那么过滤函数也就多,分配的空间也就越大(存放bits),那么误判率也就越小。


2 将击透的key缓存起来,但是时间不能太长,下次进来是直接返回不存在,但是这种情况无法过滤掉动态的key,就是说每次请求进来都是不同额key,这样还是会造成这个问题


 


原文链接:https://blog.csdn.net/qq_27409289/article/details/85885121


TopTop