缓存穿透,缓存击穿,缓存雪崩

缓存穿透

原因

查询一个数据库不存在的数据,既然数据库不存在,那么缓存自然也不会存在,因此所有这种垃圾查询都会打到数据库上,给数据库造成负担。

解决方法

  • 布隆过滤器
  • 缓存空对象

布隆过滤器

相信不少同学面试时被问过场景设计题:如果目前我们有个营销垃圾邮箱的汇总表,我们希望设计一个高效的拦截过滤器,怎么设计呢?

大家肯定脱口而出:hashmap/hashset。

对,这个思路一点没错,如果我们的邮箱汇总表不大,当然可以这么干。但如果汇总表稍微大一点点比如上到10亿,那就不能这么干了。一般来说面试官会回答:这么做占用空间太大,服务器内存撑不住,可以换一个思路,我们的这个业务可以允许一定的误报。

既然面试官都给提示了,那咱就往挤压内存占用的思路去考虑,既然又希望减少空间,又能接受误报,可以考虑这个思路。

首先我们先设置一个空bit数组,初始全0:
在这里插入图片描述

设计k个hash映射函数,这k个映射函数各不相同,然后开启一个for循环,把在汇总表中的邮箱通过这个映射函数得到数组指定位置,并置为1。比如这样:
在这里插入图片描述
然后这样
在这里插入图片描述
下次用户有想查询的邮箱,可以通过这一系列hash函数,判断对应位置是否全为1。如果是,则很大概率就是垃圾邮箱了。为啥说是很大概率,因为完全不同的字段,在一个hash函数中,映射也会相同的。

那如何减少误判概率?

  • 增加hash函数个数
  • 增加这个数组长度

最后提一下,这个数组可以就认为是布隆过滤器。布隆过滤器详情

缓存空对象
这个就很好理解了,一般查询的逻辑,是这样的:

  1. 用户发送查询请求
  2. 缓存层收到请求,在缓存中查找,如果有则返回,没有则走步骤3
  3. 数据库收到查询请求,如果存在就返回结果,并放到缓存,不存在直接返回

举例来说,我们用了个员工id和其对应的工资表。如果员工有100个人,那我们id肯定是1-100排列。如果此时有个请求,想查询id=1000的,那肯定啥也查不到,会被直接返回。

而缓存空对象,做的就是把这个值也缓存下来,即在缓存层中,添加一个id为1000,值为null的键值对。下次有对id为1000的请求,查询直接打到缓存上,减少了数据库压力。
但这种操作会增加内存开销,所以如果采用这种方法,一般空对象缓存的过期时间极短

缓存击穿

原因

我们知道,缓存层会设置一个数据过期时间,防止数据过多时,内存增大,撑爆服务器。
因此,当某一时刻,一个热点数据过期时,此时若有大量查询该数据的请求时,由于数据过期,缓存中没有,即“击穿了缓存”,所有的请求都会打到数据库,造成数据库负担。

解决方法

  1. 从Mysql角度出发

咱站在MySQL的角度,多心疼一下它。不就是大流量我MySQL处理不了嘛,那我直接把流量想个招,变成原来的1/100-1/10,那MySQL肯定轻轻松松可以处理。

那咋搞呢?加锁,线程读数据,之前100个可以同时读取数据,现在加锁,线程读数据,先看看数据有没有上锁,上锁了,等着,没上锁,获取锁后,再给数据库请求。数据库压力肯定瞬间下来了。

在这里插入图片描述
但有的读者肯定会问:这样子,用户体验是不是就会卡顿。这是必然的,牺牲用户体验,保证数据库不死机,这就是这个方式的设计出发点。

  1. 从Redis角度出发

咱站在Redis角度想想。为啥出现目前问题,还不是因为Redis缓存数据过期没了。那如果对Redis动刀,怎么解决?那肯定是修改Redis缓存的数据过期策略了。最常见的,也是大家八股文背的最多的,就是设置热点数据永不过期。

但这个方法,咋说呢,简单粗暴,很多情况下,我们无法预知热点数据。比如换做半年前,鸿星尔克出圈前,电商平台设计者在初始化redis数据库的时候,也不可能直接把它归为热点数据。

那咋搞呢?还是从数据过期策略出发,咱可以搞个这样的逻辑嘛。

如果发现这个数据快过期,并且好像最近这个数据访问特别多,后台新启一个异步线程,重新在缓存层中添加这个数据的缓存,给这个数据再续一续命。

缓存雪崩

原因

在某一时刻,一大批缓存同时失效,此时若有大量对于这一批数据的请求进来,所有的请求都会打到数据库上,造成数据库崩溃。

缓存雪崩这个名词非常形象,雪崩这两个词完美的比喻了当前的情况。之前提到的缓存击穿,是单点情况,雪崩就是多点情况。

解决方法

  1. 从Mysql的角度出发

无论是对很多键的并发查询(缓存雪崩),还是一次性对同一个键的很多次并发查询(缓存击穿),MySQL的解决方法都是简单粗暴:强行减少请求量。

和击穿一样,加锁。线程读数据,先看看数据有没有上锁,上锁了,等着,没上锁,获取锁后,再给数据库请求。数据库压力肯定瞬间下来了。
在这里插入图片描述
这个解决方法带来的问题也很好回答:用户体验是不是就会卡顿。这是必然的,牺牲用户体验,保证数据库不死机,这就是这个方式的设计出发点。

但相对缓存击穿的情形,对于缓存雪崩采取这种策略,效果并不会这么给力。原因也很简单,此时很多客户端的访问请求,不单单是基于一个键,并发量的减少不会像缓存击穿这么明显。

  1. 从Redis角度出发
  • 设置热点数据永不过期

  • 设置随机过期时间,避免缓存在同一时间大批失效。换句话,就是让缓存失效时间均匀一点。

如果我们一开始就没缓存怎么办?

在项目发布伊始,数据库中存满了数据,缓存却是空的。如果此时系统上线,请求又多,那就是妥妥的雪崩。甚至可以说,雪崩是这种情况下的必然事件。

对于这种情况下怎么处理呢?

这里得引入一个名词:灰度发布。上了班的老铁们应该不陌生,但由于可能有读者朋友还在上学,我们普及一下:

灰度发布

首先灰这个词是怎么来的呢?当然是由黑和白来的,互联网一般会把新系统完全上线或者新系统没上线定义为黑和白。所以新产品在处于没完全上线,就被定义为灰了。我们经常听说的A/B测试就是一种灰度发布方式,即一部分用户继续用老系统A,另一部分用户用新系统B,如果用户对新系统意见不大,甚至反馈比A好,那就逐步扩大范围,慢慢把所有用户都迁移到B上来。

聊完灰度发布,相信大家瞬间对刚刚问题有了答案。如果采用灰度发布方式,进行缓存预热,一开始流量小,数据库hold住,缓存也可以多出很多数据,渐渐增加流量,缓存也不为空,就轻轻松松解决这个问题了。

缓存雪崩处理原则

了解了这么多,来看个面试题吧。

面试官:如果某个查询逻辑确实复杂,数据库回应很慢,redis又对这条查询没缓存的情况,这种情况引起雪崩,我们怎么处理?

此时大家一定牢记一条原则:

结合业务需求具体分析!

结合业务需求具体分析!

结合业务需求具体分析!

来解释一下面试题:用户A发了个非常复杂的查询请求,我们后端数据库收到查询后,开始执行查询,由于查询可能涉及多表联查,花的时间不少,还在查询过程中, 此时用户B,C,D,E,F…发了同样复杂的请求过来,由于缓存此时为空,请求毫无疑问,就打到了数据库上,要是服务器性能差点,数据库就雪崩了。此时咱们外层看着好像Mysql收到的请求好像也不多啊…

遇到这种情况,一定具体分析业务大体需求了。

  • 如果注重时效性,那就得一开始就对这些复杂查询做做缓存。
  • 不注重,那就上锁,加锁,超时的请求返回错误信息等等,减少数据库压力。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值