Redis 实战 - 缓存异常及解决方案

在这里插入图片描述

文章目录

  • 概述
  • 一、缓存穿透
    • 1.1 缓存穿透是什么
    • 1.2 解决方案
  • 二、缓存击穿
    • 2.1 缓存击穿是什么
    • 2.2 解决方案
  • 三、缓存雪崩
    • 3.1 缓存雪崩是什么
    • 3.2 解决方案
  • 四、拓展
    • 4.1 缓存预热
    • 4.2 缓存降级
  • 五、结语

把今天最好的表现当作明天最新的起点…….~

概述

  在实际的业务场景中,Redis 一般和其他数据库搭配使用,比如和关系型数据库 MySQL 配合使用,用来减轻后端数据库的压力。Redis 会把 MySQL 中经常被查询的数据缓存起来,比如热点数据,这样当用户来访问的时候,就不需要到 MySQL 中去查询,而是直接获取 Redis 中的缓存数据,从而降低了后端数据库的读取压力。如果说用户查询的数据在 Redis 没有找到,那么用户的查询请求就会被转到 MySQL 数据库。当 MySQL 将查询到的数据返回给客户端时,同时也会将数据缓存到 Redis 中,这样用户再次读取时,就可以直接从 Redis 中获取数据。流程图如下所示:
在这里插入图片描述
  在使用 Redis 作为缓存数据库的过程中,有时也会遇到一些棘手问题,比如常见缓存穿透、缓存击穿和缓存雪崩等问题,如下图所示。本文中将对这些问题做简单地说明,并且提供有效的解决方案。

Redis 缓存异常
缓存穿透
缓存击穿
缓存雪崩

一、缓存穿透

1.1 缓存穿透是什么

  一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,那么就去数据库去查找。当用户查询某个数据时,Redis 中不存在该数据,也就是缓存没有命中,此时查询请求就会转向数据库,结果发现数据库中也不存在该数据,数据库只能返回一个空对象(相当于进行了两次无用的查询)。用户拿不到数据时,就会一直发请求查询数据库,这样会对数据库的访问造成很大的压力。如果这种类请求非常多,或者用户利用这种请求进行恶意攻击,就会给数据库造成很大压力,甚至于崩溃,这种现象就叫缓存穿透。
在这里插入图片描述

  这种现象的原因其实很好理解,当客户端访问不存在的数据时,先请求 Redis,但是此时 Redis 中并没有数据,此时会访问到数据库,但是数据库中也没有数据,这个数据穿透了缓存,直击数据库。我们都知道数据库能够承载的并发不如 Redis 这么高,如果大量的请求同时过来访问这种不存在的数据,这些请求就都会访问到数据库。

1.2 解决方案

  简单的解决方案就是当Redis、数据库中都没有值返回空对象时, 可以在 Redis 中存放一个空值,同时为其设置一个过期时间。这样,当用户再次发起相同请求访问这个不存在的数据,那么就会从缓存中拿到一个空对象,用户的请求被阻断在了缓存层。这样就可以减少重复查询空值引起的系统压力增大,从而从而保护了后端数据库。示例代码如下:

private String queryMessager(String key){// 从缓存中获取数据String message = getFromCache(key);// 如果缓存中没有 从数据库中查找if(StringUtils.isBlank(message)){message = getFromDb(key);// 如果数据库中也没有数据 就设置短时间的缓存if(StringUtils.isBlank(message)){// 设置缓存时间(缓存的key,缓存的值,失效时间:单位秒)redisClient.setNxEx(key,null,60);} else {redisClient.setNxEx(key,message,1800);}}return message;
}

  这种做法虽然优化了缓存穿透问题,但也存在一些问题。虽然请求进不了数据库,但是会占用 Redis 的缓存空间。而大量的空缓存导致资源的浪费,也有可能导致 Redis 和数据库中的数据不一致。

二、缓存击穿

2.1 缓存击穿是什么

  我们的业务通常会有几个数据会被频繁地访问,比如秒杀活动,这类被频地访问的数据被称为热点数据。比如某个热点数据,它无时无刻都在接受大量的并发访问,如果在某一时刻忽然过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,导致大量的并发请求直接访问数据库,就像在一个完好无损的桶上凿开了一个洞,引起数据库压力瞬间增大,这种现象被称为缓存击穿。
在这里插入图片描述

  缓存击穿一般出现在高并发系统中,是大量并发用户同时请求到缓存中没有但数据库中有的数据,也就是同时读缓存没读到数据,又同时去数据库去取数据。由于请大量请求同时过来,来不及更新缓存就全部打到数据库那边,引起数据库压力瞬间增大。

2.2 解决方案

  • 将热点数据设置加上互斥锁
    • 此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。当第一个数据库查询请求发起后,就将缓存中该数据上锁;此时到达缓存的其他查询请求将无法查询该字段,从而被阻塞等待;当第一个请求完成数据库查询,并将数据更新值缓存后,释放锁;此时其他被阻塞的查询请求将可以直接从缓存中查到该数据。

      private ReentrantLock reentrantLock = new ReentrantLock();
      public static String getData(String key) throws InterruptedException {// 从 Redis 查询数据String result = getDataByKey(key);// 参数校验if (StringUtils.isBlank(result)) {// 获取锁if (reentrantLock.tryLock()) {// 去数据库查询result = getDataByDB(key);// 校验if (StringUtils.isNotBlank(result)) {// 搞进缓存setDataToKey(key, result);}// 释放锁,正常会在finally中释放reentrantLock.unlock();} else {// 稍等一下Thread.sleep(100L);result = getData(key);}}return result;
      }
      
    • 当某一个热点数据失效后,只有第一个数据库查询请求发往数据库,其余所有的查询请求均被阻塞,从而保护了数据库。但是,由于采用了互斥锁,其他请求将会阻塞等待,可能会存在死锁和线程池阻塞的风险,此时系统的吞吐量将会下降,这需要结合实际的业务考虑是否允许这么做。

  • 将热点数据设置为永远不过期
    • 当向缓存中存储这些数据的时候,可以将他们的缓存失效时间错开,这样能够避免同时失效。如在一个基础时间上加/减一个随机数,从而将这些缓存的失效时间错开。

      private void setRandomTimeForReidsKey(String redisKey,String value){//随机函数Random rand = new Random();//随机获取30分钟内(30*60)的随机数int times = rand.nextInt(1800);//设置缓存时间(缓存的key,缓存的值,失效时间:单位秒)redisClient.setNxEx(redisKey,value,times);
      }
      
    • 这种方案由于没有设置真正的过期时间,实际上已经不存在热点key产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。

三、缓存雪崩

3.1 缓存雪崩是什么

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

应用 Redis 数据库 从缓存读取数据 缓存过期 缓存不存在 从数据库读取数据 返回数据库中的数据 将数据加载到缓存 应用 Redis 数据库

  但当Redis 故障宕机或者缓存中大批量的数据同一时间过期(失效),而此时数据访问量又非常大,无法在 Redis 中处理,于是全部直接访问数据库,从而导致数据库压力突然暴增,严重时甚至可能导致数据库崩溃。就像雪崩一样,引发一系列连锁效应,从而波及整个系统崩溃,这种现象被称为缓存雪崩。如下图所示:
在这里插入图片描述

  假设当时每秒6000个请求,本来缓存在可以扛住每秒5000个请求,但是缓存当时所有的Key都失效了。此时1秒6000个请求全部落数据库,数据库必然扛不住,可能DBA都没反应过来就直接挂了,即便是重启数据库,但是数据库立马又被新的流量给打死了。以秒杀系统为例,图示说明:
在这里插入图片描述

  它和缓存击穿不同,缓存击穿是在并发量特别大时,某一个热点 key 突然过期,而缓存雪崩则是大量的 key 同时过期,因此它们根本不是一个量级。

3.2 解决方案

  出现上述情况的常见原因主要有以下两点:

  • 大量缓存数据同时过期,导致本应请求到缓存的需重新从数据库中获取数据。
  • Redis 本身出现故障,无法处理请求,那自然会再请求到数据库那里。

  针对上面出现故障的情况,可以从以下几点出发解决:

  • 事前:构建高可用的集群,实现主 Redis 实例挂掉后,能有其他从库快速切换为主库,继续提供服务,避免全盘崩溃。
  • 事中:在往 Redis 存数据时,可以通过随机、微调、均匀设置等方式设置过期时间,这样可以保证数据不会在同一时间大面积失效。如果事情已经发生了,那就要为了防止数据库被大量的请求搞崩溃,可以采用服务熔断或者请求限流的方法。当然服务熔断相对粗暴一些,停止服务直到redis服务恢复;而请求限流相对温和一些,保证一些请求可以处理,不过还是看具体业务情况选择合适的处理方案。
  • 事后:redis持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

四、拓展

4.1 缓存预热

  缓存预热就是系统上线前后,将相关的缓存数据直接加载到缓存系统中去,而不依赖用户。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题。用户直接查询事先被预热的缓存数据,这样可以避免那么系统上线初期,对于高并发的流量,都会访问到数据库中, 对数据库造成流量的压力。根据数据不同量级,可以有以下几种做法:

  • 数据量不大:项目启动的时候自动进行加载。
  • 数据量较大:后台定时刷新缓存。
  • 数据量极大:只针对热点数据进行预加载缓存操作。

4.2 缓存降级

  缓存降级是指当缓存失效或缓存服务出现问题时,为了防止缓存服务故障,导致数据库跟着一起发生雪崩问题,所以也不去访问数据库,但因为一些原因,仍然想要保证服务还是基本可用的,虽然肯定会是有损服务。因此,对于不重要的缓存数据,我们可以采取服务降级策略。一般做法有以下两种:

  • 直接访问内存部分的数据缓存。
  • 直接返回系统设置的默认值。

五、结语

  Redis 缓存异常会面临的三个问题:缓存雪崩、击穿和穿透。其中,缓存雪崩和缓存击穿主要原因是数据不在缓存中,而导致大量请求访问了数据库,数据库压力骤增,容易引发一系列连锁反应,导致系统奔溃。不过,一旦数据被重新加载回缓存,应用又可以从缓存快速读取数据,不再继续访问数据库,数据库的压力也会瞬间降下来。因此,缓存雪崩和缓存击穿应对的方案比较类似。而缓存穿透主要原因是数据既不在缓存也不在数据库中。因此,缓存穿透与缓存雪崩、击穿应对的方案不太一样。
  Redis 缓存在互联网中至关重要,可以很大的提升系统效率。 本文介绍的缓存异常以及解决思路有可能不够全面,但也提供相应的解决思路和代码大体实现,希望可以为大家提供一些遇到缓存问题时的解决思路。如果有不足的地方,也请帮忙指出,大家共同进步。
在这里插入图片描述

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

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

相关文章

LeetCode 第131场双周赛个人题解

100309. 求出出现两次数字的 XOR 值 原题链接 求出出现两次数字的 XOR 值 - 力扣 (LeetCode) 竞赛 思路分析 签到题,一次遍历 AC代码 class Solution:def duplicateNumbersXOR(self, nums: List[int]) -> int:cnt Counter(nums)res 0st set(nums)for x …

【大模型】Spring AI对接ChatGpt使用详解

目录 一、前言 二、spring ai介绍 2.1 什么是Spring AI 2.2 Spring AI 特点 2.3 Spring AI 为开发带来的便利 2.4 Spring AI应用领域 2.4.1 聊天模型 2.4.2 文本到图像模型 2.4.3 音频转文本 2.4.4 嵌入大模型使用 2.4.5 矢量数据库支持 2.4.6 用于数据工程ETL框架 …

2024-05-22 VS2022使用modules

点击 <C 语言编程核心突破> 快速C语言入门 VS2022使用modules 前言一、准备二、使用其一, 用VS installer 安装模块:第二个选项就是, 与你的代码一同编译std模块, 这个非常简单, 但是也有坑. 总结 前言 要解决问题: 使用VS2022开启modules. 想到的思路: 跟着官方文档整…

Java进阶学习笔记19——内部类

1、 内部类&#xff1a; 是类中五大成分之一&#xff08;成员变量、方法、构造函数、内部类、代码块&#xff09;&#xff0c;如果一个类定义在另一个 类的内部&#xff0c;这个类就是内部类。 场景&#xff1a;当一个类的内部&#xff0c;包含了一个完整的事物&#xff0c;且…

Android ART 虚拟机简析

源码基于&#xff1a;Android U 1. prop 名称选项名称heap 变量名称功能 dalvik.vm.heapstartsize MemoryInitialSize initial_heap_size_ 虚拟机在启动时&#xff0c;向系统申请的起始内存 dalvik.vm.heapgrowthlimit HeapGrowthLimit growth_limit_ 应用可使用的 max…

Scikit-Learn朴素贝叶斯

Scikit-Learn朴素贝叶斯 1、朴素贝叶斯1.1、贝叶斯分类1.2、贝叶斯定理1.3、贝叶斯定理的推导1.4、朴素贝叶斯及原理1.5、朴素贝叶斯的优缺点2、Scikit-Learn朴素贝叶斯2.1、Sklearn中的贝叶斯分类器2.2、Scikit-Learn朴素贝叶斯API2.3、Scikit-Learn朴素贝叶斯实践(新闻分类与…

爬山算法的详细介绍

目录 &#x1f349;概述 &#x1f349; 步骤 &#x1f349; 优缺点 &#x1f348;优点 &#x1f348;缺点 &#x1f348;应对策略 &#x1f349;示例 &#x1f348;旅行商问题 &#x1f34d;步骤 &#x1f34d;分解代码 &#x1f34e;包含头文件 &#x1f34e;定义函…

Cortex-M3的SysTick 定时器

目录 概述 1 SysTick 定时器 1.1 SysTick 定时器功能介绍 1.2 SysTick 定时器功能实现 1.3 SysTick在系统中的作用 2 SysTick应用的实例 2.1 建立异常服务例程 2.2 使能异常 2.3 闹钟功能 2.4 重定位向量表 2.5 消灭二次触发 3 SysTick在FreeRTOS中的应用 3.1 STM…

在docker中运行SLAM十四讲程序

《十四讲》的示例程序依赖比较多&#xff0c;而且系统有点旧。可以在容器中运行。 拉取镜像 docker pull ddhogan/slambook:v0.1这个docker对应的github&#xff1a;HomeLH/slambook2-docker 拉下来之后&#xff0c;假如是Windows系统&#xff0c;需要使用XLaunch用于提供X11…

面试大杂烩之kafka

面试这个领域最近环境不行&#xff0c;所以卷起来流量挺大 关于K8s 其实看我之前的博客&#xff0c;k8s刚有点苗头的时候我就研究过&#xff0c;然后工作的时候间接接触 也自己玩过 但是用的不多就忘记了&#xff0c;正苦于不知道写什么&#xff0c;水一篇 用来面试应该是够了…

C++ | Leetcode C++题解之第111题二叉树的最小深度

题目&#xff1a; 题解&#xff1a; class Solution { public:int minDepth(TreeNode *root) {if (root nullptr) {return 0;}queue<pair<TreeNode *, int> > que;que.emplace(root, 1);while (!que.empty()) {TreeNode *node que.front().first;int depth que…

huggingface 笔记:PretrainModel

1 from_pretrained 从预训练模型配置中实例化一个 PyTorch 预训练模型默认情况下&#xff0c;模型使用 model.eval() 设置为评估模式&#xff08;Dropout 模块被禁用&#xff09; 要训练模型&#xff0c;应该首先使用 model.train() 将其设置回训练模式 1.1 主要参数 pretra…

java 子类继承父类

为什么需要继承 我现在要有两个类一个 一个是小学生&#xff0c;一个是大学生 代码 小学生 package b; public class encapsulatio{public String name;public int age;public double score;public void setscore (double score) {this.scorescore;}public void testing() {S…

(三)MySQL 索引

欢迎访问 什么是索引&#xff1f; 提高查询效率的一种数据结构&#xff0c;索引是数据的目录 索引的分类 按「数据结构」分类&#xff1a;Btree索引、Hash索引、Full-text索引。按「物理存储」分类&#xff1a;聚簇索引、二级索引。按「字段特性」分类&#xff1a;主键索引…

Spring6 对 集成MyBatis 开发运用(附有详细的操作步骤)

详细实现操作步骤 具体实现内容&#xff1a;我们运用 Spring6 和 MyBatis 实现一个转账操作(该转账操作&#xff0c;进行一个事务上的控制&#xff0c;运用 MyBatis 执行 SQL 语句)。 第一步&#xff1a;准备数据库表 使用t_act表&#xff08;账户表&#xff09; 连接数据库的…

三个有意思的链表面试题的完成

上一篇博客我们已经完成了链表的所有内容&#xff0c;那么这一篇博客我们来看一下三个特别有意思的链表题目。 **第一个题目如下&#xff1a;**相信不少朋友看到这题目就已经晕了&#xff0c;那就简单说明下这个题目&#xff0c;题目就是创建一个链表&#xff0c;其中每个节点…

Android14 - 绘制系统 - 概览

从Android 12开始&#xff0c;Android的绘制系统有结构性变化&#xff0c; 在绘制的生产消费者模式中&#xff0c;新增BLASTBufferQueue&#xff0c;客户端进程自行进行queue的生产和消费&#xff0c;随后通过Transation提交到SurfaceFlinger&#xff0c;如此可以使得各进程将缓…

【vue3+elementuiplus】el-select下拉框会自动触发校验规则

场景&#xff1a;编辑弹框省份字段下拉框必填&#xff0c;触发方式change&#xff0c;有值第一次打开不会触发校验提示&#xff0c;关闭弹框再次打开触发必填校验提示&#xff0c;但是该字段有值 问题的原因是&#xff1a;在关闭弹层事件中&#xff0c;我做了resetfileds&…

SpringBoot + MybatisPlus

SpringBoot MybatisPlus 整合记录 1. 硬件软件基本信息2. 相关链接3. 通过idea快速生成一个Springboot项目4. 启动报错问题解决问题一&#xff1a;Springboot启动的时候报错提示 “没有符合条件的Bean关于Mapper类型”问题二&#xff1a;启动的时候提示需要一个Bean&#xff0…

电磁仿真--CST网格介绍

1. 简介 网格会影响仿真的准确性和速度&#xff0c;花时间理解网格化过程是很重要的。 CST 中可用的数值方法包括FIT、TLM、FEM、MoM&#xff0c;使用不同类型的网格&#xff1a; FIT和TLM&#xff1a;六面体 FEM&#xff1a;四面体、平面 MoM&#xff1a;表面 CFD&#…