Redis雪崩、穿透、击穿分析

Redis常见使用问题及解决方案思考

       文章总结了使用缓冲中常见到的问题及解决方案,以后在业务中遇到新的问题会继续记录,方便自己回忆参考,文中给出的解决方案未必都在实战中测试过,只是抛砖引玉,广大读者如能发现更好的解决方案可以反馈作者补充。

1 缓存雪崩

1.1 何为缓存雪崩

       自然界中的雪崩场景,通常由于外力的作用或其他因素,导致原本处在“平衡”中的积雪开始滑动,进而导致大量的积雪从高出倾盆而下滑动,场景十分“壮观而吓人”。下图有雪崩的图,可以看出雪崩的特点:同时大量的积雪滑动
       做一个类比,从缓存中取值使用的是key,把key比作雪花,如果key失效了取不到值,就需要去DB或其他数据工具获取。如果大量key因为“失效时间”这个动作而失效,会导致大量的请求直接请求数据库,相当于没有缓存这一层,试想DB扛不住了整个服务不就“挂了”,你说“吓人”嘛!

雪崩
在这里插入图片描述

请求流程
在这里插入图片描述
跳过缓存
在这里插入图片描述

1.2 解决方案思考

       雪崩是大量的key在同一时间失效,导致查询不到缓存而直接请求数据库。那我们就从雪崩的特点出发。既然是因为缓存失效而导致,那是不是可以不让缓存失效(这不是废话吗!!!),貌似是避免了这个问题,但是又需要考虑缓存空间的问题(不然干嘛研究各种缓存失效算法,FIFO、LRU、LFU等),性价比不高,解决一个问题又引入另一问题。继续观察还可以发现是因为同一时间失效,那就让缓存不在同一时间失效不就可以了嘛。我们总结下:

方案1:不让缓存失效
不让缓存失效,直接导致的问题就是永远在内存中,占用空间不会得到释放,可是在我们的实际业务中,针对有些场景确实存在缓存不失效,只是在业务代码中更新缓存,如电商平台的APP首页。而且我们可以根据业务场景只针对热点型的key设置永久有效。

方案2:设置不同过期时间
让key分散的在不同时间失效,是不是就可以保证至少不会让数据裸奔。具体设置key时,可以在失效时间基础上加随机值:

setRedis(key, value, expireTime + Math.random());

其实,设置不同过期时间在一定概率下,有可能还是会出现大量的key在同一时间失效,但是业务场景往往也是有自己的运行规律的,大家也需要结合场景综合考虑设置过期时间,上面两种方案可以结合使用。

2 缓存穿透

2.1 何为缓存穿透

       使用缓存最直接的目的是为了较少DB文件系统IO,只通过网络IO,甚至使用本地缓存时无IO来操作数据。可是总是会存在一个恶意请求,或者系统设计开发未考虑到的情形导致查询一个压根不存在的key,那进而会查询数据库,可是数据库也不存在这个数据,反过来自然也无法缓存这个数据,这种情形称之为缓存穿透。此时,数据库就在裸奔,缓存起不到任何减轻DB的作用。试想大量的请求过来,服务器压力会很大而且一直在浪费计算资源。

2.2 解决方案思考

       同样我们从出问题的情形分析,发下如下特点:缓存没有该数据key、数据库也不存在该数据,也就是一直在请求空数据。问题很清晰了,那就兵来将挡水来土掩。既然一直在请求一个空数据,那我们就让数据存在。可是现实是为什么该数据却没有?我们是不是就要分辨下请求:是恶意请求,还是因为开发没考虑到。如果是前一个情形那就不该让请求进来,在网关风控环节就应该拦住,在处理方法前就判断请求参数的合法性。如果是后一个情形,那是不是考虑补全遗漏的场景。
       可是风控也不是万能的,总有一些神一样的高手存在,就像矛和盾任何一方没有绝对的胜利,那怎么办?总不能坐等服务被干翻吧。那就在缓存里刻意放置一些空数据,减轻我们的DB压力。缓存的出现一再刷新QPS的峰值,但是要知道redis也是有峰值限制的,单体可能是几万QPS,集群可能达到十万、百万级别。但是也不是永无止境的,总有上限。万一数据库保住了,缓存服务被搞挂了,哈哈也是一脸黑。总结上面的分析过程,提出下面几个方案:

方案1:拦截恶意请求
总有一些看不到的群里为了利益而不折手段,薅羊毛、恶意打压竞争对手等等。我们要保障好服务稳定运行。在架构的前端即流量入口端增加各种防范手段,拦截恶意请求。常见简单方案如,用户打标、IP地址过滤、参数校验、人工识别等,有技术和资金实力的会使用机器学习构建风控模型。这个需要根据公司的情况考虑自实现或借助云厂商。

方案2:补全遗漏场景
有时会存在考虑不周的情形,遗漏了某一场景或者人为失误,那就把原本缺失的数据补全。让缓存发挥作用,这个没什么好聊的。

方案3:设置空数据
上面也分析到风控不是万能的,甚至有的公司没有办法做自己的风控,但是还是要继续服务用户的。那就针对的性的去设置些空数据,让请求读取缓存后直接返回,不再走数据库这层。比如用户查询自己的订单列表,一般都会分页返回,如果只是做了page和pageSize的整数校验,很可能会被恶意请求钻了空子,传过来一个很大的正整数值然后不停的去请求,数据库也没有订单信息。那我们发现了就设置些空数据,不要再请求数据库了,而后再考虑拦截此类请求。当然如果是内部调用那就另说了。

方案4:Bloom Filter过滤查询key
设置空数据了,请求依然可以被处理,但是没有任何服务提升,当我们是地主家的傻儿子啊。让你浪费我们的服务器资源。毕竟缓存服务也是需要考虑高可用稳定性的,既然风控还没帮我们拦截住或者是因为公司内部调用API,也不能一刀切全拦截,那就别再一次次的读取缓存再做处理了,干脆直接判断你查询的key是不是存在,不存在直接返回,后面的逻辑都无需再执行。此时就可以用到redis提供的Bloom Filter(布隆过滤器)。

具体原理和使用可参考:布隆过滤器原理使用介绍

3 缓存击穿

3.1 何为缓存击穿

       击穿这个词语往往给人的感觉或者印象是,高速的物体穿透另一个物体,即击打穿透。如果巨量的请求需要读取一个热点key,在key失效的一瞬间,是不是大量的请求因为key失效直接打到数据库。可以把大量的请求比作这个高速的物体,把缓存给打穿了(缓存失效动作),直接到数据库了。

3.2 解决方案思考

       可想数据库此时要处理突如其来的大量请求,很有可能扛不住而服务宕机,进而引起连锁反应影响基于此库的所有服务。即使运维和DBA发现了重启服务,但是请求依然大量的涌进来也是于事无补,就像船漏了,使劲往外舀水也不解决根本问题。怎么办?分析这个问题的特点:大量的请求读取keykey失效了。分析过缓存雪崩后,我们是不是可以借鉴下。既然是热点key(有同学好奇不能是非热点key吗?大量的请求都要读取,那就成热点key了),那就别让失效了,毕竟经常读取。同样热点key也不是24小时都在读,也得考虑空间成本。
       又想让key不一直占用存储空间,又想服务稳定可用,那就还得继续正常处理请求。数据库为什么会挂掉,因为请求太多了,处理不过来了,同时又有大量的请求进来,各种资源都拉满处理。那我们就让请求少点进来,怎么控制呐?加锁!!!
       请求进来的少了,总不能让请求串行处理吧,以后谁还会使用咱们的产品。这不是自掘坟墓嘛。还得考虑让用户体验好点,不能让用户拿着手机看着白屏一脸茫然的等着。既然都限制了到数据库的请求了,依然还有很多那就,让请求别来了,在前边的流程里,直接给一个友好的提示或者让用户刷新一两次成功。也就是得想好降级的方案,再甚者直接熔断。

方案1:不让缓存失效或者拉长失效时间
既然是热点key,可以不失效或者拉长失效时间。毕竟不是所有的场景都是24小时不间断的有大量请求。在业务运行中可观察规律,请求处于低谷时考虑失效重新刷新缓存。

方案2:加锁
获取数据一般流程是先判断缓存是否存在,不存在读取数据库,设置缓存。那就在读取数据库这个环节增加锁,让一个请求进来读取后放到缓存,其余线程再读取缓存,没必要都要到数据库读取一次。

方案3:增加降级方案
再高端的配置,也是有天花顶的,请求多了也会吃不消。我们可以让一部分请求直接做简单后处理返回,也就是再业务增加降级方案,即正常流程不可走时,走备选方案。

4 总结

       总结以上三个问题,明白了几个问题含义,发现缓存不可用时的影响。所提出的方案也没有最完美的,需要相互配合一块使用。系统开发设计之初,可以在一些关键节点增加降级熔断方案,保证服务稳定。
       同理整个系统架构中其他环节也会出现各种问题,而各个环节存在直接或间接的依赖,进而会出现连锁反应,处理问题时就得把握好这个”平衡“。大家在处理问题时要考虑好,做事情也要想好备选方案以备不时之需。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值