Redis应用缓存穿透、缓存击穿、缓存雪崩问题的原因及解决方案

一、缓存穿透

1、原因

所谓缓存穿透,可以理解为请求DB中不存在的key对应的数据。

通常情况下,当请求redis中某个key时,如果redis中没有,会请求DB,若DB上有key对应的数据,那么就会将这条数据缓存到redis中。但是当DB上也没有key对应的数据时,出于容错考虑,不会回写DB中不存在的数据到redis中。如果请求DB中不存在的key的并发量很小的时候还好,如果并发量很大,那么大量的请求会全部怼到DB上,从而可能会压垮DB。

2、解决方案

  1. 对空值缓存:如果查询DB返回的结果为空(不管数据是否不存在),仍将这个空结果(null)进行缓存,并设置较短的过期时间,最长不超过5分钟。但这个方案有个问题,若有人恶意请求大量DB中不存在的数据,那么redis中将会缓存大量值为null的key,会占用大量内存,并且一些有用的缓存数据可能会被redis的内存淘汰策略淘汰掉。
  2. 设置白名单:提供一个能迅速判断请求是否有效的拦截机制。比如,使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问,请求的key和bitmap里面的id进行比较,如果该key不在bitmaps里面,进行拦截,不允许访问。可以把这种方式理解为二级缓存,bitmaps作为一级缓存,key-value作为二级缓存。这种方式也会减少redis的压力。
  3. 使用布隆过滤器:布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被 这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。
  4. 进行实时监控当发现redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务。

二、缓存击穿

1、原因

        所谓缓存击穿,可以理解为请求的某一个key不在redis中,但是DB中存在key对应的数据。(注意这里是某一个key

        通常我们在使用redis缓存数据的时候会给key设置过期时间,若某个key已过期,此时大量的并发请求访问该key,这些请求发现缓存过期一般都会从DB加载数据并回设到缓存,这些并发请求会造成DB压力过大甚至瞬间压垮DB。

2、解决方案

  1. 不设置过期时间:在业务允许的情况下,可以设置热点key永不过期。
  2. 实时调整:现场监控哪些数据热门,实时调整key的过期时长。
  3. 数据预热:在redis高峰访问之前,把一些热门数据提前存入到redis里面,把这些热门key的过期时间设置长一些。
  4. 使用分布式锁:当大量的并发请求访问redis的过期key时,这些并发请求会怼到DB上并回写redis。此时我们使用分布式锁(如redis的setnx操作),设置一个排他锁,只有获得到这个锁的线程才可以去DB上查询并回写redis,其他的请求等待一会再从redis中查询这个key,从而避免大量的并发请求DB。但是这种方式会降低系统的吞吐量,在高并发的场景下影响性能。注意,因为现在大部分项目是分布式的,即项目部署到多台服务器上,故java的锁不能满足这种情况。若是项目的单机部署的,也可以使用java锁。

三、缓存雪崩

1、原因

        所谓缓存雪崩,可以理解为请求的多个key不在redis中,但是DB中存在key对应的数据。(注意这里是多个key

        与缓存击穿类似, 通常我们在使用redis缓存数据的时候会给key设置过期时间,若同一时间大量缓存key大面积的失效,此时大量的并发请求访问这些key,这些请求发现缓存过期一般都会从DB加载数据并回设到缓存,这些并发请求会造成DB压力过大甚至瞬间压垮DB。与缓存击穿不同的是,缓存雪崩是大量的key过期,而缓存击穿是某一个key过期。此外,redis宕机,导致在同一时间大规模的key失效也会导致缓存雪崩。

        缓存失效时的雪崩效应对底层系统的冲剂是非常可怕的。

2、解决方案

  1. 构建redis集群:为防止redis宕机导致缓存雪崩,可以搭建redis集群,保障redis的高可用。
  2. 将缓存失效时间分散开:可以在原有过期时间的基础上增加一个随机值,比如1-5分钟随机。这样过期时间=基础过期时间+随机时间。这样每一个缓存的过期时间的重复率就会降低,避免大量key集体失效。
  3. 数据预热:在redis高峰访问之前,把一些热门数据提前存入到redis里面,设置不同的过期时间,让缓存失效的时间尽量不同。
  4. 异步更新策略设置过期标志,记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存。比如redis中有个key为A,过期时间为5min,我们给A设置过期标志为B,B的过期时间要比A的小,比如B的过期时间为4min,当B失效时去更新A,并将A的过期时间仍设为5min,B的过期时间仍设为4min。
  5. 使用分布式锁或队列:使用分布式锁或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到DB上。但是这方案吞吐量明显下降了,不适合高并发的场景。
  6. 构建多级缓存架构:nginx缓存 + redis缓存 +其他缓存(ehcache等)。这种方式会加大成本。
  7. 双缓存:我们有两个缓存,缓存A和缓存B。比如缓存A的失效时间为20min,缓存B不设失效时间。自己做缓存预热。然后细分以下几个小点:
          i)从缓存A读数据,有则直接返回;
          ii)缓存A中没有数据,直接从缓存B中读数据,直接返回,并且异步启动一个更新线程;
          iii)更新线程同时更新缓存A和缓存B。

四、总结

        缓存穿透就是没有命中redis,DB中也不存在;缓存击穿就是某个热点key失效;缓存雪崩就是大量key失效。我觉得我的好友凯少对redis的这三个问题比喻的很好:redis就像一堵墙,key就是一块块砖,雪崩就是整个墙都塌了或者部分塌了,击穿就是只有一块砖坏了,穿透就是请求可以遁入虚空,无视这堵墙。

你可能感兴趣的:(缓存,redis)