Young87

当前位置:首页 >个人收藏

Redis-缓存雪崩,缓存穿透,缓存击穿出现的原因及解决方案、补充无底洞问题

缓存处理流程

      前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。

      

缓存雪崩

出现过程

1、缓存层由于某种原因宕机或失效

2、大量缓存数据同时过期

假设有如下一个系统,高峰期请求为5000次/秒,4000次走了缓存,只有1000次落到了数据库上,数据库每秒1000的并发是一个正常的指标,完全可以正常工作,但如果缓存宕机了,或者缓存设置了相同的过期时间,导致缓存在同一时刻同时失效,每秒5000次的请求会全部落到数据库上,数据库立马就死掉了,因为数据库一秒最多抗2000个请求,如果DBA重启数据库,立马又会被新的请求打死了,这就是缓存雪崩。

缓存雪崩是指缓存中数据大批量到过期时间(大量缓存中数据一起到期),而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据(热点数据),缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。


解决方法

1、事前:①redis高可用,主从+哨兵,redis cluster,避免全盘崩溃,②如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。③缓存的失效时间设置为随机值,避免同时失效/设置热点数据永远不过期。

2、事中:ehcache本地缓存 + hystrix限流&客户端降级,避免MySQL被打死

客户端降级场景距离:降级机制在高并发系统中是非常普遍的:比如推荐服务中,如果【个性化推荐服务】不可用,可以降级应用为【推荐热点数据】,不至于造成前端页面是开天窗。

扩展阅读:

3、事后:redis持久化RDB+AOF,快速恢复缓存数据

缓存穿透

出现过程

假如客户端每秒发送5000个请求,其中4000个为黑客的恶意攻击,即在数据库中也查不到。举个例子,用户id为正数,黑客构造的用户id为负数,如果黑客每秒一直发送这4000个请求,缓存就不起作用,数据库也很快被打死。

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

问题发现

通常可以在程序中分别统计【总调用数】、【缓存层命中数】、【存储层命中数】,如果发现大量存储层空命中,可能就是出现了缓存穿透问题
 

解决方法

1、请求参数进行校验:对请求参数进行校验,不合理直接返回(接口层增加校验,如用户鉴权校验,id基础校验,id<=0的直接拦截)
2、缓存空对象:查询不到的数据也放到缓存,value为空,如 set -999 “”(从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击)
3、使用布隆过滤器拦截在访问缓存层和存储层之前,将【存在的key】用布隆过滤 器提前保存起来,做第一层拦截,快速判断key是否在数据库中存在,不存在直接返回

第一种是最基本的策略,第二种其实并不常用,第三种比较常用。

为什么第二种并不常用呢?

因为如果黑客构造的请求id是随机数,第二种并不能起作用,反而由于缓存的清空策略,(例如清除最近没有被访问的缓存)导致有用的缓存被清除了。

下面给出2,3两种方案的适用场景——来自《redis开发与运维》709

缓存击穿


出现过程

设置了过期时间的key,承载着高并发,是一种热点数据。从这个key过期到重新从MySQL加载数据放到缓存的一段时间,缓存失效到重新加载到缓存这段时间,大量的请求有可能把数据库打死。缓存雪崩是指大量缓存失效,缓存击穿是指热点数据的缓存失效。

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期的热点数据,这些数据设置了过期时间的key,承载着高并发,是一种热点数据),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力

解决方法

1、设置热点key永远不过期:

永不过期包括两个层面的含义:

  • ①物理层面,从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期 后产生的缓存击穿问题
  • ②逻辑层面,为了保证能够得到更新,为每个value设置一个【逻辑过期时间】,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存,通过另一个异步线程重新设置key(缺点是重构缓存期间,会出现数据不一致的情况

2、当从缓存拿到的数据为null,重新从数据库加载数据的过程使用分布式锁,保证只有一个线程执行缓存重建过程,其他客户端可以选择等待+周期重新请求缓存,流程例如下面伪代码:

两种方案优缺点:

作为一个并发量较大的应用,在使用缓存时有三个目标:
  • 第一,加快用户访问速度,提高用户体验。
  • 第二,降低后端负载,减少潜在的风险,保证系统平稳。
  • 第三,保证数据“尽可能”及时更新。
述两种解决方案进行分析。
  • 互斥锁(mutex key):这种方案思路比较简单,但是存在一定的隐患,如果构建缓存过程出现问题或者时间较长,可能会存在死锁和线程池阻塞的风险,但是这种方法能够较好地降低后端存储负载,并在一致性上做得比较好,mzj:这里的一致性比较好是建立在设置的缓存过期时间是被应用所容忍不一致的时间范围,因此超过这个容忍时间自动过期,然后下次请求立马构建缓存,所以相对来说一致性好一些,但是如果需要保证强一致性,就得在数据变化时主动更新缓存了。
  • 永远不过期:这种方案由于没有设置真正的过期时间,实际上已经不存在热点key产生的缓存击穿危害,但是会存在数据不一致的情况,mzj:这里的数据不一致是相对互斥锁方案来说的,也就是虽然逻辑过期时间可以设置成应用容忍的时间,但是发现过期到重新构建缓存之间数据是允许继续被访问的,重构完成期间数据是不一致的,同时代码复杂度会增大(每次请求缓存时判断时间如果超过过期时间)。

下表总结两种解决缓存击穿问题方式的优缺点:

下面写个分布式锁实现的demo

Redis实现分布式锁

我之前的文章写到了Redis实现分布式锁的原理,这里就不再详细概述了

Redis分布式锁为什么要这样写?请看下面框框里的内容,来自:

除特别声明,本站所有文章均为原创,如需转载请以超级链接形式注明出处:SmartCat's Blog

上一篇: mysql performance report 开启,mysqlreport 查看mysql状态

下一篇: 今日头条 ANR 优化实践系列 - 设计原理及影响因素

精华推荐