高并发下redis作为缓存必见的问题

最近面试邀请明显多于了三四月份,明天打算去面试php工程师,为了巩固一下年久失修的redis相关知识,今天下午划水来发表一篇redis相关的博客,有错误的地方也欢迎指正。今天我来讲的不是redis的基础知识,而是在高并发下redis常见问题。所谓高并发我另一篇文章有讲到,这里我只提出一个数据,qps达到1000,假设我们今天面临的是如此高压下的redis相关问题,不讲操作,讲理论。

qps达到1000,假设我们mysql优化的相当不错,单个请求响应时间在0.01s,也假设我们的后端能够承受,排除掉了所有网络io的请求所消耗的时间,单页面只存在一个sql,我们的最长的请求消耗时间大约在10s左右。而且这还是相当相当理想的状态,就像真空环境一样是不可能在业务上达到这个级别。一个普通的网站sql大概也在4到5个左右,在我们今天提出的qps下,单个页面打开最长可能上百秒,这肯定是客户不能接受的。

那是不是我们单纯的加redis就够了呢,肯定是不行的。上了这个级别,代理转发的服务器可能都在数十台以上了,更加别说我们后端服务器的台数了。我们今天不说后台和前台和数据库怎么优化,也不讨论redis的部署量,太笼统了大家也看不下去。咱就单提redis,这个级别的redis都会遇到什么样的问题和解决办法。

缓存穿透问题

此问题我们其实稍微上了规模的公司基本上都能被问到相关问题更何况咱今天聊的问题。

定义:大量查询一个缓存中不存在的数据,将大量的查询打到关系型数据库上,简称缓存穿透。

解决思路:只要出现这种情况基本上就可以确定网站是被人攻击了,就算单秒查询1000次,这种几率在我们默认的合法操作情况下也是近千分之一的概率。当一个不存在的值反复被查询,那么我们就有两个解决方法了

解决方法:1.终止程序,但是这种也是非常不可取的,万一是真实用户不小心触碰到了bug呢。

2.布隆过滤器:( Bloom Filter)是由布隆(Burton Howard Bloom)在1970年提出的。它实际上是由一个很长的二进制向量和一系列随机映射函数组成,布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率。简单一点来说就是用bitmap 将所有可能存在的key进行hash方式缓存起来。一定不存在的将通不过过滤器这一层。缺点:虽然其中的算法是很厉害的,但是还是走了更多的程序,每次进行查询都会走一遍过滤器。

3.简单粗暴的方法,也是我比较喜欢使用的。将不存在的值缓存成空值。第一次到达db后,将空值缓存起来并设置有效时间,我一般是设置1分钟,反复查询我们还是将空值返回给他们,redis的单机可是能承载10w次的qps,多配置几台基本上满足所有需求,但是一定要记得设置失效时间,不然虽然是空值,一多也会占用相当多的资源


缓存雪崩问题

定义:我们给热键设置失效时间,当失效的一瞬间,DB未来的及返回数据给redis,导致大量请求达到关系型数据库上,导致关系型数据库崩溃,就是缓存雪崩而不是字面理解的缓存崩了。

解决思路:在高并发下的情况下我们这种情况是很常见的,记住我们现在讨论的是qps=1000的情况。其实这就和我们的使用缓存是一个道理,当一个热键要失效了,我们先应该查询数据库,然后将值返回给redis进行新值缓存,这就引入了常用的一个概念:互斥锁。

解决:

1.互斥锁:利用setnx,当我们利用此api的时候,我们可以这么用,因为我现在用的是手机在打字呢,所以就不能演示代码了。我们最新的请求进入,发现key值不存在,此时我们设置一个setnx 对应的键“mutex:key:shenchenxi”有过期时间的临时键,然后if(这个键不存在){更新键}else{休息50毫秒},就是一个互斥锁了。但是缺点也是很明显的,此时很多人都将会会被列入等待范畴,

2.永不过期:此方案是将热键设置成用不过期,然后我们可以再设置一个热键对应的逻辑过期时间,每次请求热键的时候查看逻辑过期时间是否存在,然后结合setnxapi进行设置类似互斥锁操作,只是如果setnx还未成功,我们直接返回的是老的数据,再setnx设置成功过后我们就记得删除互斥锁和更新逻辑过期时间,此方案当然也是存在缺点的:就是数据一致性得不到保证,当热键过期后,那么缓存有效时间=原本热键过期时间+热键重构时间,和互斥锁相比,就多了热键重构时间中的老数据。然后加上更加多的内存占用,因为一个键要使用两个键的内存来保证。

缓存算法

我们总归要面对一个现实,内存再好用,总会有溢出的一天,我们总要面对内存溢出的问题,此时redis就给我们提供了相应的算法了,但是在今天这样的场景的话选择策略是相对来说很重要的,但是一般来说没有一个绝对好的方案和策略,只有适合的场景,今天我还是推荐lru

FIFO算法:First in First out,先进先出。原则:一个数据最先进入缓存中,则应该最早淘汰掉。也就是说,当缓存满的时候,应当把最先进入缓存的数据给淘汰掉。

LFU算法:Least Frequently Used,最不经常使用算法,就是使用最少的部分的键。

LRU算法:Least Recently Used,近期最少使用算法。和LFU相比呢,区别在于我们是最近使用的最少的键

但是以上策略使用就要涉及到一个参数:maxmemory_policy:最大内存预警,当数据到达此值时候将会触发以上配置的策略,一般咱还可以有程序上的主动更新和设置键值的过期时间,来控制缓存更新,和内存的占用刷新。

我们公司一般缓存更新的话就是:lru做兜底,然后基本上是程序上主动更新为主,一般设置过期时间的话也只是短信验证码场景之类的小部分应用场景


以上基本上是咱今天高并发下的比见的问题,但是其实还有很多什么配置上的东西呢我们并没有深入讲解,要将就没完了,还有redis的持久化,这些就留给下一章吧。

你可能感兴趣的:(高并发下redis作为缓存必见的问题)