ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

【闲聊杂谈】捋一捋Redis中的击穿、穿透、雪崩以及分布式锁

2021-10-02 21:02:45  阅读:155  来源: 互联网

标签:请求 过期 Redis 数据库 杂谈 一捋 击穿 数据


0、Redis作为缓存

首先我们有这么一个前提,击穿、穿透以及雪崩,这三个场景都是在Redis作为缓存的情况下所发生的。也就是说Redis后面还会有一个关系型物理数据库,Redis本身并不作为唯一的数据库来使用。

1、Redis中的击穿

Redis作为缓存的话,必然会有两件事情:

① Key会设置过期时间;

② 通过LRU算法或者LFU算法删除不需要的数据。

那么就会存在这种可能性,当数据在Redis中存在的时候没有访问,但是刚刚被删除以后立马就被访问了。而此时在Redis中这个数据已经被删除了,那么请求过来的时候,Redis中查不到这条数据,就会穿过Redis去请求它后面的数据库,从数据库中去查询这条数据。这就相当于在Redis身上打了一个窟窿穿透过去了,这就是Redis的击穿,一种非常形象的说法。当然这个前置条件也是在高并发的情况下,如果不是大量并发同一时间来访问的话,就那么两三个访问,那查库和查Redis并没有什么本质上的区别。所以对于击穿来说无外乎就是三个点,在高并发的情况下,曾经有你不要,现在没有你想要。

那么这个解决方案是什么?首先我们要知道延长它的过期时间,并不是一个可行的解决方案:本身Redis是作为一个缓存就肯定会受到内存大小的限制,Redis能储存的数据是有限的,所以不可能在Redis中将数据储存过久的时间;而且即使Key的有效时间设置的再长,总有过期的时候。

其实解题思路并不难,我们现在的已知条件就是:并发有了,我们要阻止这个并发到达数据库,但是Redis里又没有数据,那要怎么阻止呢?我们可以给这些高并发的请求增加一个逻辑:

①当发现Redis中没有数据以后,执行setnx(),这就相当于给Redis中设置了一把锁;

② 由于Redis是单进程单实例,所有的并发在请求Redis的时候都是排着队一个一个来,那么肯定   就只有第1个请求能抢到这把锁;

③ 只有获得这一把锁的请求,才能去访问数据库;

⑤ 访问数据库的请求,将数据库中的查询结果重新设置到Redis中,释放锁;

⑥ 没有获得锁的请求,sleep一段时间,sleep结束之后,返回①继续执行。

但是这种解决方案存在的一个问题就是:如果第1个请求挂了,它获得的锁就永远不会释放,其余的请求就一直处于等待状态,造成死锁。

这个问题也不难解决,可以设置锁的过期时间。假设第1个请求在获得锁以后,设置这把锁的过期时间为1秒钟,那么即便这个请求挂掉以后,1秒钟之后,这把锁仍然会释放,不会影响其余的请求继续争抢锁。

那这就完美了吗,还有没有别的问题?首先这把锁的过期时间并不好确定,时间设置长了只会造成资源上的浪费。而且最重要的问题是,第1个请求并没有挂掉,在锁有效的时间内,它还没有处理结束导致锁超时。

一般解决这种问题的方案是使用多线程:在第1个请求获得锁以后,重新开一个线程去监视这个获得锁的请求,每隔一段时间去看看获得锁的请求任务有没有处理完,如果没有处理完,就继续将锁的时间重置,往后延伸一段时间。那么在处理任务的时间内,这把锁就永不过期。

只不过采用这种方案的话,会让客户端的代码逻辑稍显复杂。而且这种方案也并不能100%的保证问题完美解决,通常当你使用一个方案去解决一个问题,势必会引入另外新的问题。所以从技术的角度来说,方案讨论到这里已经足够了,企业级的项目一般代码写到这种水平也可以了。

这就是自己手动实现分布式协调,确实很麻烦,后面说到Zookeeper时候,会对分布式协调这一块进行更加详细的讲解。

2、Redis中的穿透

穿透这个概念,其实之前在介绍Bloom过滤器的时候有说过:从业务接收查询的数据是数据库中根本就不存在的数据。

假设一个电商公司只卖电子产品,但是用户偏偏就在搜索框里搜索母婴产品。用户搜索的产品数据库中肯定不存在,那更不要说缓存了。这些请求就会全部穿过Redis直接到达数据库,并且这些查询都是毫无意义的查询结果,一定是没有的,只会白白的增加数据库的压力,浪费性能。

所以穿透和击穿根本的区别在于:击穿所查询的数据是数据库中有,只是Redis缓存中过期了;而穿透所查询的数据是数据库中根本就没有。

那么针对穿透这个问题的解决方案是什么?之前所说的Bloom过滤器,就是一个有效的解决方案。不过Bloom过滤器本身也存在一些问题:Bloom过滤器中的数据只能增加,不能删除。

① 可以使用布谷鸟过滤器替代;

② 如果继续使用Bloom过滤器的话,可以对数据库中增加的商品在Reids中设置一个Key,数据为null。这样下次请求来Redis中查询的话,查询到这个数据其实是空的,再去数据库中查,查询之后设置到Redis中缓存。

3、Redis中的雪崩

前面我们说了击穿是因为一个key失效,面对高并发的情况下导致请求全部压到数据库上,我们得规避这个问题。实际上在线上环境中这种情况并没有那么常见,反倒是雪崩,如果架构方面没有设计好的话,容易引发这个问题。

什么时候产生雪崩?Reids中大量的Key同时失效,在高并发的情况下,请求过来的时候,Redis中查不到这些数据,就会穿过Redis去请求它后面的数据库,从数据库中去查询这条数据。这种场景在企业级应用中非常常见:比如设计一批Key的有效期到晚上12点结束,第2天就会使用一批新的数据替代。

解决雪崩这个问题一般有两种方案:

① 随机(均匀)分布过期时间;

② 强依赖击穿解决方案。

从上面举的例子来看,其实第1种解决方案并不实际,因为最容易产生雪崩的场景往往是人为设置同一时间设置大批Key同时过期,从根源上直接掐死了随机分布过期时间的可能。第1种方案的使用场景不是没有,只是比较少,只是很多时候雪崩的造成是人为刻意的。所以更多时候企业中对于雪崩的解决方案,其实就是强依赖击穿的解决方案。

当然如果在流量过大的时候,可以在业务层加一段关于零点延时的逻辑:让所有的请求随机阻塞不同的时间再来访问,归根结底就是用尽一切方法在同一时间点规避超大流量同时访问。这种解决方案会更加轻盈一点。

4、Redis中的分布式锁

如果用Redis作为分布式锁,要怎么做?光是一个setnx()就可以了吗?时间到了活还没干完怎么办?或者时间还没到但是自个儿挂了怎么办?

其实所有的解决方案在上面击穿中已经讲了,无非就是三点:

① setnx()

② 过期时间

③ 多线程监控(守护线程,或事务线程),延长过期时间

或者使用Redisson,但Redisson其实并不常见,否则也不会再诞生Zookeeper了,用Zookeeper做分布式事务锁是最好的。其实这里面涉及到一个常识就是:都已经是拿来做锁了,任何东西一旦触碰到锁以后,也就说明在效率方面已经不会有太高的要求,只会对准确性和一致性要求极高。而这恰恰与Redis的设计理念是背道而驰的,虽然Zookeeper肯定是没有Redis快(下一篇来讲讲这是为什么),但是在数据的可靠性、API开发的简易性这块Zookeeper比较占优势。

标签:请求,过期,Redis,数据库,杂谈,一捋,击穿,数据
来源: https://blog.csdn.net/FeenixOne/article/details/120587457

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有