1. 缓存雪崩

在短时间内本应交由 Redis 处理的大量请求,都发送到了数据库进行处理,从而导致对数据库的压力迅速增大,严重时数据库可能崩溃,从而导致整个系统崩溃,就像雪崩一样,引发连锁效应,所以叫缓存雪崩。

出现上述情况的常见原因主要有以下两点:

  • 大量缓存数据同时过期,导致本应请求到缓存的需重新从数据库中获取数据。
  • redis 本身出现故障,无法处理请求,那自然会再请求到数据库那里。

针对大量缓存数据同时过期的情况:

  • 实际设置过期时间时,应当尽量避免大量 key 同时过期的场景,如果真的有,那就通过随机、微调、均匀设置等方式设置过期时间,从而避免同一时间过期。
  • 添加互斥锁,使得构建缓存的操作不会在同一时间进行。
  • 双 key 策略,主 key 是原始缓存,备 key 为拷贝缓存,主 key 失效时,可以访问备 key,主 key 缓存失效时间设置为短期,备 key 设置为长期。
  • 后台更新缓存策略,采用定时任务或者消息队列的方式进行 redis 缓存更新或移除等。

针对 Redis 本身出现故障的情况:

  • 在预防层面,可以通过主从节点的方式构建高可用的集群,也就是实现主 Redis 实例挂掉后,能有其他从库快速切换为主库,继续提供服务。
  • 如果事情已经发生了,那就要为了防止数据库被大量的请求搞崩溃,可以采用服务熔断或者请求限流的方法。当然服务熔断相对粗暴一些,停止服务直到 redis 服务恢复,请求限流相对温和一些,保证一些请求可以处理,不是一刀切,不过还是看具体业务情况选择合适的处理方案。

2. 缓存击穿

缓存击穿一般出现在高并发系统中,是大量用户同时并发请求缓存中没有但数据库中有的数据,也就是同时读 Redis 缓存,但缓存中没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大。

和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

出现这种情况一般因为某个热点数据缓存过期,由于是热点数据,请求并发量大,所以过期的时候还是会有大量请求同时过来,来不及更新缓存就全部打到数据库了。

针对这种情况有两种常见的处理方案:

  • 简单粗暴的对热点数据不设置过期时间,这样不会过期,自然也就不会出现上述情况了,如果后续想清理,可以通过后台进行清理。
  • 添加互斥锁,即当过期之后,除了请求过来的第一个查询的请求可以获取到锁请求到数据库,并再次更新到缓存中,其他的会被阻塞住,直到锁被释放,同时新的缓存也被更新上去了,后续请求又会请求到缓存上,这样就不会出现缓存击穿了。

3. 缓存穿透

缓存穿透是指数据既不在 Redis 中,也不在数据库中,这样就导致每次请求过来的时候,在缓存中找不到对应 key 之后,每次都还要去数据库再查询一遍,发现数据库也没有,相当于进行了两次无用的查询。

这样请求就可以绕过缓存直接查数据库,如果这个时候有人想恶意攻击系统,就可以故意使用空值或者其他不存在的值进行频繁请求,那么就会对数据库造成比较大的压力。

针对缓存穿透,一般有以下三种处理方案:

  • 非法请求的限制,主要是指参数校验、鉴权校验等,从而一开始就把大量的非法请求拦截在外,这在实际业务开发中是必要的手段。

  • 缓存空值或者默认值,如果从缓存取不到的数据,在数据库中也没有取到,那我们仍然把这个空结果进行缓存,同时设置一个较短的过期时间。通过这个设置的默认值存放到缓存,这样第二次到缓存中获取就有值了,而不会继续访问数据库,可以防止有大量恶意请求是反复用同一个 key 进行攻击。

  • 使用布隆过滤器快速判断数据是否存在。

    那什么是布隆过滤器呢,简单来说,就是可以引入了多个相互独立的哈希函数,保证在给定的空间和误判率下,完成元素判重。因为我们知道,存在 hash 碰撞这样一种情况,那如果只使用一个 hash 函数,则碰撞冲突的概率明显会变大,那为了减少这种冲突,我们可以多引入几个 hash 函数,而布隆过滤器算法的核心思想就是利用多个不同的 hash 函数来解决这样一种冲突。它的优点是空间效率高,查询时间短,远超其他算法,而它的缺点就是会存在一定的误识别率,它不能完全保证请求过来的 key 通过布隆过滤器的校验就一定有这个数据,毕竟理论上还是会存在冲突情况,无论概率多小。但是,只要没有通过布隆过滤器的校验,那么这个 key 就一定不存在,只要利用这一点其实就已经可以过滤掉大部分不存在的 key 的请求了,在正常场景下已然足够了。

4. 缓存预热

缓存预热就是系统上线前后,将相关的缓存数据直接加载到缓存系统中去,而不依赖用户触发请求时才加入。这样就可以避免在用户请求的时候,先查询数据库再将数据缓存的问题。用户直接查询事先被预热的缓存数据,这样可以避免那么系统上线初期,对于高并发的流量,都会访问到数据库中,对数据库造成流量的压力。

根据数据不同量级,可以有以下几种做法:

  • 数据量不大:项目启动的时候自动进行加载。
  • 数据量较大:后台定时刷新缓存。
  • 数据量极大:只针对热点数据进行预加载缓存操作。

5. 缓存降级

当缓存失效或缓存服务出现故障时,我们为了防止数据库发生雪崩而不去访问数据库,但此时仍然想要保证服务的主体功能是基本可用的。因此对于不重要的缓存数据,我们可以采取服务降级策略。

一般做法有以下两种:

  • 直接访问内存部分的数据缓存。
  • 直接返回系统设置的默认值。