缓存击穿
缓存击穿问题又叫热点KEY问题,就是一个被高并发访问并且重建业务较为复杂的key突然失效了,无数的请求访问在瞬间给数据库带来巨大的冲击。
有同学可能不能理解为什么会有高并发访问,重建业务较为复杂又为什么会影响呢?
高并发访问: 打个比方,一个限卖服务,一个商品很多人访问,那么这个key是不是一直被访问呀?这就是高并发访问
重建业务复杂: 比如一个接口需要调很多表,根据表里面的不同字段综合出一个数据,这样的处理是很耗时间的,因此在这么一段长时间里面,我们还没来得及把这个数据放到缓存中,众多请求都打进来了。这就好比,你去撑伞,但是你撑的很慢,还没等你撑完伞,你的衣服就被打湿了。
常见解决方案
1.采用互斥锁的方式
我们来解释一下这张图。现在想象一个场景,现在很多个线程同时涌入一个接口了。其中一个线程很幸运。因为缓存中查不到,它获得了互斥锁,由它进行数据库的读,redis的写。此时其他线程同样读取不到redis数据,但是唯一的锁已经被拿走了。因此他们只能不停的休眠重试去拿锁。休眠是为了等待那个幸运儿执行完他的存redis的任务。之后缓存命中。
没错这种方式极大保护了数据库,它只被访问了一次。但有个问题就是,一个线程执行操作的时候,其他线程只能不停的等。
2.逻辑过期
其实缓存击穿的本质就是key过期了嘛。那么反过来讲,只要我们不设置过期时间,是不是就不会有这个问题了呢?
这个时候会有同学问了,不设置过期时间,redis撑得住吗?不会被用满嘛
放心,虽然程序不设置过期时间,但是我们会设置逻辑过期。比如说等一个活动过了,我们自己给它删了就行。
那么什么是逻辑过期呢?
{
name:"nika",
age:18,
expire:1231312
}
不过是把它写在了数据里面
同学们看下面这张图
看着这张图,同学们可能会有些疑惑。
唉?这不是还是在争锁吗?没抢到锁的线程不是还是在等待吗?
让我细细为你解读。
第一个线程还是那个幸运儿,他发现逻辑时间过期了,他抢到了锁,但是他这次不去执行写redis的操作了,它让另一个线程去完成,自己仍然返回已经逻辑过期的数据。
第三个线程发现查询缓存过期了,它就知道了,别人在帮自己完成redis的写操作,那我就不去做了,直接返回旧的,已经逻辑过期的数据
其实线程四才是最终赢家,它看到了没有过期的数据,那我还用忙啥呢?所以有的时候真的是来的早不如来的巧!!
同学们,在我们解读的时候你会发现,有的线程返回的是老的,已经逻辑过期的数据。它不能保证数据的一致性!
确实,这也是这个方法的一个漏洞。
**没有十全十美的方法!!
解决缓存击穿的方法总结
互斥锁
优点
- 没有额外的内存消耗
- 保证一致性
- 实现简单
缺点
- 可能有死锁的风险。比如说你一个服务拿到了锁,去调另一个服务的时候,它也上了锁。你在等待,然而另一个服务也调用了你的服务,它也在等待你。这样互相等待,你让别的线程怎么办?是不是就形成了死锁??
- 线程需要等待,性能收到影响
逻辑过期
优点
- 线程无需等待
缺点
- 不保证一致性
- 实现复杂
- 有额外内存消耗
注意:这两个方法一个看重一致性,一个看重可用性,需要根据场景实际去选择
缓存雪崩
缓存雪崩指的是同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大的压力。
比如说我们为了让某个接口能更快的访问,将所有数据放在了redis里面,做了一些缓存预热。然后这个时候,你的过期时间是固定的,因此key是会同时失效的。
或者你的redis因为某些原因,直接死了,宕机了。你的数据库就像是在暴露在紫外线外面一样暴晒,数据库很容易挂掉,几乎等于裸奔。
请看下面图解
解决方案
-
缓存预热的时候给不同的Key利用随机数添加随机值,这样就不会同时失效了
-
利用Redis集群提高服务的可用性。平摊每个redis的压力
-
给缓存业务添加降级限流策略
-
给业务添加多级缓存。比如给浏览器层,java应用层,多加点缓存,Nginx缓存等
这样的话,就相当于你披了5层防弹衣。也相当于分担了缓存压力。