Redis缓存雪崩、缓存穿透、热点Key解决方案和分析

缓存穿透

缓存系统,按照KEY去查询VALUE,当KEY对应的VALUE必定不存在的时候并对KEY并发请求量很大的时候,就会对后端形成很大的压力。redis

(查询一个必然不存在的数据。好比文章表,查询一个不存在的id,每次都会访问DB,若是有人恶意破坏,极可能直接对DB形成影响。)sql

因为缓存不命中,每次都要查询持久层。从而失去缓存的意义。数据库

 

解决方法:后端

一、缓存层缓存空值。 
–缓存太多空值,占用更多空间。(优化:给个空值过时时间) 
–存储层更新代码了,缓存层仍是空值。(优化:后台设置时主动删除空值,并缓存把值进去)数组

二、将数据库中全部的查询条件,放到布隆过滤器中。当一个查询请求来临的时候,先通过布隆过滤器进行检查,若是请求存在这个条件中,那么继续执行,若是不在,直接丢弃。缓存

 

备注:并发

    好比数据库中有10000个条件,那么布隆过滤器的容量size设置的要稍微比10000大一些,好比12000.异步

    对于误判率的设置,根据实际项目,以及硬件设施来具体决定。可是必定不能设置为0,而且误判率设置的越小,哈希函数跟数组长度都会更多跟更长,那么对硬件,内存中间的要求就会相应的高。分布式

  private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, 0.0001); 函数

    有了size跟误判率,那么布隆过滤器就会产生相应的哈希函数跟数组。

    综上:咱们能够利用布隆过滤器,将redis缓存击穿控制在一个可容忍的范围内。

 


缓存雪崩(缓存失效)

        若是缓存集中在一段时间内失效,发生大量的缓存穿透,全部的查询都落在数据库上,形成了缓存雪崩。

        缓存层宕掉后,流量会像奔逃的野牛同样,打向后端存储

    解决方法:

  1. 在缓存失效后,经过加锁或者队列来控制读数据库写缓存的线程数量。好比对某个key只容许一个线程查询数据和写缓存,其余线程等待。
  2. 能够经过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存
  3. 不一样的key,设置不一样的过时时间,让缓存失效的时间点尽可能均匀
  4. 作二级缓存,或者双缓存策略。A1为原始缓存,A2为拷贝缓存,A1失效时,能够访问A2,A1缓存失效时间设置为短时间,A2设置为长期。

 

 

热点key

      (1) 这个key是一个热点key(例如一个重要的新闻,一个热门的八卦新闻等等),因此这种key访问量可能很是大。

      (2) 缓存的构建是须要必定时间的。(多是一个复杂计算,例如复杂的sql、屡次IO、多个依赖(各类接口)等等)

       因而就会出现一个致命问题:在缓存失效的瞬间,有大量线程来构建缓存(见下图),形成后端负载加大,甚至可能会让系统崩溃 。

    解决方法:

1. 使用互斥锁(mutex key):这种解决方案思路比较简单,就是只让一个线程构建缓存,其余线程等待构建缓存的线程执行完,从新从缓存获取数据就能够了

2. "提早"使用互斥锁(mutex key):在value内部设置1个超时值(timeout1), timeout1比实际的memcache timeout(timeout2)小。当从cache读取到timeout1发现它已通过期时候,立刻延长timeout1并从新设置到cache。而后再从数据库加载数据并设置到cache中。

3. "永远不过时":

 这里的“永远不过时”包含两层意思:

    (1) 从redis上看,确实没有设置过时时间,这就保证了,不会出现热点key过时问题,也就是“物理”不过时。

    (2) 从功能上看,若是不过时,那不就成静态的了吗?因此咱们把过时时间存在key对应的value里,若是发现要过时了,经过一个后台的异步线程进行缓存的构建,也就是“逻辑”过时

4. 资源保护:能够作资源的隔离保护主线程池,若是把这个应用到缓存的构建也何尝不可。

四种方案对比:

      做为一个并发量较大的互联网应用,咱们的目标有3个:

      1. 加快用户访问速度,提升用户体验。

      2. 下降后端负载,保证系统平稳。

      3. 保证数据“尽量”及时更新(要不要彻底一致,取决于业务,而不是技术。)

      因此第二节中提到的四种方法,能够作以下比较,仍是那就话:没有最好,只有最合适。 

解决方案 优势 缺点
简单分布式锁(Tim yang)

 1. 思路简单

2. 保证一致性

1. 代码复杂度增大

2. 存在死锁的风险

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

加另一个过时时间(Tim yang)  1. 保证一致性 同上 
不过时(本文)

1. 异步构建缓存,不会阻塞线程池

1. 不保证一致性。

2. 代码复杂度增大(每一个value都要维护一个timekey)。

3. 占用必定的内存空间(每一个value都要维护一个timekey)。

资源隔离组件hystrix(本文)

1. hystrix技术成熟,有效保证后端。

2. hystrix监控强大。

 

 

1. 部分访问存在降级策略。 


总结

 

   1.  热点key + 过时时间 + 复杂的构建缓存过程 => mutex key问题

   2. 构建缓存一个线程作就能够了。

   3. 四种解决方案:没有最佳只有最合适。

相关文章
相关标签/搜索