redis缓存异常

缓存雪崩

  1. 什么是缓存雪崩?
    缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
  2. 解决方案
    1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
    2. 一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。
      伪代码:
      /*
      注意:加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。
      假设在高并发下,缓存重建期间key是锁着的,这是过来1000个请求999个都在阻塞的。
      同样会导致用户等待超时,这是个治标不治本的方法!
      加锁排队的解决方式分布式环境的并发问题,有可能还要解决分布式锁的问题;
      线程还会被阻塞,用户体验很差!因此,在真正的高并发场景下很少使用!
      */
      //伪代码
      public object GetProductListNew() {
              
              
          int cacheTime = 30;
          String cacheKey = "product_list";
          String lockKey = cacheKey;
      
          String cacheValue = CacheHelper.get(cacheKey);
          if (cacheValue != null) {
              
              
              return cacheValue;
          } else {
              
              
              synchronized(lockKey) {
              
              
                  cacheValue = CacheHelper.get(cacheKey);
                  if (cacheValue != null) {
              
              
                      return cacheValue;
                  } else {
              
              
      	            //这里一般是sql查询数据
                      cacheValue = GetProductListFromDB(); 
                      CacheHelper.Add(cacheKey, cacheValue, cacheTime);
                  }
              }
              return cacheValue;
          }
      }
      
    3. 给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。
      伪代码:
      /* 解释说明:
      1、缓存标记:记录缓存数据是否过期,如果过期会触发通知另外的线程在后台去更新实际key的缓存;
      2、缓存数据:它的过期时间比缓存标记的时间延长1倍,例:标记缓存时间30分钟,数据缓存设置为60分钟。 
      这样,当缓存标记key过期后,实际缓存还能把旧数据返回给调用端,直到另外的线程在后台更新完成后,才会返回新缓存。
      
      关于缓存崩溃的解决方法,这里提出了三种方案:
      	使用锁或队列、设置过期标志更新缓存、
      	为key设置不同的缓存失效时间,
      	还有一各被称为“二级缓存”的解决方法,有兴趣的读者可以自行研究。
      */
      //伪代码
      public object GetProductListNew() {
              
              
          int cacheTime = 30;
          String cacheKey = "product_list";
          //缓存标记
          String cacheSign = cacheKey + "_sign";
      
          String sign = CacheHelper.Get(cacheSign);
          //获取缓存值
          String cacheValue = CacheHelper.Get(cacheKey);
          if (sign != null) {
              
              
              return cacheValue; //未过期,直接返回
          } else {
              
              
              CacheHelper.Add(cacheSign, "1", cacheTime);
              ThreadPool.QueueUserWorkItem((arg) -> {
              
              
      			//这里一般是 sql查询数据
                  cacheValue = GetProductListFromDB(); 
      	        //日期设缓存时间的2倍,用于脏读
      	        CacheHelper.Add(cacheKey, cacheValue, cacheTime * 2);                 
              });
              return cacheValue;
          }
      } 
      

缓存穿透

  1. 什么是缓存穿透?
    缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
  2. 解决方案
    1. 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
    2. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
    3. 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力

缓存击穿

  1. 什么是缓存击穿
    缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
  2. 解决方案
    1. 设置热点数据永远不过期。
    2. 加互斥锁

缓存预热

  1. 什么是缓存预热
    缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
  2. 解决方案
    1. 直接写个缓存刷新页面,上线时手工操作一下;
    2. 数据量不大,可以在项目启动的时候自动进行加载;
    3. 定时刷新缓存;

缓存降级

https://www.iteye.com/blog/1181731633-2370315

当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。

缓存降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。

在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:

  1. 一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
  2. 警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
  3. 错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
  4. 严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。

热点数据和冷数据

https://www.jianshu.com/p/053ba529bf02

缓存热点key

缓存中的一个Key(比如一个促销商品),在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

解决方案

对缓存查询加锁,如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询

猜你喜欢

转载自blog.csdn.net/weixin_44533129/article/details/112734436