当前位置: 首页 > news >正文

REDIS中的缓存穿透,缓存击穿,缓存雪崩原因以及解决方案

  1. 需求引入

一般在项目的开发中,都是使用关系型数据库来进行数据的存储,通常不会存在什么高并发的情况,可是一旦涉及大数据量的需求,比如商品抢购,网页活动导致的主页访问量瞬间增大,单一使用关系型数据库来保存数据的系统会因为面向磁盘,导致磁盘读/写速度比较慢,一瞬间成千上万的请求到来,系统在极短的时间内完成成千上万次的读/写操作,这个时候数据库一般不能够承受,极其容易造成数据库系统瘫痪,最终导致服务宕机等严重性能弊端,为了解决上述问题,项目通常会引入NoSQL数据库,这是一种基于内存的数据库,并且提供一定的持久化功能,redis就是NoSQL数据库中的一种,可以将 Redis 作为数据库的缓存,用与缓解关系型数据库的压力,但是引入redis有可能会出现缓存穿透,缓存击穿,缓存雪崩等问题
  1. 缓存穿透

(1).什么是缓存穿透

缓存穿透是指查询一个缓存中和数据库中都不存在的数据,导致每次查询这条数据都会发生缓存缺失,从而透过缓存,直接访问数据库,发现数据库中也没有要访问的数据,最后返回空。如果用户使用这条不存在的数据疯狂发起请求,就会同时给缓存和数据库带来巨大压力,从而可能压垮数据库,导致数据库宕机

(2).产生缓存穿透的原因

业务层误操作:缓存中的数据和数据库中的数据都被误删除,所以缓存和数据库中都没有数据
恶意攻击:专门访问数据库中没有的数据

(3).解决方案

1).对请求参数进行校验

对于请求的参数应该要先进行校验,请求的参数应该要在规定范围内,过滤掉一些无效的请求,避免请求进入缓存或者数据库

2).采用布隆过滤器

使用布隆过滤器快速判断数据是否存在,避免从数据库中查询数据是否存在,减轻数据库压力

布隆过滤器,就是一种BitMap数据结构,将目前所有可以访问到的资源通过简单的映射关系放入到布隆过滤器中(哈希计算),当一个请求来临的时候先进行布隆过滤器的判断,如果有那么才进行放行,否则就直接拦截,它是由一个长度为m bit的位数组与n个hash函数组成的数据结构,位数组中每个元素的初始值都是0。在初始化布隆过滤器时,会先将所有key进行n次hash运算,这样就可以得到n个位置,然后将这n个位置上的元素改为1。这样,就相当于把所有的key保存到了布隆过滤器中了。

举个例子,比如我们一共有3个key,我们对这3个key分别进行3次hash运算,key1经过三次hash运算后的结果分别为2/6/10,那么就把布隆过滤器中下标为2/6/10的元素值更新为1,然后再分别对key2和key3做同样操作,结果如下图:

这样,当客户端查询时,也对查询的key做3次hash运算得到3个位置,然后看布隆过滤器中对应位置元素的值是否为1,如果所有对应位置元素的值都为1,就证明key在库中存在,则继续向下查询;如果3个位置中有任意一个位置的值不为1,那么就证明key在库中不存在,直接返回客户端空即可。如下图:

当客户端查询key4时,key4的3次hash运算中,有一个位置8的值为0,就说明key4在库中不存在,直接返回客户端空即可。

所以,布隆过滤器就相当于一个位于客户端与缓存层中间的拦截器一样,负责判断key是否在集合中存在。如下图:

布隆过滤器的好处就是解决了第一种缓存空值的不足,但布隆过滤器也存在缺陷它不能完全保证请求过来的 key ,通过布隆过滤器的校验,就一定有这个数据。 但是,只要没有通过布隆过滤器的校验,那么这个 key 就一定不存在。 其实这样就已经可以过滤掉大部分不存在的 key 请求了。

如果布隆过滤器的哈希槽过短,很有可能导致大部分的位置都为 1 ,那么此时,布隆过滤器就失去了它的意义。 所以,当我们发现布隆过滤器大部分位置都为1了,就要扩宽哈希槽

3).对空值进行缓存

一旦发生缓存穿透,当查询返回的数据为空(不管是数据不存在,还是系统故障)的时候,在Redis缓存一个空值或者业务层协商确定的缺省值,然后给这个空对象的缓存设置一个很短的过期时间,最长不超过五分钟,应用发生后续请求在进行查询时,就可以直接从缓存中拿到返回给业务应用,避免大量请求发送给数据库处理,保持数据库的正常运行

这种解决方式有两个缺点:(1)需要缓存层提供更多的内存空间来缓存这些空对象,当这种空对象很多的时候,就会浪费更多的内存;(2)会导致缓存层和存储层的数据不一致,即使在缓存空对象时给它设置了一个很短的过期时间,那也会导致这一段时间内的数据不一致问题

//伪代码
public function GetShopList() {var cache_time = 60;var cache_key = "shop_list";var cache_value = Cache::Get(cacheKey);if (cache_value != null) {return cache_value;} else {//数据库查询不到,为空cache_value = GetShopListData();if (cache_value == null) {//如果发现为空,设置个默认值,也缓存起来cache_value = null;}//增加缓存Cache::Add(cache_key, cache_value, cache_time);return cache_value;}
}

(4).注意事项:

1).使用空值作为缓存的时候,key设置的过期时间不能太长,防止占用太多redis资源
2).使用空值作为缓存只能防止黑客重复使用相同的id暴力攻击,但是如果黑客使用动态的无效id攻击就没有效果(需要配合网警)
3).使用布隆过滤器可能会存在哈希冲突
  1. 缓存击穿

(1).什么是缓存击穿

针对某个访问非常频繁的热点数据请求,突然在缓存中失效(数据过期),在该热点数据重新载入缓存之前,有大量的访问该数据请求穿过缓存,一下子都发送到后端数据库,导致数据库压力激增影响数据库处理其他请求,造成大量请求阻塞,这个时候大并发的请求可能会瞬间把后端DB压垮。缓存击穿一般是热点 key 在 Redis 中过期了导致的,最直接的方法就是,对于热点 key ,不设置过期时间

(2).解决方案

1).第一种是设置key永不过期

a.在设置热点key的时候,不给key设置过期时间即可
b.正常给key设置过期时间,不过在后台同时启一个定时任务去定时地更新这个缓存
c.提前对热点数据进行设置:类似于新闻、某博等软件都需要对热点数据进行预先设置在redis中
d.监控数据,适时调整:监控哪些数据是热门数据,实时的调整key的过期时长

2).第二种是使用分布式锁

锁的对象就是key,当大量查询同一个key的请求并发进来时,保证同一时刻只能有一个请求获取到锁,然后获取到锁的线程查询数据库,然后将结果放入到缓存中,这样,其他的线程只需等待该线程运行完毕,即可重新从Redis中获取数据
业界比较常用的做法,是使用mutex,简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去加载请求数据库,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行加载请求数据库的操作并回设缓存;否则,就重试整个get缓存的方法
SETNX,是SET if Not eXists 的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果
public function get(key) {var value = redis.get(key);if (value == null) { //代表缓存值过期//设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能请求加载数据库if (redis.setnx(key_mutex, 1, 3 * 60) == 1) {  //代表设置成功value = db.get(key);redis.set(key, value, expire_secs);redis.del(key_mutex);} else {  //这个时候代表同时候的其他线程已经请求加载数据库并回设到缓存了,这时候重试获取缓存值即可sleep(50);get(key);  //重试}} else {return value;      }}

4.缓存雪崩

(1).什么是缓存雪崩

与缓存击穿的区别在于缓存雪崩针对的是很多key缓存,缓存击穿则是某一个key,缓存正常从Redis中获取示意图:
缓存失效瞬间示意图:
缓存雪崩指的是,缓存中有大量的key在同一时刻过期,或者Redis直接宕机了,然后大量请求发送到了数据库,导致数据库的压力激增,甚至可能导致数据库崩溃,从而导致整个系统崩溃,引发雪崩一样的连锁效应

(2).产生缓存雪崩的原因

1).缓存中大量 key 同时过期

当redis中的大量key集体过期,可以理解为redis中的大部分数据都被清空了(失效了),那么这时候如果有大量并发的请求来到,那么redis就无法进行有效的响应(命中率急剧下降),请求就直接去数据库访问了,可能导致素剧看直接崩溃

2).Redis 实例挂掉了,无法处理请求

(3).解决方案

a.将失效时间分散开:
在实际应用中应当避免大量 key 同时过期的场景。如果确实有这种业务场景,可以微调这批 key 过期的时间,使其能有一定的相差间隔,防止集体过期
b.使用多级架构:
使用nginx缓存+redis缓存+其他缓存,不同层使用不同的缓存,可靠性更强,Redis 主从集群其实可以比较好地实现主 Redis 实例挂掉后,能有其他从库快速切换为主库,继续提供服务
c.设置缓存标记:
记录缓存数据是否过期,如果过期会触发通知另外的线程在后台去跟新实际的key
d.使用锁或者队列的方式:
如果查不到就加上排它锁,其他请求只能进行等待

以上都是预防的措施,如果已经发生了 缓存雪崩,为了防止数据库被大量的请求搞崩溃,可以采用 服务熔断 或者 请求限流
服务熔断就是暂停对业务提供 Redis 服务,直到 Redis 恢复正常,再向外提供服务。 当然,这种情况下,业务也会整个停摆了。
另外一种比较温和的办法就是请求限流。请求限流顾名思义,就是限制请求的流量,随机丢弃一部分的请求,以保证不会同时有太多请求压入数据库

除了上面的解决方式,还可以使用其他策略,比如设置key永不过期、加分布式锁等

缓存失效时的雪崩效应对底层系统的冲击非常可怕!大多数系统设计者考虑用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件

加锁排队,伪代码如下


public function GetList() {$cache_time = 30;$cache_key = "product_list";$lock_key = $cache_key;$cache_value = Cache::get($cache_key);if ($cache_value != null) {return $cache_value;} else {synchronized($lock_key) {$cache_value = Cache::get($cache_key);if ($cache_value != null) {return $cache_value;} else {//这里一般是sql查询数据$cache_value = GetList(); Cache::Add($cache_key, $cache_value, $cache_time);}}return $cache_value;}
}
加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。假设在高并发下,缓存重建期间key是锁着的,这是过来1000个请求999个都在阻塞的。同样会导致用户等待超时,这是个治标不治本的方法!
注意:加锁排队的解决方式分布式环境的并发问题,有可能还要解决分布式锁的问题;线程还会被阻塞,用户体验很差!因此,在真正的高并发场景下很少使用!

随机值伪代码

//伪代码
public object GetProductListNew() {$cacheTime = 30;$cacheKey = "product_list";//缓存标记$cacheSign = cacheKey + "_sign";$sign = Cache::Get($cacheSign);//获取缓存值$cacheValue = Cache::Get($cacheKey);if ($sign != null) {return $cacheValue; //未过期,直接返回} else {Cache::Add($cacheSign, "1", $cacheTime);ThreadPool.QueueUserWorkItem((arg) -> {//这里一般是 sql查询数据$cacheValue = GeList(); //日期设缓存时间的2倍,用于脏读CacheHelper.Add($cacheKey, $cacheValue, $cacheTime * 2);                 });return $cacheValue;}
} 

(4).Redis实例发生宕机产生缓存雪崩

1. 业务系统中实现服务熔断或者请求限流机制

服务熔断:发生缓存雪崩时,为了防止发生连锁的数据库雪崩,甚至整个系统崩溃,暂停业务应用对缓存系统的接口访问。

具体实现:业务调用缓存接口时,缓存客户端不再请求Redis,而是直接返回,等到Redis缓存实例重新恢复服务以后,在允许应用请求发送到缓存系统。

优点:避免了大量请求因缓存缺失,而积压到数据库系统,保证了数据库系统的正常运行。

2.事先预防

搭建主从节点构建Redis缓存高可用集群

当Redis缓存的主节点故障宕机了,从节点切换为主节点,继续提供缓存服务避免缓存实例宕机造成的缓存雪崩问题

相关文章:

REDIS中的缓存穿透,缓存击穿,缓存雪崩原因以及解决方案

需求引入一般在项目的开发中,都是使用关系型数据库来进行数据的存储,通常不会存在什么高并发的情况,可是一旦涉及大数据量的需求,比如商品抢购,网页活动导致的主页访问量瞬间增大,单一使用关系型数据库来保存数据的系统…...

数据库及缓存之MySQL(一)

思维导图 常见知识点 1.mysql存储引擎: 2.innodb与myisam区别: 3.表设计字段选择: 4.mysql的varchar(M)最多存储数据: 5.事务基本特性: 6.事务并发引发问题: 7.mysql索引: 8.三星索引&#xf…...

项目管理中,项目经理需要具备哪些能力?

项目经理是团队的领导者,是带领项目团队对项目进行策划、执行,完成项目目标,对于项目经理来说,想要有序推进项目,使项目更成功,光有理论知识是不够的,也要具备这些能力: 1、分清主…...

itk中的一些图像处理

文章目录1.BinomialBlurImageFilter计算每个维度上的最近邻居平均值2.高斯平滑3.图像的高阶导数 RecursiveGaussianImageFilter4.均值滤波5.中值滤波6.离散高斯平滑7.曲率驱动流去噪图像 CurvatureFlowImageFilter8.由参数alpha和beta控制的幂律自适应直方图均衡化9.Canny 边缘…...

Endless lseek导致的SQL异常

最近碰到同事咨询的一个问题&#xff0c;在执行一个函数时&#xff0c;发现会一直卡在那里。 strace抓了下发现会话一直在执行lseek&#xff0c;大致情况如下&#xff1a; 16:13:55.451832 lseek(33, 0, SEEK_END) 1368064 <0.000037> 16:13:55.477216 lseek(33, 0, SE…...

JUC-day01

JUC-day01 什么是JUC线程的状态: wait sleep关键字:同步锁 原理(重点)Lock接口: ReentrantLock(可重入锁)—>AQS CAS线程之间的通讯 1 什么是JUC 1.1 JUC简介 在Java中&#xff0c;线程部分是一个重点&#xff0c;本篇文章说的JUC也是关于线程的。JUC就是java.util .con…...

Mind+Python+Mediapipe项目——AI健身之跳绳

原文&#xff1a;MindPythonMediapipe项目——AI健身之跳绳 - DF创客社区 - 分享创造的喜悦 【项目背景】跳绳是一个很好的健身项目&#xff0c;为了获知所跳个数&#xff0c;有的跳绳上会有计数器。但这也只能跳完这后看到&#xff0c;能不能在跳的过程中就能看到&#xff0c;…...

数据库概述

20世纪60年代后期&#xff0c;就出现了数据库技术。取得成就如下&#xff1a;造就了四位图灵奖得主发展成为以数据建模和DBMS核心技术为主&#xff0c;内容丰富的一门学科。带动了一个巨大的软件产业-DBMS产品及其相关工具和解决方案。四个基本概念数据数据是数据库中存储的基本…...

【已解决】解决IDEA的maven刷新依赖时出现Connot reconnect错误

前言 小编我将用CSDN记录软件开发求学之路上亲身所得与所学的心得与知识&#xff0c;有兴趣的小伙伴可以关注一下&#xff01;也许一个人独行&#xff0c;可以走的很快&#xff0c;但是一群人结伴而行&#xff0c;才能走的更远&#xff01;让我们在成长的道路上互相学习&#…...

动态链接库(.so)文件的变编译和引用、执行

动态链接库(.so)文件的变编译和引用 动态链接库&#xff1a;SO&#xff08;Shared Object&#xff09;是一种动态链接库&#xff0c;也被称为共享库。它是一种可被多个程序共享使用的二进制代码库&#xff0c;其中包含已编译的函数和代码。与静态链接库不同&#xff0c;动态链接…...

linux(centos8)文件解压命令

linux解压命令tar 解压命令常用解压命令1 [.tar] 文件 解压到当前文件夹2 [.tar.gz] 文件 解压到当前文件夹3 [.tar] 解压到指定文件夹 -C 必须是大写unzip 解压命令常用解压命令1 [.zip]解压到当前文件夹2 [.zip] 解压到指定文件夹2 [.zip] 解压到指定文件夹&#xff08;强行覆…...

阅读笔记6——通道混洗

一、逐点卷积 当前先进的轻量化网络大都使用深度可分离卷积或组卷积&#xff0c;以降低网络的计算量&#xff0c;但这两种操作都无法改变特征图的通道数&#xff0c;因此需要使用11的卷积。总体来说&#xff0c;逐点的11卷积有如下两点特性&#xff1a; 可以促进通道之间的信息…...

上海亚商投顾:沪指失守3300点 卫星导航概念全天强势

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。市场情绪指数早间低开后震荡回升&#xff0c;沪指盘中一度翻红&#xff0c;随后又再度走低&#xff0c;创业板指午后跌近1%。…...

疯狂的SOVA:Android银行木马“新标杆”

2021年8月初&#xff0c;一款针对Android银行APP的恶意软件出现在人们的视野中&#xff0c;ThreatFabric 安全研究人员首次发现了这一木马&#xff0c;在其C2服务器的登录面板&#xff0c;研究人员发现&#xff0c;攻击者将其称之为SOVA。 ** SO** ** V** ** A简介** 在俄语中…...

汽车零部件企业数字工厂管理系统建设方案

在汽车零部件制造领域&#xff0c;伴随工业信息化与机器人化&#xff0c;制造模式逐渐从 CAD/CAE/CAM 数字化设计及加工走向全产品周期虚拟现实的数字化工厂管理系统平台&#xff0c;实现虚拟现实设计制造&#xff0c;防范产品缺陷并预防设备故障&#xff0c;大幅提高生产效率。…...

【线程同步工具】Semaphore源码解析

控制对资源的一个或多个副本的并发访问 Java API 提供了一种信号量机制 Semaphore。 一个信号量就是一个计数器&#xff0c; 可用于保护对一个或多个共享资源的访问。 当一个线程要访问多个共享资源中的一个时&#xff0c;它首先需要获得一个信号量。如果信号量内部的计数器的…...

获取实时天气

一、用天气API&#xff08;需要付费&#xff09; 网址&#xff1a;https://www.tianqiapi.com/请求方式及url&#xff1a;请求方式&#xff1a;GET接口地址&#xff1a;https://tianqiapi.com/free/day请求示例https://www.tianqiapi.com/free/day?appid_____&appsecret__…...

【数据库】redis数据持久化

目录 数据持久化 一&#xff0c; RDB 1&#xff0c; 什么是RDB 2&#xff0c;持久化流程 3&#xff0c; 相关配置 案例演示&#xff1a; 4&#xff0c; 备份和恢复 1、备份 2、恢复 3&#xff0c;优势 4&#xff0c; 劣势 二&#xff0c;AOF 1&#xff0c;什么是A…...

前端编译、JIT编译、AOT编译

一、前端编译&#xff1a;java设计之初就是强调跨平台&#xff0c;通过javac将源文件编译成于平台无关的class文件&#xff0c; 它定义了执行 Java 程序所需的所有信息&#xff08;许多Java"语法糖"&#xff0c;是在这个阶段完成的&#xff0c;不依赖虚拟机&#xff…...

父子组件中,子组件调用父组件的方法

父子组件中&#xff0c;子组件调用父组件的方法 方法一&#xff1a;直接在子组件中通过this.$parent.event来调用父组件的方法 父组件 <template><p><child>父组件</child></p> </template> <script>import child from ~/compone…...

第七章.深度学习

第七章.深度学习 7.1 深度学习 深度学习是加深了层的深度神经网络。 1.加深层的好处 1).可以减少网络的参数数量 5*5的卷积运算示例&#xff1a; 重复两次3*3的卷积层示例&#xff1a; 图像说明&#xff1a; ①.一次5 * 5的卷积运算的区域可以由两次3 * 3的卷积运算抵消&a…...

小学生学Arduino---------点阵(三)动态的显示与清除

学习目标&#xff1a; 1、理解“整数值”的概念与使用 2、理解“N1”指令的意义 3、掌握“反复执行多次”指令的使用 4、掌握屏幕模块的清除功能指令 5、理解“反复执行”指令与“反复执行多次”指令的嵌套使用 6、搭建电路图 7、编写程序 效果&#xff1a; 整数包括&#xf…...

opencv图片处理

目录1 图片处理1.1 显示图片1.2 旋转图片1.3 合并图片1.4、Mat类1.4.1、像素的储存结构1.4.2、访问像素数据1.6、rgb转灰度图1.7、二值化1.8、对比度和亮度1.9、图片缩放1.9.1、resize临近点算法双线性内插值1.9.2、金字塔缩放1.10、图片叠加1 图片处理 1.1 显示图片 #includ…...

C++ Primer Plus 学习笔记(二)—— 复合类型

数组 当我们只是定义了数组&#xff0c;而没有对数组进行初始化时&#xff0c;那数组的值将是未定义的。 在对数组进行初始化时&#xff0c;如果只对数组的一部分进行初始化&#xff0c;编译器会将把其他元素自动设置为0。 #include <iostream>using namespace std;in…...

代码随想录算法训练营第七天 | 454.四数相加II 、 383. 赎金信、15. 三数之和、18. 四数之和 、总结

打卡第七天&#xff0c;还是哈希表。 今日任务 454.四数相加II383.赎金信15.三数之和18.四数之和总结 454.四数相加II 代码随想录 class Solution { public:int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, ve…...

apply函数族

apply函数族 apply函数族是R语言中帮助用户实现高效的向量化运算的一系列函数&#xff0c;包括apply,lapply,sapply,vapply等。 apply() apply函数以列或行为单位进行循环操作&#xff0c;可以处理matrix、array数据&#xff0c;返回一个向量或matrix。 apply(data,1/2,fuc…...

读书笔记可读性素材

《深入理解Java虚拟机》 《深入理解Java虚拟机》 《深入理解Java虚拟机》 本地方法栈&#xff08;Native Method Stacks&#xff09; 本地方法栈&#xff08;Native Method Stacks&#xff09; 本地方法栈&#xff08;Native Method Stacks&#xff09; -----------------…...

【C++】vector 模拟实现

vectorvector 容器vector 基本使用vector 定义库中各类接口的使用迭代器容量相关接口元素访问相关接口元素修改相关接口模拟实现 vector前期准备构造与析构赋值运算符重载迭代器相关容量相关元素访问相关元素的修改相关二维数组的创建对于自定义类型数据的测试vector 容器 C S…...

canvas初体验

canvas介绍 Canvas 最初由Apple于2004 年引入&#xff0c;用于Mac OS X WebKit组件&#xff0c;为仪表板小部件和Safari浏览器等应用程序提供支持。后来&#xff0c;它被Gecko内核的浏览器&#xff08;尤其是Mozilla Firefox&#xff09;&#xff0c;Opera和Chrome实现&#x…...

JavaWeb12-线程通讯(线程等待和唤醒)

目录 1.方法介绍 1.1.wait()/wait(long timeout)&#xff1a;让当前线程进入等待状态。 1.1.1.wait执行流程 1.1.2.wait结束等待的条件 1.1.3.wait() VS wait(long timeout) 1.1.4.为什么wait要放在Object中&#xff1f; --->PS&#xff1a;wait(0) 和 sleep(0) 的区…...