想让面试官对你刮目相看?快来看看缓存击穿与缓存雪崩

144 阅读5分钟

缓存击穿

缓存击穿问题又叫热点KEY问题,就是一个被高并发访问并且重建业务较为复杂的key突然失效了,无数的请求访问在瞬间给数据库带来巨大的冲击。

有同学可能不能理解为什么会有高并发访问,重建业务较为复杂又为什么会影响呢?

高并发访问: 打个比方,一个限卖服务,一个商品很多人访问,那么这个key是不是一直被访问呀?这就是高并发访问

重建业务复杂: 比如一个接口需要调很多表,根据表里面的不同字段综合出一个数据,这样的处理是很耗时间的,因此在这么一段长时间里面,我们还没来得及把这个数据放到缓存中,众多请求都打进来了。这就好比,你去撑伞,但是你撑的很慢,还没等你撑完伞,你的衣服就被打湿了。

image-20221022195707953

常见解决方案

1.采用互斥锁的方式

image-20221022201013375

我们来解释一下这张图。现在想象一个场景,现在很多个线程同时涌入一个接口了。其中一个线程很幸运。因为缓存中查不到,它获得了互斥锁,由它进行数据库的读,redis的写。此时其他线程同样读取不到redis数据,但是唯一的锁已经被拿走了。因此他们只能不停的休眠重试去拿锁。休眠是为了等待那个幸运儿执行完他的存redis的任务。之后缓存命中。

没错这种方式极大保护了数据库,它只被访问了一次。但有个问题就是,一个线程执行操作的时候,其他线程只能不停的等。

2.逻辑过期

其实缓存击穿的本质就是key过期了嘛。那么反过来讲,只要我们不设置过期时间,是不是就不会有这个问题了呢?

这个时候会有同学问了,不设置过期时间,redis撑得住吗?不会被用满嘛

放心,虽然程序不设置过期时间,但是我们会设置逻辑过期。比如说等一个活动过了,我们自己给它删了就行。

那么什么是逻辑过期呢?

{
    name:"nika",
    age:18,
    expire:1231312
}

不过是把它写在了数据里面

同学们看下面这张图

image-20221022202023900

看着这张图,同学们可能会有些疑惑。

唉?这不是还是在争锁吗?没抢到锁的线程不是还是在等待吗?

让我细细为你解读。

第一个线程还是那个幸运儿,他发现逻辑时间过期了,他抢到了锁,但是他这次不去执行写redis的操作了,它让另一个线程去完成,自己仍然返回已经逻辑过期的数据。

第三个线程发现查询缓存过期了,它就知道了,别人在帮自己完成redis的写操作,那我就不去做了,直接返回旧的,已经逻辑过期的数据

其实线程四才是最终赢家,它看到了没有过期的数据,那我还用忙啥呢?所以有的时候真的是来的早不如来的巧!!

同学们,在我们解读的时候你会发现,有的线程返回的是老的,已经逻辑过期的数据。它不能保证数据的一致性!

确实,这也是这个方法的一个漏洞。

**没有十全十美的方法!!

解决缓存击穿的方法总结

互斥锁

优点

  • 没有额外的内存消耗
  • 保证一致性
  • 实现简单

缺点

  • 可能有死锁的风险。比如说你一个服务拿到了锁,去调另一个服务的时候,它也上了锁。你在等待,然而另一个服务也调用了你的服务,它也在等待你。这样互相等待,你让别的线程怎么办?是不是就形成了死锁??
  • 线程需要等待,性能收到影响

逻辑过期

优点

  • 线程无需等待

缺点

  • 不保证一致性
  • 实现复杂
  • 有额外内存消耗

注意:这两个方法一个看重一致性,一个看重可用性,需要根据场景实际去选择

缓存雪崩

缓存雪崩指的是同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大的压力。

比如说我们为了让某个接口能更快的访问,将所有数据放在了redis里面,做了一些缓存预热。然后这个时候,你的过期时间是固定的,因此key是会同时失效的

或者你的redis因为某些原因,直接死了,宕机了。你的数据库就像是在暴露在紫外线外面一样暴晒,数据库很容易挂掉,几乎等于裸奔。

请看下面图解

image-20221022193057912

解决方案

  • 缓存预热的时候给不同的Key利用随机数添加随机值,这样就不会同时失效了

  • 利用Redis集群提高服务的可用性。平摊每个redis的压力

  • 给缓存业务添加降级限流策略

  • 给业务添加多级缓存。比如给浏览器层,java应用层,多加点缓存,Nginx缓存等

    这样的话,就相当于你披了5层防弹衣。也相当于分担了缓存压力。