文章目录

  • 1. 缓存穿透
  • 1.1 带来的问题
  • 1.2 解决办法
  • 1.2.1 缓存空值
  • 1.2.2 BloomFilter
  • 1.2.3 如何选择
  • 2. 缓存击穿
  • 2.1 带来的问题
  • 2.2 解决办法
  • 3. 缓存雪崩
  • 3.1 解决办法
  • 3.1.1 事前:使用集群缓存,保证缓存服务的高可用
  • 3.1.2 事中:使用 ehcache 本地缓存 + Hystrix 限流&降级 ,避免 MySQL 被打死的情况发生
  • 3.1.3 事后:开启 Redis 持久化机制,尽快恢复缓存集群
  • 4. 热点数据集中失效
  • 4.1 解决办法
  • 4.1.1 设置不同的失效时间
  • 4.1.2 互斥锁


1. 缓存穿透

正常情况下,我们去查询数据都是存在。那么请求去查询一条压根数据库中根本就不存在的数据,也就是缓存和数据库都查询不到这条数据,但是请求每次都会打到数据库上面去。这种查询不存在数据的现象我们称为缓存穿透。

1.1 带来的问题

拿一个不存在的id去查询数据,会产生大量的请求到数据库去查询。可能会导致你的数据库由于压力过大而宕掉。

1.2 解决办法

1.2.1 缓存空值

将缓存中没有存储这些空数据的key值设置为null丢到缓存里去,后面再出现查询这个key的请求的时候,直接返回null,就不用在数据库查询了,注意要设置过期时间。

1.2.2 BloomFilter

BloomFilter 类似于一个hase set 用来判断某个元素(key)是否存在于某个集合中。
这种方案可以加在第一种方案中,在缓存之前在加一层 BloomFilter ,在查询的时候先去 BloomFilter 去查询 key 是否存在,如果不存在就直接返回,存在再走查 缓存 -> 查 DB。

1.2.3 如何选择

针对于一些恶意攻击,攻击带过来的大量key 是不存在的,那么我们采用第一种方案就会缓存大量不存在 key 的数据。此时我们采用第一种方案就不合适了,我们完全可以先对使用第二种方案进行过滤掉这些key。

  • 针对这种key 异常多,请求重复率比较低的数据,我们就没有必要进行缓存,使用第二种方案直接过滤掉。
  • 对于空数据的key 有限的,重复率比较高的,我们则可以采用第一种方式进行缓存。

2. 缓存击穿

缓存击穿是我们可能遇到的第二个使用缓存方案可能遇到的问题。指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。

2.1 带来的问题

会造成某一时刻数据库请求量过大,压力剧增。

2.2 解决办法

上面的现象是多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个 互斥锁来锁住它。其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。

3. 缓存雪崩

缓存的情况是说,当某一时刻发生大规模的缓存失效的情况。比如你的缓存服务宕机了,会有大量的请求进来直接打到DB上面。结果就是DB 称不住,挂掉。

3.1 解决办法

3.1.1 事前:使用集群缓存,保证缓存服务的高可用

这种方案就是在发生雪崩前对缓存集群实现高可用,如果是使用 Redis,可以使用 主从+哨兵 ,Redis Cluster 来避免 Redis 全盘崩溃的情况。

3.1.2 事中:使用 ehcache 本地缓存 + Hystrix 限流&降级 ,避免 MySQL 被打死的情况发生

使用 ehcache 本地缓存的目的也是考虑在 Redis Cluster 完全不可用的时候,ehcache 本地缓存还能够支撑一阵。

使用 Hystrix 进行 限流 & 降级 ,比如一秒来了5000个请求,我们可以设置假设只能有一秒 2000 个请求能通过这个组件,那么其他剩余的 3000 请求就会走限流逻辑,然后去调用我们自己开发的降级组件(降级)。比如设置的一些默认值呀之类的。以此来保护最后的 MySQL 不会被大量的请求给打死。

3.1.3 事后:开启 Redis 持久化机制,尽快恢复缓存集群

一旦重启,就能从磁盘上自动加载数据恢复内存中的数据。

hangfire redis存储 redis存储引擎_缓存

4. 热点数据集中失效

我们在设置缓存的时候,一般会给缓存设置一个失效时间,过了这个时间,缓存就失效了。对于一些热点的数据来说,当缓存失效以后会存在大量的请求过来,然后打到数据库去,从而可能导致数据库崩溃的情况。

4.1 解决办法

4.1.1 设置不同的失效时间

为了避免这些热点的数据集中失效,那么我们在设置缓存过期时间的时候,我们让他们失效的时间错开。比如在一个基础的时间上加上或者减去一个范围内的随机值。

4.1.2 互斥锁

结合上面的击穿的情况,在第一个请求去查询数据库的时候对他加一个互斥锁,其余的查询请求都会被阻塞住,直到锁被释放,从而保护数据库。但是也是由于它会阻塞其他的线程,此时系统吞吐量会下降。需要结合实际的业务去考虑是否要这么做。