Redis如何解决缓存穿透、缓存雪崩、缓存击穿?

缓存的目的就是保护数据库

什么是缓存穿透?

就是查询缓存和数据库都没有的数据,导致每次都会查询数据库

解决方案:

  1. 缓存一个空对象。当第一次查询缓存没有查询到数据的时候,从数据库也没有查询到数据,这个时候将id和一个空对象添加到缓存中。下一次查询这个数据的时候就会从redis查询到空对象然后返回。还需要修改一个地方,在命中缓存的代码中,之前是查询到就返回,没有查询到就去查数据库,这个时候在查询到缓存后,还需要判断这个数据是不是属于之前添加到缓存中的那个空对象。是那个空对象就返回没有查询到数据这个结果。否则直接返回查询到的数据结果。

缺点:

只能针对同一个key起作用,如果每次都查询不同的key且都不存在于缓存和数据库,就不 起作用。

redis中会存在大量空对象,都是没用的数据,占用redis的内存

b. 使用布隆过滤器。添加redission依赖

添加依赖后,创建布隆过滤器有两个参数,一个是预插入数据数,一个是容错率。

底层原理:创建一个位(bit)数组,长度为预插入的数据个数,每次下标位置初始值都为0。将要插入的数据通过不同的hash算法得到不同的hash值,然后将hash值与预插入数据数进行取模运算得到一个数字,也就是下标。将这些下标所在的0改为1。这里的hash算法个数是通过预插入数据数还有预判概率算出来的,个数越多那么通过取模运算后得到的下标也就越多,后期通过mightContain判断一个数据存不存在的时候,就要多个下标返回的数据都是1,那么才表示可能存在。但是如果hash算法个数过多,而数组容量太小,只put几个数据数组中的数据就全变为1了,这样就会大大增加误判的概率。

谷歌的布隆过滤器是通过jvm实现的,数据都存放在内存中,不能从持久化保存,并且能存入的数据量最多是21亿。所以我们可以基于redis实现自己创建布隆过滤器,它的最大容量是String类型数据能存储的最大容量也就是512Mb,42亿。

什么是缓存雪崩?

一时间redis中的所有key全部失效,只能访问数据库,给数据库带来巨大压力导致崩溃。

造成原因:

  1. 所有的key设置了相同的expire过期时间

  1. redis所在的服务器崩了

解决方案:

  1. 对key设置不同的过期时间,在一段时间内随机取值

  1. 进行分布式开发,搭建redis集群。如果是搭建切片式的集群,数据就会分散存储,不会一时间全部不能使用。如果数据量不是很多,可以搭建副本模式集群,每份数据都保存到几个redis中

在搭建redis集群的时候,数据量太大我们需要扩容,而数据量减小我们需要缩容。因此,数据如何存放就是一个问题。例如之前我们将数据通过hash算法还有取模运算将数据均匀分散到两台redis中,但这个时候,需要加入一台redis,那么之前两台redis的数据就要全部进行重新运算,再进行分散存储。所以这个时候:

hash一致性的算法可以解决这个问题

我们构造一个hash环,将0-2^32-1所有的整数放进去,然后将两台redis的唯一计算机主机名称或者ip地址进行hash算法,然后对2^32-1进行取模运算,得到一个值,就是该redis主机所在hash环的位置,存入的数据也是通过hash和取模计算出在hash环相应的位置,我们将数据通过顺时针进行寻找离的最近的redis主机,将这个数据存入到该redis中。当有其他redis主机加入,也是先计算它在hash环中的位置,我们只需要将它顺时针前面的另一个redis中的数据进行重新计算,然后出入。每次有新redis服务加入,就只需要改动两台redis主机中的数据,而不需要全部都改动。

弊端:数据倾斜,有些redis中的数据多,有些少,因为他们在hash环中的位置并不是均匀的,所以可以通过添加虚拟redis服务,将hash环里面的redis服务尽量变得均匀分散。

什么是缓存击穿?

有一个热点数据突然失效,导致大量请求访问数据库。一般的公司不需要解决缓存击穿,因为不会有那么一条热点数据访问量这么高导致数据库崩溃。

解决办法:

不给key设置过期时间

使用互斥机制(加锁)来解决

  1. 分布式锁(zookeeper、mysql分布式锁、redis、redission)

  1. 本地锁(JUC包下的Lock接口的各种实现类的锁,JDK自带synchronized锁)

对象都是由对象头组成,在对象头中有个锁的标识位,通过改变这个标识位,来加锁。

本地锁的锁对象如果时实例方法那么就是调用方法的对象,如果是类方法静态方法,那么锁对象就是这个类。

分布式锁的锁对象就是分布式中间件中的对象

使用分布式锁产生问题:

  1. 无法释放锁

  1. 设置过期时间不是原子性

  1. 锁的误删

  1. 删锁的原子性

  1. 出现死锁(不是分布式锁特有,只要加锁就有可能出现) 写的代码逻辑问题,保证不写错就行

使用redis解决缓存击穿,锁升级过程

  1. 使用redis的setnx命令:当且仅当key不存在时,将value保存,并返回1,若key存在不做任何操作,并返回0;java代码是:Integer lock = redisTemplate.setIfAbsent(key,value);接下来判断lock是否为true,是的话,就是获取到了锁,然后查询数据库,保存到redis,删除lock。没有获取到锁就递归调用该方法。

  1. 无法释放锁:如果一个线程获取到了锁,但是出现了异常,不能释放锁,就会导致其他线程获取不了锁,一直重复递归,最终可能栈内存溢出。

解决方案:给key设置一个过期时间。redisTemplate.expire(key,timeout,timeunit)。

  1. 设置过期时间不是原子性操作:可能获取到锁后,设置过期时间的时候出现了问题

方案:是用redisTemplate.setIfAbsent()四个参数的方法,里面加上了设置过期时间和单位两个参数

  1. 锁的续期问题:当一个线程获取到了所之后,去查询数据库消耗的时间比较久,业务还没有执行完,就因为到了过期时间然后释放掉了锁。

方案:使用redission框架的看门狗机制。

使用while(true),在里面创建一个线程,设置为守护线程,每隔一段时间就将过期时间进行续期,因为是守护线程,所以当业务执行完毕,这个线程也就会销毁。

  1. 锁的误删:在执行业务的时候,因为网络或者一些其他原因,导致过期时间到了释放锁,这个时候其他线程获取到了锁,在执行业务的时候,上个线程突然抢到了cpu的执行权,开始执行删除锁的操作,但删除的却不是自己的锁。

方案:给锁设置一个标识,例如使用uuid作为value设置到lock中,在删除锁的时候,先从redis中获取lock的value,判断一下是不是当前线程之前创建创建的uuid,如果不是,就代表lock不是本线程的锁。

  1. 删锁不是原子性操作:在判断lock是当前线程后,准备执行删锁操作,这个时候锁因为到期释放掉,这个时候被其他线程获取到,然后这个线程抢夺到cpu执行权就又会出现误删锁的情况,无论使用几重检查都可能出现这种意外情况。

方案:使用lua脚本,保证判断锁和删除锁是原子性操作

这里的token就是获取锁的时候给当前线程设置的唯一标识,例如uuid

  1. 出现死锁:当一个线程获取到锁去执行业务,在锁的过期时间之内同时出现了百万的请求,先获取锁没有获取到,然后递归调用方法,就会出现栈内存溢出。这个时候使用自旋锁,没有获取到锁的时候先不着急递归,通过一个死循环让线程一直获取锁,获取到了就跳出循环执行递归,没有获取到就一直获取。但是,当进行递归的时候,进入方法内,最上面的代码就是又去设置token,获取锁,很显然获取不到,两个锁的token不一致,但是锁又没办法释放掉,所以就出现了死锁

方案:设置一个全局变量map,在自旋的时候获取到了锁就将当前线程作为key,true作为value存入这个map中,递归后就先从map中根据线程获取一个tag,若为true则表示该线程已经获取到了锁,就直接进行下面的操作,若没有获取到就表示这是第一次进入方法中,直接获取锁就可以。

问题1:删锁的时候会判断token,递归进来的没有token就一直删除不了

方案1:存入map的时候,value不再使用true和false,将token存进来就可以

方案2:使用ThreadLocal,里面有个get和set方法,也是通过map的形式,会自动将当前线程作为key

  1. cpu占用较高:在自旋的时候,如果请求量较大,那么百万请求就会重复去抢锁,占用cpu大量资源。

方案:在自旋的时候,先将线程睡几秒,因为这几秒也是获取了锁的线程去查询数据库然后存缓存的时间。睡醒后再去抢锁,没抢到继续睡然后再抢。

缓存穿透缓存击穿缓存雪崩是常见的缓存问题,下面是关于Redis缓存穿透缓存击穿缓存雪崩的介绍: 1. 缓存穿透缓存穿透是指当一个请求查询一个不存在于缓存中的数据时,由于缓存无法命中,请求会直接访问数据库。这种情况下,如果有大量的请求查询不存在的数据,会导致数据库压力过大,影响系统性能。 2. 缓存击穿缓存击穿是指当一个热点数据的缓存过期或失效时,大量的请求同时访问该数据,导致缓存无法命中,请求会直接访问数据库。这种情况下,数据库会承受巨大的压力,可能导致数据库崩溃。 3. 缓存雪崩缓存雪崩是指当缓存中的大量数据同时过期或失效时,大量的请求会直接访问数据库,导致数据库压力剧增,性能下降甚至系统崩溃。缓存雪崩通常是由于缓存服务器故障、缓存设置不合理或者缓存数据过期时间设置不当等原因引起的。 为了避免缓存穿透缓存击穿缓存雪崩问题,可以采取以下措施: - 缓存穿透:可以在应用层对查询的数据进行校验,如果数据不存在,则不进行缓存操作,避免大量无效的请求访问数据库。 - 缓存击穿:可以互斥锁或分布式锁来保护热点数据的问,当缓存失效时,只允许一个请求访问数据库并更新缓存,其他请求等待缓存更新完成后再从缓存中获取数据。 - 缓存雪崩:可以采用多级缓存缓存预热、设置合理的缓存过期时间等策略来避免大量缓存同时失效,保证系统的稳定性和性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值