Redis中的缓存雪崩、穿透、击穿,及解决方案(如何保证缓存与数据库双写时的数据一致性?)

     下图是一个正常的系统架构图,其中缓存的作用是减轻数据库的压力提升系统的性能,无论是缓存雪崩、缓存击穿还是缓存穿透都是缓存失效了导致数据库压力过大。
在这里插入图片描述

缓存雪崩

什么是缓存雪崩?

    缓存雪崩是指在某一个时刻出现大规模的缓存失效的情况,大量的请求直接打在数据库上面,可能会导致数据库宕机,如果这时重启数据库并不能解决根本问题,会再次造成缓存雪崩。

为什么会造成缓存雪崩?

一般来说,造成缓存雪崩主要有两种可能

  • Redis宕机了
  • 很多key采取了相同的过期时间
如何解决缓存雪崩?
  • 为避免Redis宕机造成缓存雪崩,可以搭建Redis集群
  • 尽量不要设置相同的过期时间,例如可以在原有的过期时间加上随机数
  • 服务降级,当流量到达一定的阈值时,就直接返回“系统繁忙”之类的提示,防止过多的请求打在数据库上,这样虽然难用,但至少可以使用,避免直接把数据库搞挂

缓存击穿

什么是缓存击穿?

    缓存雪崩是大规模的key失效,而缓存击穿是一个热点的Key,有大并发集中对其进行访问,突然间这个Key失效了,导致大并发全部打在数据库上,导致数据库压力剧增,这种现象就叫做缓存击穿。
    比较经典的例子是商品秒杀时,大量的用户在抢某个商品时,商品的key突然过期失效了,所有请求都到数据库上了。

如何解决缓存击穿
  • 考虑热点key不设置过期时间,避免key过期失效
  • 加锁,如果缓存失效的情况,只有拿到锁才可以查询数据库,降低了在同一时刻打在数据库上的请求,防止数据库宕机,不过这样会导致系统的性能变差。

缓存穿透

什么是缓存穿透

    缓存穿透是指用户的请求没有经过缓存而直接请求到数据库上了,比如用户请求的key在Redis中不存在,或者用户恶意伪造大量不存在的key进行请求,都可以绕过缓存,导致数据库压力太大挂掉。

如何解决缓存穿透
  • 参数校验,例如可以对用户id进行校验,直接拦截不合法的用户的请求
  • 布隆过滤器,布隆过滤器可以判断这个key在不在数据库中,特点是如果判断这个key不在数据库中,那么这个key一定不在数据库中,如果判断这个key在数据库中,也不能保证这个key一定在数据库中。就是会有少数的漏网之鱼,造成这种现象的原因是因为布隆过滤器中使用了hash算法,对key进行hash时,不同的key的hash值一定不同,但相同的hash的值不能说明这两个key相同。
    更多关于布隆过滤器请点击下面链接:
    详解布隆过滤器

如何保证缓存与数据库双写时的数据一致性?

    首先先来了解下一致性,在分布式系统中,一致性是指多副本问题中的数据一致性。一致性可以分为强一致性、弱一致性和最终一致性。

  • 强一致性:当更新操作完成之后,任何多个后续进程或者线程的访问都会返回最新的更新过的值。强一致性对用户比较友好,但对系统性能影响比较大。
  • 弱一致性:系统并不保证后续进程或者线程的访问都会返回最新的更新过的值。
  • 最终一致性:也是弱一致性的一种特殊形式,系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。

    大多数系统都是采用的最终一致性,最终一致性是指系统中所有的副本经过一段时间的异步同步之后,最终能够达到一个一致性的状态,也就是说在数据的一致性上存在一个短暂的延迟。

    如果想保证缓存和数据库的数据一致性,最简单的想法就是同时更新数据库和缓存,但是这实现起来并不现实,常见的方案主要有以下几种:

  • 先更新数据库,后更新缓存
  • 先更新缓存,后更新数据库
  • 先更新数据库,后删除缓存
  • 先删除缓存,后更新数据库

    乍一看,感觉第一种方案就可以实现缓存和数据库一致性,其实不然,更新缓存是个坑,一般不会有更新缓存的操作。因为很多时候缓存中存的值不是直接从数据库直接取出来放到缓存中的,而是经过一系列计算得到的缓存值,如果数据库写操作频繁,缓存也会频繁更改,所以更新缓存代价是比较大的,并且更改后的缓存也不一定会被访问就又要重新更改了,这样做无意义的性能消耗太大了。下面介绍删除缓存的方案。

先更新数据库,后删除缓存

    这种方案也存在一个问题,如果更新数据库成功了,删除缓存时没有成功,那么后面每次读取缓存时都是错误的数据。

    解决这个问题的办法是删除重试机制,常见的方案有利用消息队列和数据库的日志利用消息队列实现删除重试机制,如下图
在这里插入图片描述
    步骤在图中写的已经比较清除了,这里简单说下为什么使用消息队列,消息队列可以保证写到队列中的消息在成功消费之前不会消失,并且在第4步中获取消息时只有消费成功才会删除消息,否则会继续投递消息给应用程序,符合消息重试的要求。

    但这个方案也有一些缺点,比如系统复杂度高,对业务代码入侵严重,这时可以采用订阅数据库日志的方法删除缓存。如下图
在这里插入图片描述

先删除缓存,后更新数据库

这种方案也存在一些问题,比如在并发环境下,有两个请求A和B,A是更新操作,B是查询操作

  1. 假设A请求先执行,会先删除缓存中的数据,然后去更新数据库
  2. B请求查询缓存发现为空,会去查询数据库,并把这个值放到缓存中
  3. 在B查询数据库时A还没有完全更新成功,所以B查询并放到缓存中的是旧的值,并且以后每次查询缓存中的值都是错误的旧值

    这种情况的解决方法通常是采用延迟双删,就是为保证A操作已经完成,最后再删除一次缓存。
在这里插入图片描述
    逻辑很简单,删除缓存后,休眠一会儿再删除一次缓存,虽然逻辑看起来简单,但实现起来并不容易,问题就出在延迟时间设置多少合适,延迟时间一般大于B操作读取数据库+写入缓存的时间,这个只能是估算,一般可以考虑读业务逻辑数据的耗时 + 几百毫秒。

    在实际应用中,还是先更新数据库后删除缓存这种方案用的多些。
    需要注意的是,无论哪种方案,如果数据库采取读写分离+主从复制延迟的话,即使采用先更新数据库后删除缓存也会出现类似先删除缓存后更新数据库中出现的问题,举个例子

  1. A操作更新主库后,删除了缓存
  2. B操作查询缓存没有查到数据,查询从库拿到旧值
  3. 主库将新值同步到从库
  4. B操作将拿到的旧值写入缓存

    这就造成了缓存中的是旧值,数据库中的是新值,解决方法还是上面说的延迟双删,延迟时间要大于主从复制的时间。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值