redis学习笔记(七):redis常见问题和解决方案

目录

一、缓存穿透

1、基本介绍

2、解决方案

(1)布隆过滤器

(2)缓存空对象

(3)参数校验

(4)对比

二、缓存击穿

1、基本介绍

2、解决方案

(1)互斥锁

(2)永不过期

(3)两种方案对比

三、缓存雪崩

1、基本介绍

2、解决方案

(1)过期时间打散

(2)热点数据不过期

(3)加互斥锁

(4)高可用

(5)服务降级

(6)双层缓存策略


一、缓存穿透

1、基本介绍

        缓存穿透是指查询一个根本不存在的数据,缓存层和持久层都不会命中。在日常工作中出于容错的考虑,如果从持久层查不到数据则不写入缓存层,缓存穿透将导致不存在的数据每次请求都要到持久层去查询,失去了缓存保护后端持久的意义。

        正常情况下,当用户请求过来的时候,先查询缓存,如果缓存中存在数据则直接返回;如果缓存中不存在数据,则去查询数据库。当数据库中存在数据时,会将数据放入缓存然后返回;如果数据库中不存在数据,则直接返回空。   

         但是如果用户请求的ID在缓存中不存在或者恶意用户伪造不存在的ID发起请求,那么就会导致每次从缓存中都查不到数据,需要去查询数据库,且数据库也无法查询到数据,也不能放入缓存。因此导致缓存失去了作用,如同被穿透一般,每次都要访问数据库,当流量大的时候会导致数据库崩溃。因此,redis中如果同一个key的缓存命中率很低,有可能就是出现了缓存穿透的问题。

2、解决方案

(1)布隆过滤器

        布隆过滤器的特点是判断不存在的,则一定不存在;判断存在的,大概率存在,但也有小概率不存在。并且这个概率是可控的,我们可以让这个概率变小或者变高,取决于用户本身的需求。

        布隆过滤器由一个 bitSet 和 一组 Hash 函数(算法)组成,是一种空间效率极高的概率型算法和数据结构,主要用来判断一个元素是否在集合中存在。

        在初始化时,bitSet 的每一位被初始化为0,同时会定义 Hash 函数,例如有3组 Hash 函数:hash1、hash2、hash3。

布隆过滤器的写入流程

当我们要写入一个值时,过程如下,以“Python”为例:

1)首先将“Python”跟3组 Hash 函数分别计算,得到 bitSet 的下标为:1、7、10。

2)将 bitSet 的这3个下标标记为1。

假设我们还有另外两个值:java 和 C++,按上面的流程跟 3组 Hash 函数分别计算,结果如下:

java:Hash 函数计算 bitSet 下标为:1、7、11

c++:Hash 函数计算 bitSet 下标为:4、10、11

布隆过滤器的查询流程

当我们要查询一个值时,过程如下,同样以“Python”为例::

1)首先将“Python”跟3组 Hash 函数分别计算,得到 bitSet 的下标为:1、7、10。

2)查看 bitSet 的这3个下标是否都为1,如果这3个下标不都为1,则说明该值必然不存在,如果这3个下标都为1,则只能说明可能存在,并不能说明一定存在。

其实上图的例子已经说明了这个问题了,当我们只有值“Python”和“C++”时,bitSet 下标为1的有:1、4、7、10、11。

当我们又加入值“java”时,bitSet 下标为1的还是这5个,所以当 bitSet 下标为1的为:1、4、7、10、11 时,我们无法判断值“java”存不存在。

其根本原因是,不同的值在跟 Hash 函数计算后,可能会得到相同的下标,所以某个值的标记位,可能会被其他值给标上了。

这也是为啥布隆过滤器只能判断某个值可能存在,无法判断必然存在的原因。但是反过来,如果该值根据 Hash 函数计算的标记位没有全部都为1,那么则说明必然不存在,这个是肯定的。
降低这种误判率的思路也比较简单:

1)一个是加大 bitSet 的长度,这样不同的值出现“冲突”的概率就降低了,从而误判率也降低。

2)提升 Hash 函数的个数,Hash 函数越多,每个值对应的 bit 越多,从而误判率也降低。

总结一下:布隆过滤器认为不在的,一定不会在集合中;布隆过滤器认为在的,可能在也可能不在集合中。

(2)缓存空对象

        布隆过滤器可以过滤掉很多不存在的用户id请求。但它会带来两个问题:

  • 布隆过滤器存在误杀的情况,可能会把少部分正常用户的请求也过滤了。
  • 如果用户信息有变化,需要实时同步到布隆过滤器,不然会有问题。

        当缓存未命中,查询持久层也为空,可以将返回的空对象写到缓存中,这样下次请求该key时直接从缓存中查询返回空对象,请求不会落到持久层数据库。为了避免存储过多空对象,通常会给空对象设置一个过期时间。

这种方法会存在两个问题:

  • 如果有大量的key穿透,缓存空对象会占用宝贵的内存空间。
  • 空对象的key设置了过期时间,在这段时间可能会存在缓存和持久层数据不一致的场景。

(3)参数校验

        对用户id做检验,比如合法id是15xxxxxx,以15开头的。如果用户传入了16开头的id,比如:16232323,则参数校验失败,直接把相关请求拦截掉。这样可以过滤掉一部分恶意伪造的用户id。

(4)对比

解决方案适用场景维护成本
缓存空对象

数据命中率不高;

数据频繁变化,实时性高

代码维护简单;

需要过多的缓存空间;

数据不一致

布隆过滤器

数据命中率不高;

数据相对固定,实时性低

代码维护复杂;

缓存空间占用少

二、缓存击穿

1、基本介绍

        缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

        例如我们再商城购买某个热门的商品,一般情况下为了保证访问速度,商城系统会把商品信息放到缓存中,而该商品到了过期时间就会失效。此时如何大量用户去请求同一个商品,但是这个商品却已经在缓存中失效了,这样就会使得大量请求直接冲击数据库,就可能造成瞬间数据库的压力骤增而直接挂掉。

2、解决方案

(1)互斥锁

只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。

(2)永不过期

永不过期包含两层意思:

  •  从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是“物理”不过期。
  • 从功能层面来看,为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去更新缓存。

(3)两种方案对比

  •  分布式互斥锁:这种方案思路比较简单,但是存在一定的隐患,如果在查询数据库 + 和 重建缓存(key失效后进行了大量的计算)时间过长,也可能会存在死锁和线程池阻塞的风险,高并发情景下吞吐量会大大降低!但是这种方法能够较好地降低后端存储负载,并在一致性上做得比较好。
  • 永远不过期:这种方案由于没有设置真正的过期时间,实际上已经不存在热点key产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。

三、缓存雪崩

1、基本介绍

        缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,请求直接落到数据库上,引起数据库压力过大甚至宕机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

缓存雪崩目前有两种:

  • 有大量的热门缓存,同时失效。会导致大量的请求,访问数据库。而数据库很有可能因为扛不住压力,而直接挂掉。
  • 缓存服务器宕机了,可能是机器硬件问题,或者机房网络问题。总之,造成了整个缓存的不可用。

2、解决方案

(1)过期时间打散

        既然是大量缓存集中失效,那最容易想到就是让他们不集中生效。可以给缓存的过期时间时加上一个随机值时间,使得每个 key 的过期时间分布开来,不会集中在同一时刻失效。这样即使在高并发的情况下,多个请求同时设置过期时间,由于有随机数的存在,也不会出现太多相同的过期key。

(2)热点数据不过期

        该方式和缓存击穿一样,也是要着重考虑刷新的时间间隔和数据异常如何处理的情况。

(3)加互斥锁

        该方式和缓存击穿一样,按 key 维度加锁,对于同一个 key,只允许一个线程去计算,其他线程原地阻塞等待第一个线程的计算结果,然后直接走缓存即可。

(4)高可用

        针对缓存服务器宕机的情况,在前期做系统设计时,可以做一些高可用架构。比如:如果使用了redis,可以使用哨兵模式,或者集群模式,避免出现单节点故障导致整个redis服务不可用的情况。

        使用哨兵模式之后,当某个master服务下线时,自动将该master下的某个slave服务升级为master服务,替代已下线的master服务继续处理请求。

(5)服务降级

        缓存降级是指缓存失效或缓存服务器挂掉的情况下,不去访问数据库,直接返回默认数据或访问服务的内存数据。在项目实战中通常会将部分热点数据缓存到服务的内存中,这样一旦缓存出现异常,可以直接使用服务的内存数据,从而避免数据库遭受巨大压力。降级一般是有损的操作,所以尽量减少降级对于业务的影响程度。

(6)双层缓存策略

        主缓存:有效期按照经验值设置,设置为主读取的缓存,主缓存失效后从数据库加载最新值。

        备份缓存:有效期长,获取锁失败时读取的缓存,主缓存更新时需要同步更新备份缓存。

        缓存预热什么是缓存预热?

        缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统,这样就可以避免在用户请求的时候,先查询数据库,然后再将数据回写到缓存。

        如果不进行预热, 那么 Redis 初始状态数据为空,系统上线初期,对于高并发的流量,都会访问到数据库中, 对数据库造成流量的压力。

缓存预热的操作方法

  • 数据量不大的时候,工程启动的时候进行加载缓存动作;
  • 数据量大的时候,设置一个定时任务脚本,进行缓存的刷新;
  • 数据量太大的时候,优先保证热点数据进行提前加载到缓存。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值