redis缓存穿透、缓存雪崩、热点Key问题分析及解决方案

咱们一般使用 缓存 + 过时时间的策略来帮助咱们加速接口的访问速度,减小了后端负载,同时保证功能的更新。redis

缓存穿透

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

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

因为缓存不命中,每次都要查询持久层(回源),从而失去缓存的意义。后端

解决方法

  1. 缓存层缓存空值
    • 缓存太多空值,占用更多空间。(优化:给个空值过时时间)
    • 存储层更新代码了,缓存层仍是空值。(优化:后台设置时主动删除空值,并缓存把值进去)
  2. 将数据库中全部的查询条件,放到 布隆过滤器 中。当一个查询请求来临的时候,先通过布隆过滤器进行检查,若是请求存在这个条件中,那么继续执行,若是不在,直接丢弃。

注意事项:数组

​ 使用布隆过滤器时,若是数据库中有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

  • 这个key是一个热点key(例如一个重要的新闻,一个热门的八卦新闻等等),因此这种key访问量可能很是大。
  • 缓存的构建是须要必定时间的。(多是一个复杂计算,例如复杂的sql、屡次IO、多个依赖(各类接口)等等)

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

解决办法

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

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

  3. "永远不过时":

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

    • 从redis上看,确实没有设置过时时间,这就保证了,不会出现热点key过时问题,也就是“物理”不过时;
    • 从功能上看,若是不过时,那不就成静态的了吗?因此咱们把过时时间存在key对应的value里,若是发现要过时了,经过一个后台的异步线程进行缓存的构建,也就是“逻辑”过时
  4. 资源保护:能够作资源的隔离保护主线程池,若是把这个应用到缓存的构建也何尝不可。

四种方案对比

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

  • 加快用户访问速度,提升用户体验。
  • 下降后端负载,保证系统平稳。
  • 保证数据“尽量”及时更新(要不要彻底一致,取决于业务,而不是技术。)

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

解决方案 优势 缺点
简单分布式锁(Tim yang) 1. 思路简单<br />2. 保证一致性 1. 代码复杂度增大<br />2. 存在死锁的风险<br />3. 存在线程池阻塞的风险
加另一个过时时间(Tim yang) 1. 保证一致性 同上
不过时(本文) 1. 异步构建缓存,不会阻塞线程池 1. 不保证一致性。<br />2. 代码复杂度增大(每一个value都要维护一个timekey)。<br />3. 占用必定的内存空间(每一个value都要维护一个timekey)。
资源隔离组件hystrix(本文) 1. hystrix技术成熟,有效保证后端。<br />2. hystrix监控强大。 1. 部分访问存在降级策略。

总结

  • 热点key + 过时时间 + 复杂的构建缓存过程 => mutex key问题
  • 构建缓存一个线程作就能够了。
  • 四种解决方案:没有最佳只有最合适。

参考文献:

相关文章
相关标签/搜索