• NoSQL之redis缓存雪崩、穿透、击穿概念解决办法


    目录

    一:缓存雪崩

    1.1概念理解

    1.2解决方案

    1.2.1数据预热

    1.2.2缓存层高可用

    1.2.3做二级缓存,或双缓存策略

    1.2.4缓存业务添加限流和服务降级策略

    1.3举例

    二:缓存穿透

    2.1概念理解

    2.2解决方案

    2.2.1缓存空值

    2.2.2布隆过滤器拦截

    2.2.3其它的方法

    2.3举例

    三:缓存击穿

    3.1概念理解

    3.2解决方案

    3.2.1分布式互斥锁

    3.2.2永不过期

    3.3俩种方案对比

    一:缓存雪崩

    1.1概念理解

    所谓的redis缓存雪崩指的是:redis中大量key集中过期或者redis服务器宕机,从而导致大量请求从数据库获取数据,导致数据库服务器访问压力过大,引起数据库压力造成查询堵塞甚至宕机。

    1.2解决方案

    1.2.1数据预热

    可以通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

    1.2.2缓存层高可用

    可以搭建redis集群,保证高可用;缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务。利用sentinel或cluster实现。

    1.2.3做二级缓存,或双缓存策略

    采用多级缓存,本地进程作为一级缓存,redis作为二级缓存,不同级别的缓存设置的超时时间不同,即使某级缓存过期了,也有其他级别缓存兜底,比如说nginx缓存。

    1.2.4缓存业务添加限流和服务降级策略

    在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
    业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
    SETNX,是(SET if Not eXists)的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。

    1.3举例

    国内比较知名的一个互联网公司,曾因为缓存事故,导致雪崩,后台系统全部崩溃,事故从当天下午持续到晚上凌晨 3~4 点,公司损失了几千万。

    缓存雪崩的事前事中事后的解决方案如下。

    事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。
    事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
    事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

    用户发送一个请求,系统 A 收到请求后,先查本地 ehcache 缓存,如果没查到再查 redis。如果 ehcache 和 redis 都没有,再查数据库,将数据库中的结果,写入 ehcache 和 redis 中。

    限流组件,可以设置每秒的请求,有多少能通过组件,剩余的未通过的请求,怎么办?走降级!可以返回一些默认的值,或者友情提示,或者空白的值。

    好处:

    数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。
    只要数据库不死,就是说,对用户来说,2/5 的请求都是可以被处理的。
    只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来一次。


    二:缓存穿透

    2.1概念理解

    缓存穿透是指查询一个根本不存在的数据,缓存层和持久层都不会命中。在日常工作中出于容错的考虑,如果从持久层查不到数据则不写入缓存层,缓存穿透将导致不存在的数据每次请求都要到持久层去查询,失去了缓存保护后端持久的意义。(对于系统A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。黑客发出的那 4000 个攻击,缓存中查不到,每次你去数据库里查,也查不到。)

    缓存穿透问题可能会使后端存储负载加大,由于很多后端持久层不具备高并发性,甚至可能造成后端存储宕机。通常可以在程序中统计总调用数、缓存层命中数、如果同一个Key的缓存命中率很低,可能就是出现了缓存穿透问题。
    造成缓存穿透的基本原因有两个:

    • 自身业务代码或者数据出现问题(例如:set 和 get 的key不一致)
    • 一些恶意攻击、爬虫等造成大量空命中(爬取线上商城商品数据,超大循环递增商品的ID) 

    2.2解决方案

    2.2.1缓存空值

    缓存空对象:是指在持久层没有命中的情况下,对key进行set (key,null)

    缓存空对象会有两个问题:

    • value为null 不代表不占用内存空间,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间,比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。
    • 缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。

    2.2.2布隆过滤器拦截

    布隆过滤器原理是一个二进制的数组,存储的是0或1,在添加元素时进行多次hash运算,得到多个0或1,存储到相应位置。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。

    在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截,当收到一个对key请求时先用布隆过滤器验证是key否存在,如果存在在进入缓存层、存储层。可以采用布隆过滤器,使用一个足够大的bitmap,用于存储可能访问的key,不存在的key直接被过滤;访问key未在DB查询到值,也将空值写进缓存,但可以设置较短过期时间。这种方法适用于数据命中不高、数据相对固定、实时性低的应用场景,代码维护较为复杂,但是缓存空间占用少。

    2.2.3其它的方法

    热点参数的限流降级
    进行权限校验
    做好数据格式的校验

    2.3举例

    数据库 id 是从 1 开始的,结果黑客发过来的请求 id 全部都是负数。这样的话,缓存中不会有,请求每次都“视缓存于无物”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。

    解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 set -999 UNKNOWN。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据


    三:缓存击穿

    3.1概念理解

    缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。

    3.2解决方案

    3.2.1分布式互斥锁

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

    3.2.2永不过期

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

    3.3俩种方案对比

    分布式互斥锁:这种方案思路比较简单,但是存在一定的隐患,如果在查询数据库 + 和 重建缓存(key失效后进行了大量的计算)时间过长,也可能会存在死锁和线程池阻塞的风险,高并发情景下吞吐量会大大降低!但是这种方法能够较好地降低后端存储负载,并在一致性上做得比较好。

    永远不过期:这种方案由于没有设置真正的过期时间,实际上已经不存在热点key产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。

  • 相关阅读:
    ArcGIS Pro实践技术应用、制图、空间分析、影像分析、三维建模、空间统计分析与建模、python融合
    【Mac】Ulysses for Mac(优秀的markdown写作软件) v34.3中文版安装教程
    后端Web开发之Maven
    LabVIEW车体静强度试验台测控系统
    赴日开发工程师是做什么的?
    工厂方法模式 创建型模式之四
    Vue精美简洁登录页
    入门力扣自学笔记164 C++ (题目编号:面试题 01.08)
    【Python_PySide2学习笔记(十八)】勾选按钮QCheckBox类的基本用法
    面试必考精华版Leetcode994. 腐烂的橘子
  • 原文地址:https://blog.csdn.net/ver_mouth__/article/details/126101750