Redis最主要的功能就是用来当作缓存,减少MySQL等关系型数据库的访问压力。
但是会出现以下三种问题:
缓存穿透就是攻击者高并发的查询一个数据库和缓存层都不存在对应数据的key,因为缓存层没有对应的数据,所以每次请求都会查询数据库,造成数据库的巨大压力。
解决方法:
1、缓存null
对于不存在的数据,在缓存层将key缓存,缓存的值为null,并为其设置一个过期时间。
缓存null 适用于 攻击者攻击使用key变换不频繁的情况下。如果攻击者每次都随机生成一个key,那么缓存null就不起作用了。
2、布隆过滤器
布隆过滤器 是 一个bit数组,通常表示为int数组,可以快速查询一个key是否存在。具体做法如下:
1、插入
将key通过哈希算法得到对应的哈希值,然后将其对bit数组的长度取模,然后将对应的bit设为1。
2、查询
通过哈希算法得到对应的哈希值,对bit数组长度取模,然后查看对应的bit是否为1。如果为1,说明存在,如果为0,说明不存在。
布隆过滤器是存在哈希冲突的,所以可以引入多个哈希函数,当插入的时候,利用多个哈希函数找到对应的位置,将对应的bit设为1.
然后查询的时候,也利用多个哈希函数分别找到对应的位置,只有所有哈希函数对应的bit都为1,才说明这个key存在。
由于存在哈希冲突,所以如果一个key对应的布隆过滤器中的bit全部为1,key也不一定真实存在。
但是如果一个key插入过,那么布隆过滤器的bit肯定是全部为1的,是一定能查询得到的。
布隆过滤器是将数据库对应的合法的key用布隆过滤器存储起来,每次在请求缓存层之前,首先查询布隆过滤器是否存在对应的key,如果不存在,就直接返回null。如果存在,查询对应的缓存。
一般情况下,我们可以按照 表名:列名:列值 的方式来设计 key。
3、参数检验
参数检验和布隆过滤器是大致的原理,都是通过检验的方式来减少缓存穿透。只不过参数检验的粒度更大,也更简单。简单的判断参数是否是合法参数,比如id<0等不合法的参数,一概直接返回null。
一些并发度相当高的场景,比如秒杀活动、双11等。
如果一个热点key这个时候过期了,然后大量的并发访问会直接请求数据库,并且这个key对应的值需要一个复杂计算,比如多个表的联合查询等。
这样的话,会有大量的线程来执行大量的复杂计算操作,会造成服务器的资源不足,造成服务器的宕机,也就是击穿了服务器。
解决方法:
1、互斥锁
缓存击穿的原因就是有大量线程同时执行复杂的数据库查询操作。可以在redis中加入一个互斥锁,只有当拿到redis的互斥锁的线程,才能执行对应的数据库查询操作,然后重建对应的redis缓存。其他的线程都通过redis缓存来获取数据。
伪代码
res = getRedis(key);//从redis获取值
mutexKey = "XXXX";//互斥锁对应的key,根据业务自定义
if(res == null){//没有获取到数据
for(;;){
if( (res = getRedis(key)) != null){//如果其他线程已经重建了缓存,直接使用缓存
return res;
}else if(mutex = setnx(mutexKey,value) == "OK"){//代表设置互斥key成功
val = queryDatabase(key);//查询数据库
setRedis(key,val);//设置缓存
setExpire(mutexKey,timeout);//设置mutexKey过期时间
return val;
}
}
}
redis提供了setnx()的接口,setnx 就是 set not exist的意思,也就是只有对应的key不存在的时候才能创建成功,返回"OK"。
因为redis是单线程接收命令的,所以可以将setnx()来用作一个简单的互斥锁。
2、设置永不过期
数据设置为永不过期,不会出现缓存击穿的问题,但是会一直占用内存。
如果大量的key在同一个时间全部过期失效或者redis宕机了,那么对应的请求会突然直接落到数据库中,导致数据库的崩溃,这就是缓存雪崩。
解决方法:
1、 为每个key在原本过期时间基础上,再添加一个随机值作为真正的过期时间,尽量不让大量key在同一时间内失效
2、 搭建一个redis集群,当一个redis服务宕机后,还可以使用其他redis的服务。可以使用sentinel或者cluster实现。关于redis的集群,我们单独写一篇文章详细分析。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/qq_40276626/article/details/120630665
内容来源于网络,如有侵权,请联系作者删除!