心里有爱眼里有光

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

一、常用的redis缓存处理流程

 

二、缓存穿透

  当前端请求一个不存在的数据时,按照上述流程,会先从redis中查询,未查到数据,然后到后端数据库查询,也未查到数据,这时返回空数据(返回为空的数据不写入redis)。下次再查询此数据,由于redis中还是没有此数据,因此还会到后端数据库查询。像这样每次都穿过redis到后端数据库查询的情况,就叫缓存穿透。若有人恶意用此方式大量查询,则可能导致后端数据库崩溃。

 

  解决方案

    1、前端优化:参数验证(页面端、接口端等),从前端拦截无效的查询。

    2、后端优化:从数据库中查询出为空时,将key-value对以key-null写入redis,这样大量查询时就从redis中查到,不会再穿透到后端数据库。同时,为key-null设置过期时间,一般为(30s~5m),设置太短,会有大量穿透请求到后端数据库。设置太长或不设置超时时间(该key会一直存在),后面没有恶意请求来时,该key依然占用内存。若这种key很多时,可能会导致内存溢出。

public object getOrderList() {
    int cacheTime = 30;
    String cacheKey = "orderList";

    cacheValue = CacheHelper.Get(cacheKey);
    if (cacheValue != null) {
        return cacheValue;
    } else {
        cacheValue = getOrderListFromDB();
     if(null == cacheValue){
             cacheValue = "";
     }
        CacheHelper.Add(cacheKey, cacheValue, cacheTime);  //即使
        return cacheValue;
    }
}    

 

三、缓存击穿

  对于设置了过期时间的热点key(在某些时候会有很高的并发访问),若超高并发访问时,刚好热点key到期,这时会出现redis中该key数据查不到,而后端数据库能查到。

  在这些并发访问线程中,第一个线程会去数据库中查询并将结果写入redis,在该操作执行期间,redis中还没有数据,这时其他线程在redis中查询不到数据就会到后端数据库去查,大量的请求可能导致后端数据库崩溃,这就叫缓存击穿。

  解决方案

  1、使用互斥锁

     缓存失效时(redis中取出值为null时),加锁成功的线程去数据库查询,并放入redis,其他线程等待一段时间后重新从redis中获取。

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);  
          } else {  //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可  
               sleep(50);  
               get(key);  //重试  
          }  
      } else {      
           return value;        
      }  
 }
  2、不设置过期时间
    在redis的设置过期时间功能上不设置过期时间。
    把过期时间设置在value里,发现如果过期了,通过异步线程去更新redis。
String get(final String key) {    
        V v = redis.get(key);    
        String value = v.getValue();    
        long timeout = v.getTimeout();      //获取时间
        if (v.timeout <= System.currentTimeMillis()) {     //发现过期了 
            // 异步更新后台异常执行    
            threadPool.execute(new Runnable() {      //可能短时间内出现很多个线程
                public void run() {    
                    String keyMutex = "mutex:" + key;    
                    if (redis.setnx(keyMutex, "1")) {    //保证只有1个线程会执行查询后端数据库并更新redis的操作  
                        // 3 min timeout to avoid mutex holder crash    
                        redis.expire(keyMutex, 3 * 60);    
                        String dbValue = db.get(key);    
                        redis.set(key, dbValue);    
                        redis.delete(keyMutex);    
                    }    
                }    
            });    
        }    
        return value;    
} 

四、缓存雪崩

  大量redis数据同时到过期时间,即使做了防缓存击穿处理,由于到期的数据量过大,还是会有很多线程同时访问后端数据库,从而可能导致后端数据库崩溃。这就是缓存雪崩。

  redis挂掉后,请求也会直接访问后端数据库,从而引起雪崩。

  解决方案

  1、缓存数据的过期时间设置随机(固定值+随机值的方式),防止同一时间大量过期。

  2、最好采用redis集群,可以将热点数据分布在不同的节点上,防止部分redis挂掉后导致的雪崩问题。

  3、对于热点数据,可以设置为永不过期,通过定时更新的方式来刷新缓存。

  4、从系统上进行限流(服务限流、接口限流等),控制请求数量。

  

  

 

 

posted on 2020-11-11 14:51  心里有爱眼里有光  阅读(94)  评论(0编辑  收藏  举报