Redis——应用问题解决

目录

1 缓存穿透

1.1 介绍

1.2 解决办法

2 缓存击穿

2.1 介绍

2.2 解决办法

3 缓存雪崩

3.1 介绍

3.2 解决办法

4 缓存预热

4.1 介绍

4.2 解决办法

5 数据一致性

5.1 一致性问题

5.2 一致性方案

(1)先更新数据库,再更新缓存

 (2)先删缓存再更新数据库(延时双删)

(3)先更新数据库再删除缓存


1 缓存穿透

1.1 介绍

key对应的数据在数据库并不存在,每次对此key的请求从缓存中获取不到,请求都会打到数据库上,从而可能压垮数据源。

比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库

通俗的来说:访问的数据在缓存和数据库中都找不到,请求一直打到数据库上。

1.2 解决办法

  1. 对空值缓存:如果一个查询结果为空,仍然把这个空结果(null)进行缓存,把有效时间时间设置短一些,最长不超过五分钟
  2. 对一定不存在的key进行过滤,接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截。
  3. 采用布隆过滤器:它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。
  4. 设置可访问的名单(白名单):使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
  5. 进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务。

2 缓存击穿

2.1 介绍

key对应的数据存在,但在redis中过期时,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从数据库加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把数据库压垮。

通俗的来说:一个热点key过期时,短时间内涌入大量的请求直接打到数据库上。

2.2 解决办法

  1. 预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的有效时间,或者设置为永不过期。
  2. 实时调整:现场监控哪些数据热门,实时调整key的过期时长
  3. 加互斥锁:当发现没有命中Redis,去查数据库的时候,在执行更新缓存的操作上加锁,当一个线程访问时,其它线程等待,这个线程访问过后,缓存中的数据会被重建,这样其他线程就可以从缓存中取值。

3 缓存雪崩

3.1 介绍

key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从数据库加载数据并回设到缓存。

出现缓存雪崩的情况大致有两种,一是redis中大量的缓存集中过期,二是redis宕机。

缓存雪崩与缓存击穿的区别在于:雪崩针对很多key缓存集体失效,击穿则是某一个key缓存失效

3.2 解决办法

  1. 将缓存失效时间分散开: 比如我们可以在原有的失效时间基础上增加一个随机值,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
  2. 构建多个redis实例,实现高可用性(主从复制或集群)。
  3. 构建多级缓存架构:nginx缓存 + redis缓存 +其他缓存(本地缓存等),减少redis压力。
  4. 使用锁或队列:用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况
  5. 设置过期标志更新缓存:记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存。

4 缓存预热

4.1 介绍

缓存预热就是系统上线后,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题。

4.2 解决办法

  1. 直接写个缓存刷新页面,上线后手动刷新;
  2. 数据量不大,可以在项目启动的时候自动进行加载;
  3. 配置一个job定时刷新缓存。

5 数据一致性

当使用Redis作为缓存的时候,查询的时候一般流程是这样的:如果缓存在Redis中存在,即缓存命中,则直接返回数据,如果Redis中没有对应缓存,则需要直接查询数据库,然后存入Redis,最后把数据返回。

5.1 一致性问题

在Redis的key值未过期的情况下,需要修改key对应的数据,此时既要操作数据库数据,也要操作Redis数据。现在我们面临了两种选择:

  1. 先操作Redis的数据,再操作数据库的数据
  2. 先操作数据库的数据,再操作Redis的数据

理论上来说,为缓存设置过期时间是最终保证数据一致性的解决方案。所有的写操作都是以数据库为准,如果数据库写入成功但是缓存更新失败,只要缓存到过期时间之后后面读缓存时自然会在数据库中读取新的值然后更新缓存。

不依赖为缓存设置过期时间的前提下如何保证数据一致性。这里主要探讨三种方案:

  1. 先更新数据库,再更新缓存
  2. 先删除缓存,再更新数据库
  3. 先更新数据库,再删除缓存

5.2 一致性方案

(1)先更新数据库,再更新缓存

这种方案是普遍被反对的:

1)首先从数据安全方面考虑,如果同时有请求A和请求B同时进行操作,A先更新了数据库的一条数据,随后B马上有更新了该条数据,但是可能因为网络延迟等原因,B却比A先更新了缓存,就会出现一种什么情况呢?缓存中的数据并不最新的B更新过的数据,就导致了数据不一致的情况。

 2)其次从业务场景方面考虑,如果是一个写数据库较多而读数据库较少的业务,如果采用这种方案就会导致数据还没读缓存就会被频繁更新,白白浪费性能。

 (2)先删缓存再更新数据库(延时双删)

问题:如果同时有一个请求A进行更新操作,请求B进行查询操作,就可能会出现A请求进行写操作前会删除缓存,B请求刚好此时进来发现缓存是空的,B请求就会查询数据库,如果此时A请求的写操作还未完成,B请求查询到的就还是旧的值,还是会将旧的值写入缓存,A请求将新的值写入数据库,此时就会导致数据不一致的问题,如果不采用给缓存设置过期时间的策略,该数据永远都是脏数据。

延时双删:就是在更新数据库之前先删除缓存,然后对数据库进行写入操作,数据库更新完成之后再次进行删除缓存的操作,目的是删除读请求可能造成的缓存脏数据,第二次删除缓存之前可以休眠几秒,具体时间开发者可以评估一下自己项目读数据业务逻辑的耗时,然后在该耗时基础上加几百ms即可,这么做的目的就是确保读请求结束之后,写请求才可以删除读请求造成的脏数据。如果MySQL采用的是读写分离的架构,可能由于主从延时的原因造成数据不一致,可以在写操作完成之后根据主从延时时间休眠一下然后再进行删除缓存的操作。

如果第二次删除失败,是会造成缓存和数据库不一致的问题,可以采取下面两种方案。

(1)可以设置缓存过期时间:差的情况就是在超时时间内数据存在不一致

(2)重试机制,重新尝试删除:

  1. 更新数据库
  2. 由于各种原因缓存删除失败
  3. 将删除失败的缓存放入消息队列中
  4. 业务代码从消息队列中获取需要删除的key
  5. 继续尝试删除操作,直到成功

(3)先更新数据库再删除缓存

有两个请求A和B,A进行查询同时B进行更新,假设发生下述情况:

  1. 此时缓存刚好失效
  2. 请求A 就会去查询数据库得到一个旧的值
  3. 请求B将新的值写入数据库
  4. 请求B写入成功后删除缓存
  5. 请求A将查到的机制写入缓存,产生脏数据

如果发声上述情况,确实会产生数据不一致的情况,但是发生这种情况的概率是多少呢?如果要产生这种结果,就必须有一个条件,就是请求B的操作时间非常短,短到什么程度呢,就是请求B写入数据库的操作要比请求A从数据库中读取数据的速度要快(因为redis非常快,因此操作redis的时间可以暂且忽略),只有这种情况下④才可能比⑤先发声,但是数据库的读操作要远比写操作快的多,不然做读写分离干嘛呢?所以这种情况发生的概率是非常非常非常的低。

但是如果必须要解决怎么办呢?就可以采用给缓存设置过期时间或者采用第二种方案的延时双删策略,保证读请求完成之后在进行删除操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值