欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Redis 缓存穿透、缓存并发、缓存雪崩

程序员文章站 2022-06-19 11:29:54
...

缓存穿透、缓存并发、缓存雪崩是常见的由于高并发而导致的缓存问题。下面讲解其产生原因和解决方案。

缓存穿透通常是由于恶意攻击或无意造成的;缓存并发是由于设计不足造成的;缓存雪崩是由于大量缓存同时失效造成的。三种问题都比较典型,是难以防范和解决的。下面讲解其解决方案,以便在缓存设计中进行参考。

1、缓存穿透

缓存穿透是指用户使用不存在的key进行大量的高并发查询,这种请求不能命中缓存,每次请求都要穿透到后端数据库系统进行查询,使数据库压力过大,导致数据库连接异常。
解决方案
(一) 采用互斥锁,每次请求数据库之前先获取锁,获得锁了再访问数据库,没得到锁,则休眠一段时间重试。
(二) 采用异步更新策略,无论key 是否取到值,都直接返回。value值中维护一个缓存失效的时间,缓存如果过期,则异步起一个线程去读数据库,更新缓存。需要做缓存预热 (项目启动前,先加载缓存)操作。
(三) 提供能迅速判断请求是否有效的拦截机制,比如,布隆过滤器,内部维护一系列合法有效的key。迅速判断出,请求所携带的Key是否有效。如果不合法,则直接返回。
(四) 将请求得到的空值缓存起来,再次接收到同样的查询请求时,若命中缓存且值为空,就直接返回,不会穿透到数据库,避免缓存穿透。如果攻击者猜到了我们的使用这种方案,每次都使用不同的参数查询,这就需要我们对输入的参数进行过滤。例如,如果我们使用ID 进行查询,我们可以对ID的格式进行分析,如果不符合格式,就直接拒绝;或者在ID上放上时间信息,根据时间信息判断ID是否合法;或者是否是我们曾经生成过的ID,这样可以拦截一定的请求。

2、缓存并发

缓存并发的问题通常发生在高并发的场景下,分两种情况:
第一种:当一个缓存key过期时,因为访问这个缓存key 的请求量较大,多个请求同时发现缓存过期,因此多个请求会同时访问数据库来查询最新数据,并且回写缓存,这样会造成应用和数据库的负载增加,性能降低,由于并发较高,甚至会导致数据库被压死。
第二种:多个子系统去set同一个key,网上给的大多数解决方案都是使用redis事务机制,但是这是行不通的,因为生产中redis都是集群部署,做了数据分片操作,一个事务涉及多个key的时候,这些key不一定都存储在一个redis-server上。

第一种缓存并发(get并发)的解决方案:
(一) 分布式锁,保证对于每个key同时只有一个线程去查询数据库,其他线程没有获得分布式锁,只需等待即可。这种方式将高并发压力转移到了分布式锁,因此对分布式锁压力很大。
(二) 本地锁,与分布式锁一样,我们采用本地锁限制只有一个线程去查询数据库,而其他线程只需要等待,等前面的线程查到数据后再访问缓存,这种方式只能限制一个服务节点只有一个线程去查询数据库,如果一个服务有多个节点,则还会有多个数据库查询操作,也就是在节点数量较多 ( 服务多节点部署 ) 的情况下不能完全解决缓存并发问题。
(三) 软过期,软过期是指对缓存中的数据设置失效时间,就是不使用缓存服务提供的过期时间,而是业务层在数据中存储过期时间信息 ( 可以考虑优先队列或者最小堆实现 ),由业务程序判断缓存是否过期并更新,在发现数据即将过期时,将缓存的时效延长,同时程序可以派遣一个新线程去数据库获取最新数据,其他线程看到延长了过期时间,就会继续使用旧数据,等派遣的线程获取到最新数据在更新缓存。

第二种缓存并发(set并发)的解决方案:
(一) 如果对这个进行set 操作,不要求顺序
这种情况下,准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可,比较简单。
(二) 如果这个key进行set 操作,要求顺序
假设有一个key1,系统A要将key1设置为value1 ,系统B要将key1设置为 value2,系统C要将key1 设置为value3.
期望key1的value值按照 value1 –> value2 –> value3的顺序变化。这时候我们在插入到数据数据库的时候,需要保存一个时间戳。假设时间戳如下:

系统A key1 {valueA  3:00}
系统B key1 {valueB  3:05}
系统C key1 {valueC  3:10}

那么,假设这会系统B先抢到锁,将key1 设置为{valueB 3:05}。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那么不做set操作了。以此类推。
(三) 使用消息队列,将set变成串行访问。

3、缓存雪崩
相关标签: redis 缓存