若是第二次看到个人文章,欢迎点击文末连接扫码订阅我我的的公众号(跨界架构师)哟~ html
每周五11:45 按时送达。固然了,也会时不时加个餐~
程序员本文长度为2805字,建议阅读8分钟。数据库
坚持原创,每一篇都是用心之做~数组
有句话说得好,欲要使其毁灭,先要使其疯狂。当你沉浸在缓存所带来的系统tps飙升的喜悦中时,使你系统毁灭的种子也已经埋在其中。缓存
并且,你所承载的tps越高,它所带来的毁灭性更大。bash
在前两篇《360°全方位解读「缓存」》和《先写DB仍是「缓存」?》中,咱们已经对缓存有了必定的认识,而且知道了关于缓存相关的「一致性」问题的最佳实践。微信
此次,咱们就来聊聊隐藏在缓存中的毁灭性种子是什么?架构
咱们从前一篇文章《先写DB仍是「缓存」?》中屡次提到的「cache miss」提及。并发
在前一篇文章《先写DB仍是「缓存」?》中,咱们屡次提到了「cache miss」这个词,利用「cache miss」来更好的保障DB和缓存之间的数据一致性。dom
然而,任何事物都是有两面性的,「cache miss」在提供便利的同时,也带来了一个潜在风险。
这个风险就是「缓存雪崩」。
在图中的第二步,大量的请求并发进入,这里的一次「cache miss」就有可能致使产生「缓存雪崩」。
不过,虽然「cache miss」会产生「缓存雪崩」,但「缓存雪崩」并不只仅产生于「cache miss」。
雪崩一词源于「雪崩效应」,是指像「多米勒骨牌」这样的级联反应。前面没顶住,致使影响后面,如此蔓延。(关于对应雪崩的方式参考以前的文章,文末放连接)
因此「缓存雪崩」的根本问题是:缓存因为某些缘由未起到预期的缓冲效果,致使请求所有流转到数据库,形成数据库压力太重。
所以,流量激增、高并发下的缓存过时、甚至缓存系统宕机都有可能产生「缓存雪崩」问题。
怎么解决这个问题呢?宕机能够经过作高可用来解决(能够参考以前的文章,文末放连接)。而在“流量激增”、“高并发下的缓存过时”这两种场景下,也有两种方式能够来解决。
经过加锁或者排队机制来限制读数据库写缓存的线程数量。好比,下面的伪代码就是对某个key只容许一个线程进入的效果。
key = "aaa";
var cacheValue = cache.read(key);
if (cacheValue != null) {
return cacheValue;
}
else {
lock(key) {
cacheValue = cache.read(key);
if (cacheValue != null) {
return cacheValue;
}
else {
cacheValue = db.read(key);
cache.set(key,cacheValue);
}
}
return cacheValue;
} 复制代码
这个比较好理解,就不废话了。
这个主要针对的是「缓存定时过时」机制下的取巧方案。它的目的是避免多个缓存key在同一时间失效,致使压力更加集中。
好比,你有10个key,他们的过时时间都是30分钟的话,那么30分钟后这10个key的全部请求会同时流到db去。
而这里说的这种方式就是将这10个key的过时时间打乱,好比设置成2五、2六、2七、...、34分的过时时间,这样压力就被分散了,每分钟只有一个key过时。
最简单粗暴的方式就是在设置「过时时间」的时候加一个随机数字。
cache.set(key,cacheValue,30+random())复制代码
整体来看,相比后者,前者的适用面更广,因此Z哥建议你用「加锁排队」做为默认的通用方案不失为一个不错的选择。
若是你据说过「缓存穿透」的话,可能会问:「缓存雪崩」和「缓存穿透」同样吗?
从产生的效果上看是同样的,可是过程不一样。
来举个例子。例子纯属虚构,别太在乎合理性~
在一个方圆一万里的地区内,只有一个修手机的老师傅。他收了一个徒弟,但愿徒弟能帮他分担掉一部分的工做压力。这里的老师傅能够看做是DB,徒弟看做是缓存。
老师傅对徒弟说,若是遇到你不会作的事你来请教我。
而后,一个客户过来讲要修一下他的卫星电话,徒弟去请教老师傅,老师傅说他也不会,先拒绝了吧。
可是因为没告诉他后续遇到修卫星电话的人该怎么作,因此后续这个客户一直来问,徒弟每次都又去请教老师傅。最终,在修卫星电话这件事上,徒弟并无帮老师傅缓解任何的压力,快被烦死了。
上面这个故事就比如「缓存穿透」。
而「缓存雪崩」则是,因为徒弟年轻力壮,精力充沛,1小时能修20个手机,老师傅只能修10个(可是手艺好,更考究)。
而后,有一天徒弟请假了,但恰巧这天来了2000个修手机的,老师傅修不过来就被累垮了。
因此,「缓存穿透」和「缓存雪崩」最终产生的效果是同样的,就是由于大量请求流到DB后,把DB拖垮(正如前面故事中的老师傅)。
二者最大的不一样在于,「缓存雪崩」问题只要数据从db中找到并放入缓存就能恢复正常(徒弟休假归来),而「缓存穿透」指的是所需的数据在DB中一直不存在的状况(老师傅也不会修)。而且,因为DB中数据不存在,因此天然每次从缓存中也找不到(徒弟也不会修)。
清楚了二者的区别以后,咱们下面就来聊聊「缓存穿透」的常见应对方式。
「缓存穿透」有时也叫作「缓存击穿」,产生的逻辑过程是这样,一直在虚线范围内流转。
在这种场景下,缓存的做用彻底失效,每次请求都“穿透”到了DB中。
可能你会想,为何会存在大量的这种db中数据不存在的状况呢?其实,任何依赖外部参数进行查询的地方均可能有这个问题的存在。好比,一个文本输入框,原本是让你输入用户名的,可是手误输入了密码,天然就找不到数据咯。更主要的问题是,会有恶意分子利用这种机制来对你的系统进行攻击,击穿缓存搞垮你的数据库,致使整个系统全面瘫痪。
一样也有两种方式来解决这个问题。
布隆过滤器就是由一个很长的二进制向量和一系列随机映射函数组成,将肯定不存在的数据构建到过滤器中,用它来过滤请求。这里就放个图,具体就不展开了,后续咱们再聊(有兴趣的能够先到搜索引擎搜《Space time trade-offs in hash coding with allowable errors》找到bloom的原始论文)。
实现代码其实并不很复杂,参考论文或者网上其余做者的一些实现就能够写出来。
不过,布隆过滤器有一个最大的缺点,也是其为了高效利用内存而付出的代价,就是没法确保100%的准确率。
因此,若是你的场景要求是100%准确的,就只能用下面这种方式了。
其实就是哪怕从db中取出的数据是“空(null)”,也把它丢失到缓存中。
这样一来,虽然缓存中存在着一个value为空的数据,可是至少他能表示“数据库里也没有不用找了”。
其实这个思路和布隆过滤器有些相似,可是它对内存的消耗会大不少,毕竟布隆过滤器是利用的bit位来存储。不过这种方式的优点是前面提到的,不会出现偏差,而布隆过滤器的错误率会随着「位数」的增长而减小,会不断趋近于0,但不会为0。
好了,咱们一块儿总结一下。
此次呢,Z哥主要和你聊了隐藏在缓存中的两颗具备“毁灭性”的种子,「缓存雪崩」和「缓存穿透」,以及应对这两颗种子的经常使用方式。
并且,顺便帮你区分清楚了「缓存雪崩」和「缓存穿透」的差别。
但愿对你有所启发。
相关文章:
做者:Zachary
出处:www.cnblogs.com/Zachary-Fan…
若是你喜欢这篇文章,能够点一下左侧的「大拇指」哦~。
这样能够给我一点反馈。: )
谢谢你的举手之劳。
▶关于做者:张帆(Zachary,我的微信号:Zachary-ZF)。坚持用心打磨每一篇高质量原创。本文首发于公众号:「跨界架构师」(ID:Zachary_ZF)。<-- 点击后阅读热门文章
按期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些思考。
若是你是初级程序员,想提高但不知道如何下手。又或者作程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注个人公众号「跨界架构师」,回复「技术」,送你一份我长期收集和整理的思惟导图。
若是你是运营,面对不断变化的市场一筹莫展。又或者想了解主流的运营策略,以丰富本身的“仓库”。欢迎关注个人公众号「跨界架构师」,回复「运营」,送你一份我长期收集和整理的思惟导图。