学懂缓存雪崩,缓存击穿,缓存穿透仅需一篇,基于Redis讲解

news2024/11/22 14:38:51

在了解缓存雪崩、击穿、穿透这三个问题前,我们需要知道为什么我们需要缓存。在了解这三个问题后,我们也必须知道使用Redis时,如何解决这些问题。

所以我将按照"为什么我们需要缓存"、"什么是缓存雪崩、击穿、穿透"、"如何解决这些问题"三部分,带你学懂缓存雪崩、击穿、穿透。

为什么我们需要缓存

用户的数据一般都是存储于数据库,数据库的数据是落在磁盘上的,磁盘的读写速度可以说是计算机里最慢的硬件了。当用户的请求都访问数据库的话,可想而知,我们整个系统的并发量肯定是比较低的,而且如果一旦并发量上来了,数据库也很容易崩溃。

那我们可以怎么样来解决这个问题呢?

  • 我们可以多用几台机器,进行负载均衡,提高系统的并发量。

  • 也可以加一个中间层(缓存),避免用户直接访问数据库。

  • ......

很显然,使用缓存是比较简单且经济的方案。其实,在现在的服务端开发中,缓存中间件已经是我们所离不开的了。其中Redis就是比较著名的key-value内存数据库。故本文章基于Redis编写。

什么是缓存雪崩、击穿、穿透

缓存雪崩

通常我们为了保证缓存中的数据与数据库中的数据一致性,会给 Redis 里的数据设置过期时间,当缓存数据过期后,用户访问的数据如果不在缓存里,业务系统需要重新生成缓存,因此就会访问数据库,并将数据更新到 Redis 里,这样后续请求都可以直接命中缓存。

这样的流程乍一看很正确,没有任何问题。实际上隐藏着一些问题,这样做将可能导致有大量的key在同一时间失效,如果此时有大量的用户请求,那就会去访问数据库,从而导致数据库的压力骤增,如果数据库顶不住当前的压力,则会导致宕机,进而引起一系列问题,最后导致系统崩溃,这就是缓存雪崩。

引发缓存雪崩有以下几种可能:

  • 大量key同时失效
  • 充当缓存的Redis宕机了

缓存击穿

我们的业务通常会有几个数据会被频繁地访问,比如秒杀活动,这类被频地访问的数据被称为热点数据。如果热点key在某个时间过期了,此时大量请求会打到数据库上,数据库很容易被击穿,这就是缓存击穿。

实际上缓存击穿与缓存穿透都是key失效的问题,你也可以认为缓存击穿是缓存穿透的子集。

缓存穿透

缓存雪崩和击穿都是key失效或者Redis不可用的场景,缓存穿透与它们不同。缓存穿透是当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增。

发生的原因有:

  • 业务操作错误,缓存的数据或数据库的数据被删除,或者意外被用户访问到不存在的数据
  • 黑客恶意攻击

如何应对

缓存雪崩应对

缓存雪崩有两种诱因,不同诱因应对的策略是不同的。

针对大量数据同时过期:

  • 均匀设置过期时间
  • 互斥锁
  • 后台更新缓存
  • ......

1.均匀设置过期时间

目的是要避免大量数据设置成同时过期。给这些设置了过期时间的key加上一个随机数,让他们尽量不同时过期。尤其是在缓存预热的时候,更需要这样做。

2.互斥锁

当业务线程在处理用户请求时,如果发现访问的数据不在Redis里,就加一个互斥锁(setnx命令可以达到这个效果),保证同一个时间内只有一个请求来构建缓存(从数据库中读,再更新到Redis),构建完后释放锁,未能获取到锁的请求,要么等锁释放后重新读取缓存,要么返回空或默认值。

 

java

复制代码

public class RedisTool { private static final String LOCK_SUCCESS = "OK"; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; /** * 尝试获取分布式锁 * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识 * @param expireTime 超期时间 * @return 是否获取成功 */ public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) { String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) { return true; } return false; } }

第一个为key,我们使用key来当锁,因为key是唯一的。

第二个为value,我们传的是requestId,很多童鞋可能不明白,有key作为锁不就够了吗,为什么还要用到value?分布式锁有一个条件,谁上的锁就必须谁解开,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。requestId可以使用多种方法生成,只要能保证在一段时间内不重复。

第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;

第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。

第五个为time,与第四个参数相呼应,代表key的过期时间。

总的来说,执行上面的set()方法就只会导致两种结果:1. 当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。2. 已有锁存在,不做任何操作。

不过这种方法就要注意一下死锁问题,然后我代码这样写的原因是,只有一个操作,是原子性的,如果把加锁和设置过期时间分开,可能会发生一些意想不到的问题。

3.后台更新缓存

业务线程不再负责更新缓存,缓存也不设置有效期,而是让缓存“永久有效”,并将更新缓存的工作交由后台线程定时更新。虽然不设置有效期,但是其实有一个逻辑过期的标识,一旦业务线程发现这个缓存过期,就把它交给后台线程去处理。业务线程返回”过期值“。

这种方法适合用于对于缓存一致性要求不会特别严格的场景

针对Redis宕机:

  • 服务熔断或进行请求限流
  • 构建Redis主从或集群来保证可靠

1.服务熔断或进行请求限流

暂停业务,直接返回错误。等Redis恢复正常后,再允许业务进行。目的是保护数据库。

也可以启用限流,只允许少部分请求访问数据库,大于能承受的压力的请求直接拒绝服务。等到Redis正常且预热完毕,再解除限流。

2.构建Redis主从和哨兵或集群

主从能够分担主节点压力,有了哨兵的话,能够在Redis主节点故障时,即使切换主节点,避免Redis故障导致的缓存雪崩问题。

Redis集群(cluster)也是同理。

缓存击穿应对

上面提到缓存击穿是缓存雪崩的子集(数据过期导致的)

所以缓存击穿的解决方法与因为数据过期导致的雪崩基本一致,可以采用:

  • 互斥锁方案(与上面讲的相同)
  • 后台更新缓存方案(与上面讲的相同)

缓存穿透应对

应对缓存穿透的方案,常见的方案有三种:

  • 非法请求的限制
  • 缓存空值或者默认值
  • 使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在

1.非法请求的限制

当不用访问数据库就能知道请求的数据是否合法时,这个方式很合适。可以直接在API的入口处做判断,避免非法请求访问缓存和数据库

2.缓存空值

当我们线上业务发现缓存穿透的现象时,可以针对查询的数据,在缓存中设置一个空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,返回给应用,而不会继续查询数据库。

要注意缓存的空值必须设置合适的过期时间,太短则会导致缓存没有阻挡住大多数的非法请求,太长则会导致浪费内存空间。

3.使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在

我们可以在写入数据库数据时,使用布隆过滤器做个标记,然后在用户请求到来时,业务线程确认缓存失效后,可以通过查询布隆过滤器快速判断数据是否存在,如果不存在,就不用通过查询数据库来判断数据是否存在。

可能有部分童鞋不了解布隆过滤器,我简单的描述一下。

布隆过滤器由「初始值都为 0 的位图数组」和「 N 个哈希函数」两部分组成。当我们在写入数据库数据时,在布隆过滤器里做个标记(对N个哈希函数逐一进行使用,得到的结果对数组长度取余得到最后结果,并且把最后结果对应的下标值为1)。下次查询数据在不在数据库时,可以用相同的方法,如果得到N个位置的值都为1,则这个请求大概率是合法的(即使会有部分非法请求还是会访问到数据库,但是布隆过滤器已经过滤了绝大多数了)

总结


在网上找到的这张表格对上述内容进行了不错的总结。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/575651.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux学习笔记 --- Linux基础命令Part1

一. Linux的目录结构 目标:1. 掌握Linux系统的目录结构 2. 掌握Linux系统的路径表达形式 1.1 Linux系统的目录结构 我们知道,在操作系统中,文件系统的目录结构一般都是树形结构。Linux的目录结构是一个树型结构Windows 系统可以拥有多…

如何使用wireShark抓取还原文件

简介 WireShark的追踪流功能可以帮我们抓取从网络上下载的各种文件,接下来就演示下如何抓取并且进行还原。 使用Nginx搭建文件存储服务器 只要是通过http网站下载的包,都可以通过追踪流工具进行抓取。这里为了演示,临时搭建一个Nginx文件存…

Yolov8涨点技巧:MobileViTAttention助力小目标检测,涨点显著,MobileViT移动端轻量通用视觉transformer

在​crack缺陷检测项目原始0.739提升至 0.772 ,涨点明显,博主多个数据集亲测有效,实现暴力涨点; 现有博客都是将MobileViT作为backbone引入Yolov5,因此存在的问题点是训练显存要求巨大,本文引入自注意力的Vision Transformer(ViTs):MobileViTAttention 1. MobileViT介绍…

English Learning - L3 作业打卡 Lesson3 Day18 2023.5.22 周一

English Learning - L3 作业打卡 Lesson3 Day18 2023.5.22 周一 引言🍉句1: People said my mother was “a good egg”.成分划分弱读连读语调 🍉句2: She would always help anyone in need.成分划分连读语调 🍉句3: We never had to “walk…

1、MQTT协议入门以及mosquitto示例

1.什么是MQTT MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上&#xf…

一款好看的markdown编辑器:md-editor-v3

文章目录 md-eidtor-v3地址其它markdown编辑器🎄 md-editor-v3⭐️ 功能一览📦 安装💡 用法✍🏻 编辑器模式📖 仅预览模式 🗺 预览图🎁 Apis🔖 MdPreivew Props🔩 MdEdit…

c++基础概念,const与指针、引用的关系,auto,decltype关键字能干啥总得了解吧。总得按照需求自定义创建实体类,自己编写头文件吧

const限定符 有时我们希望定义这样一种变量,它的值不能被改变。例如,用一个变量来表示缓冲区的大小。使用变量的好处是当我们觉得缓冲区大小不再合适时,很容易对其进行调整。另一方面,也应随时警惕防止程序一不小心改变了这个值。…

邂逅Webpack和打包过程

1 认识webpack工具 2 webpack基本打包 3 webpack配置文件 4 编写和打包CSS文件 5 编写和打包LESS文件 6 postcss工具处理CSS 从脚手架打包成普通的htmlcssjs文件 内置模块path: 拼接路径 const path require("path")const filepath "C://abc/…

Systrace系列9 —— MainThread 和 RenderThread 解读

本文是介绍 Android App 中的 MainThread 和 RenderThread,也就是大家熟悉的主线程和渲染线程。文章会从 Systrace 的角度来看 MainThread 和 RenderThread 的工作流程,以及涉及到的相关知识:卡顿、软件渲染、掉帧计算等。 这里以滑动列表为例 ,我们截取主线程和渲染线程一…

InnoDB 在可重复读 RR 隔离级别下,能解决幻读问题吗?

InnoDB 在可重复读 RR 隔离级别下,能解决幻读问题吗? MySQL 在「可重复读」隔离级别下,可以很大程度上避免幻读现象的发生(注意是很大程度避免,并不是彻底避免),所以 MySQL 并不会使用「串行化」…

Pycharm中配置不了conda解释器

我安装的是pytorch的CPU版本,在Pycharm中配置conda环境时,每次添加完都不显示,搜遍了很多方法都没用。最后成功解决,这里将一些方法进行总结,方便大家解决问题。 我的情况和解决 问题情况以及显示 1.在Pycharm的日志…

GATK最佳实践之数据预处理SnakeMake流程

<~生~信~交~流~与~合~作~请~关~注~公~众~号生信探索> 写的数据预处理snakemake流程其实包括在每个单独的分析中比如种系遗传变异和肿瘤变异流程中&#xff0c;这里单独拿出来做演示用&#xff0c;因为数据预处理是通用的&#xff0c;在call变异之前需要处理好数据。 数据…

Java中如何使用策略模式减少 if / else 分支的使用

目录 1、策略模式 1.1 、策略模式包含三个角色&#xff1a; 2、需求 2.1 、传统方式 2.2 、策略模式实现 2.2.1 、新建PolicyPatternController.java 2.2.2 、Express.java(实体类) 2.2.3 、定义一个接口&#xff1a;PolicyPatternService.java 2.2.4 、定义3个实现类…

Python根据经纬度在地图上显示(folium)

Python根据经纬度在地图上显示&#xff08;folium&#xff09; 一、folium介绍1.folium.Map参数简要介绍2.folium.Marker参数介绍 二、Python根据经纬度在地图上显示&#xff08;示例&#xff09;1.经纬度坐标标记2.经纬度坐标分组标记 一、folium介绍 1.folium.Map参数简要介…

URLConnection(五)

文章目录 1. 断开与服务器的连接2. 处理服务器响应3. 错误条件4. 重定向5. 代理6. 流模式 1. 断开与服务器的连接 HTTP1.1 支持持久连接&#xff0c;允许通过一个TCP socket发送多个请求和响应。不过使用Keep-Alive时&#xff0c;服务器不会因为已经向客户端发送了最后一个字节…

chatgpt赋能python:使用Python操作股票软件:探索股票市场的新方式

使用Python操作股票软件&#xff1a;探索股票市场的新方式 在当今股票市场中&#xff0c;许多投资者正在寻找新的方式来更好地管理其投资组合。一种新的方式是使用Python编程语言操作股票软件。Python拥有简洁的语法和丰富的库来帮助投资者更好地理解和管理股票。在本文中&…

挂耳式耳机哪个牌子好?这次推荐准没错!

在校园里&#xff0c;上网课、复习考证、刷视频……耳机总是我的第一选择&#xff0c;既不会打扰别人又能有效学习&#xff0c;普通的入耳式耳机戴久了总是会耳道痛&#xff0c;甚至出现耳道发炎问题&#xff0c;耳机也总是很难清洁。但是生活中我又离不开耳机&#xff0c;所以…

【源码解析】分库分表框架 Shardingsphere 源码解析

前言 以前研究过如何使用ShardingJdbc&#xff0c;使用ShardingJdbc进行分库分表&#xff0c;但是原理方面没有细致的深入了解。如果仅仅了解如何使用的话&#xff0c;对于改造和排查问题&#xff0c;其实都是不够的&#xff0c;所以跟踪源码了解其运行原理是很重要的。 Demo…

GPT学习笔记-Embedding的降维与2D,3D可视化

嵌入&#xff08;Embedding&#xff09;在机器学习和自然语言处理中是一种表示离散变量&#xff08;如单词、句子或整个文档&#xff09;的方式&#xff0c;通常是作为高维向量或者矩阵。嵌入的目标是捕捉到输入数据中的语义信息&#xff0c;使得语义相近的元素在嵌入空间中的距…

STM32+UART串口+DMA收发

目录 1、cubemax端配置 1.1 初始化配置 1.2 GPIO配置 1.3 UART配置 1.3.1 串口基础配置 1.3.2 DMA配置 2、keil端代码设计 2.1 初始化配置 2.2 DMA接收初始化配置 2.3 DMA发送配置 2.4 接收回调函数设置 2.5 回调函数内容代码编写 2.5.1 接收回调函数 2.5.2 发送回调…