原来解决Redis击穿、雪崩、穿透真的不是那么简单的一件事!!(缓存击穿)

目录

一、分析请求流程

二、缓存击穿

三、总结


  • 缓存雪崩
  • 缓存穿透

缓存技术是我们为了减少数据库压力而衍生出来的,Redis是当下解决缓存最常用的组件,当然Redis击穿,雪崩,击透也是我们面试的高频面试问题,但是往往大部分面试者都是给网上找到的问题答案给面试官背一遍,但是网上的方式真的能解决开发中的问题吗?绝大部分人都肯定都没有去验证,也就没有真的去考虑方法的可行性。

其实,击穿、雪崩、击透的真正崩溃的是数据库,那么能导致数据库崩溃的前提是高并发

一、分析请求流程

  • 首先,客户端(APP、WEB)用户触发请求
  • 负载均衡Nginx请求转发,流量分散层分发请求
  • Gateway,Zuul接口网关层接收请求,按照转发协议对请求进行转发
  • 微服务Service接收请求,每个请求首先访问我们的Redis缓存
  • 缓存失效或不存在缓存数据,访问DB,Mysql或Oracle关系型数据库

如上,一套比较标准的请求流程,应该也是大部分公司现在常规的请求流程。那么,我们可以看出,击穿,雪崩,击透的问题出现在第四步,缓存中的Key不存在,所以第四步失效,请求直接打到DB,导致数据库崩溃,倒是服务瘫痪。当然数据库也没有那么脆弱,崩溃的前提也是请求足够多,也就是我们的系统日活用户足够多,请求百万级别,千万级。如果说我们的系统日活用户没有多少,换句话说就没有高并发的可能,那么请求直接打到数据库上数据库也完全也能够支撑的住。所以,当我们的系统真正的有高并发请求的前提上,我们才需要去考虑或者说真正的去实践所谓的击穿,雪崩,击透。

二、缓存击穿

何为击穿?简单的来说就是Redis缓存的某一热点key失效,导致同一时间的并发请求绕过缓存,直接打到DB,导致数据库崩溃。

解决缓存击穿网上有很多解决方案。大致是一方面从Redis本身解决,也就是热点Key的失效和刷新,另一方面是业务上,加锁。

  • 热点key永不失效
  • 在热点数据更新的同时,去刷新缓存数据

这样热点key不再失效,理想状态下我们肯定是从缓存中拿到热点key的数据

         if(key is exist){

                return redis.get(key)

         }

当热点更新时,我们去刷新缓存数据
 

        if(key is update){

            refresh  (data from DB) 

        }

这种处理的最大的问题在于,我们真的在拿Redis作为一个 "库"去使用,我们都知道关系型数据库的数据真的存储在磁盘上,而Redis作为缓存查询效率高的原因是基于内存。而我们生产机的内存就那么多,我们所谓的"热点KEY永不失效",也就是说我们所谓的热点全部都会存在内存中供我们使用。内存就那么多,而我们将内存大部分都用来存热点key了,是不是很不应该。并且,热点更新的时候去刷新热点数据。热点数据肯定是时时热点,就拿微博来说,某一时刻的热点,过一段热度下来,也就消失了。那我们的热点KEY永不失效,那我们就要把这些所谓的热点全部扔到内存里面去吗?我们自身都无法时时区分什么是所谓的热点,更别说机器了,所以热点KEY永不失效并不是万能的,有些时候真的不适合来解决击穿的问题。

  • sync加锁
  • ReentrantLock.tryLock(),缓存没有,尝试加锁,抢不到就睡一会,抢到的那一个查数据库;
  • Redis分布式锁,多个服务请求唯一请求拿到锁,尝试查询数据库,并将数据放入到Redis中,其余的请求拿到锁以后查询Redis数据返回

首先,对于加锁的确可以解决击穿问题。当然具体用哪个锁,怎么用也应该具体分析。

sync关键字,单体架构解决并发请求串行是没问题,但是我们需要考虑的是,缓存击穿是什么场景下?高并发,那么高并发的系统,我们可能单体部署服务么,当然不。肯定是多实例集群部署,那么一个sync怎么锁住多个服务?其次,抛开多实例部署不谈,我们用sync锁住业务,那么其余请求都被挡在外面,换句话说也就是所有请求Redis的请求都上了互斥锁,并不合适。

所以,既然是解决缓存击穿,那么注定是高并发,那么高并发,必定多实例部署,那么多实例部署那么就涉及到分布式锁的概念。(redisson)

  • Redis中某热点key失效,可能正常或者非正常失效
  • 并发请求请求server,server向redis中拿热点key,发现为空
  • 这时候就需要redis分布式锁了,redisson提供较为完善的Api,trylock(key) ,当然众多请求中只有唯一的一个请求可以拿到锁,进而去数据库查询数据然后放回缓存中
  • 其余的请求在等待过后重新尝试拿到了锁,然后这时候再从缓存中寻找热点key,发现这时候是可以拿到热点key的数据了,返回即可

这段逻辑,看似没什么问题,但是还是有几个点需要注意

  1. redis分布式锁的失效时间。

有的人可能觉得奇怪,为什么要关心失效时间呢?我随随便便设置一个失效时间,保证不要死锁,让服务可以正常运行不就行了么?其余的还需要关心吗?当然要关心,举个例子,当你的失效时间<数据库查询时间,也就是说,你的A请求在查询数据库的时候,还没将热点数据放入缓存中,锁就失效了,B请求拿到锁,去缓存中拿的时候发现缓存中还是没有,又要去数据库中拿,这样的话,缓存击穿还是可能出现,分布式锁也就失去的意义。所以失效时间要根据查询时间具体考虑设置个有意义的时间才可以。

   2. 网络波动,或其他因素导致请求时常逐个增加。

最坏的情况,网络原因导致请求延迟增长,A请求没有放到缓存,B一样,C一样。。。。。 并且导致请求延迟越来越高,最后导致部分请求直接熔断,返回错误信息,即使没有熔断,但是由于请求时长,用户体验也会极差,我们产品肯定是用户体验是最重要的,所以上锁这种情况不是不可能出现的。

其实,即使用Redis分布式锁,用Redisson组件等等,可是以上的所有分析方法,上文中都是在单体Redis上来说,那么问题来了,如果生产用的不是单体的Redis呢?上的直接是Redis集群呢?锁获取成功的前提是什么?失效时间怎么设置?释放锁的标志是什么?这跟集群的主从关系,模式等等都有关系。

三、总结

所以,对于缓存击穿的解决方案有很多,博客更是一大堆,热点key永不失效,上锁等等,但是对于实际业务真的合适么,大部分人可能真的没有考虑,仅仅只是为了应付面试,然后看着网上很多人都这么说,然后默认就觉得这个答案是最优解。所以击穿问题真的不是那么轻松就解决的,包括我提出的这几个都是很简单的问题,相信很多人也都考虑到了。在我研究了集群的相关后,会继续更新缓存击穿,集群分布式锁问题。

缓存雪崩,缓存穿透,在后续的博客更新!!

你可能感兴趣的:(缓存击穿,雪崩,穿透)