缓存穿透、缓存击穿和缓存雪崩

csdntup

👏作者简介:大家好,我是爱发博客的嗯哼,爱好Java的小菜鸟
🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
📝社区论坛:希望大家能加入社区共同进步
🧑‍💼个人博客:智慧笔记
📕系列专栏:Redis

文章目录

  • 前言
  • 一、问题前引
  • 二、缓存穿透
    • 1. 问题描述
    • 2. 问题解决
      • 2.1 缓存空数据
      • 2.2 布隆过滤器
  • 三、缓存击穿
    • 1. 问题描述
    • 2. 问题解决
      • 2.1 设置逻辑过期
      • 2.2 设置互斥锁
  • 四、缓存雪崩
    • 1. 问题描述
    • 2. 问题解决
      • 2.1 设置随机过期时间
      • 2.2 缓存高可用
  • 总结
  • 结语

前言

一聊到redis,必不可少的就是缓存三兄弟的问题,即缓存穿透、缓存击穿和缓存雪崩,这三个问题在业务场景中相对来说比较常见的,也是比较基础的三种问题。那么这三种问题是如何引起的,并且应该如何解决,就是本章探讨的话题。


一、问题前引

大家都知道,Redis一般搭配MySQL来使用,来充当缓存处理一些业务数据。但为什么要Redis用来充当缓存呢,不能直接使用MySQL吗?

当然是可以的,但是对于一些请求量大并发次数高的场景就有问题了。

MySQL是基于磁盘的,请求查询速度偏慢,所以就需要一个基于内存的速度快的工具来缓存这些数据,Redis就应运而生了。而且当大量请求到来时,只有MySQL的话,有可能承受不住大量请求导致MySQL宕机,此时就会影响到整个服务器,所以Redis此时又充当了一个保护缓冲的作用。

二、缓存穿透

1. 问题描述

缓存穿透主要体现在穿透两个字上,穿透即为穿过缓存,打到数据库上。

当一个请求访问的时候,此时Redis没有缓存该数据,然后去数据库查询该数据也查询到,说明没有该数据。
在这里插入图片描述

此时你或许还不以为然,不就一个空数据吗?多稀罕啊。

但如果该请求是恶意请求,此时无数条请求同时访问,缓存中没有,全部都会打在数据库上,刚好还是类似于

select * from table where name = "李白"

表中有1000万条数据,name字段也没有创建索引。这时候问题是不是就大了?服务器稍微差一点,就会直接宕机。
在这里插入图片描述

这时你或许该问了,那该如何解决呢?不要急,机智的程序猿肯定有应对之法。

2. 问题解决

2.1 缓存空数据

如果此时将请求的数据缓存起来,是不是就可以避免请求打到数据库了?

你现在或许又要问了,空数据怎么缓存呢?没错,就是缓存空数据

如果请求的数据查询数据为空的话,就将该数据为空值缓存到Redis中,以后每次请求都直接访问Redis,查询到该数据,直接返回空值。这样就避免恶意请求全部打到数据库了。
在这里插入图片描述

2.2 布隆过滤器

不了解布隆过滤器的同学可以看这篇文章硬核 | Redis 布隆(Bloom Filter)过滤器原理与实战

布隆过滤器 (Bloom Filter)是由 Burton Howard Bloom 于 1970 年提出,它是一种 space efficient 的概率型数据结构,用于判断一个元素是否在集合中。

当布隆过滤器说,某个数据存在时,这个数据可能不存在;当布隆过滤器说,某个数据不存在时,那么这个数据一定不存在

哈希表也能用于判断元素是否在集合中,但是布隆过滤器只需要哈希表的 1/8 或 1/4 的空间复杂度就能完成同样的问题。

布隆过滤器可以插入元素,但不可以删除已有元素

其中的元素越多,false positive rate(误报率)越大,但是 false negative (漏报)是不可能的。
布隆过滤器原理

BloomFilter 的算法是,首先分配一块内存空间做 bit 数组,数组的 bit 位初始值全部设为 0。

加入元素时,采用 k 个相互独立的 Hash 函数计算,然后将元素 Hash 映射的 K 个位置全部设置为 1。

检测 key 是否存在,仍然用这 k 个 Hash 函数计算出 k 个位置,如果位置全部为 1,则表明 key 存在,否则不存在。

如下图所示:
在这里插入图片描述

三、缓存击穿

1. 问题描述

缓存击穿一般常见于电商场景,在双十一和六一八这种大促活动中,缓存中会缓存一些热点数据,随时都有大量的请求访问这个数据。

当某个时刻这个数据突然过期,大量请求就会集中打到MySQL数据库中。
在这里插入图片描述

如何解决这个问题呢?

2. 问题解决

该问题导致的原因是因为该缓存数据过期了,但却有大量请求访问该数据;

有两条思路去解决:

  • 不让该数据过期
  • 不让大量请求访问数据库

2.1 设置逻辑过期

热点数据随时都会有变化,不设置过期时间的话会导致更多问题,不能因此失彼。

但可以换一个思路,在数据过期时无缝衔接一个新数据,在请求看来这就是没有过期时间的一个数据。

在这里插入图片描述

此时如果大量请求访问该数据,刚好该数据缓存逻辑过期,但没有设置物理过期时间,所以数据并不会被redis清除。

此时由业务代码去判断,该缓存是否过期,如果过期则获取互斥锁新建一个子线程去访问数据库重新设置缓存,主线程返回过期数据,没有获取互斥锁的都返回过期数据

完整代码如下:

 //逻辑过期public Shop queryWithLogicalExpire(Long id) {String key = CACHE_SHOP_KEY + id;//1.从redis查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(key);//2.判断是否存在if (StrUtil.isBlank(shopJson)) {//3.未命中return null;}//4.命中,需要先把json反序列化为对象RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);Shop shop = (Shop) redisData.getData();LocalDateTime expireTime = redisData.getExpireTime();//5.判断是否过期if (expireTime.isAfter(LocalDateTime.now())) {//5.1还未过期return shop;}//5.2已经过期,需要缓存重建//6.缓存重建//6.1获取互斥锁String lockKey = LOCK_SHOP_KEY + id;boolean isLock = tryLock(lockKey);//6.2判断是否获取锁成功if (isLock) {// 6.3成功,开启独立线程,实现缓存重建CACHE_REBUILD_EXECUTOR.submit(() -> {try {//重建缓存this.saveShop2Redis(id, 20L);} catch (Exception e) {e.printStackTrace();} finally {//释放锁unlock(lockKey);}});}//6.4返回过期的店铺信息//7.返回return shop;}

2.2 设置互斥锁

怎么才能不让大量数据去访问数据库呢?

或许大家已经想到了,上面设置逻辑过期用到过的一个功能:互斥锁

在这里插入图片描述

请求首先访问缓存,如果命中的话,直接返回该数据。

如果未命中的话,则去获取互斥锁,获取成功则查询数据库重新设置缓存,获取失败,则重试获取缓存数据

完整代码如下:

/*** 通过互斥锁机制查询商铺信息* @param key*/private Shop queryShopWithMutex(String key, String cityCode) {Shop shop = null;// 1.查询缓存String shopJson = stringRedisTemplate.opsForValue().get(key);// 2.判断缓存是否有数据if (StringUtils.isNotBlank(shopJson)) {// 3.有,则返回shop = JSONObject.parseObject(shopJson, Shop.class);return shop;}// 4.无,则获取互斥锁String lockKey = RedisConstants.LOCK_SHOP_KEY + shopCode;Boolean isLock = tryLock(lockKey);// 5.判断获取锁是否成功try {if (!isLock) {// 6.获取失败, 休眠并重试Thread.sleep(100);return queryShopWithMutex(key, shopCode);}// 7.获取成功, 查询数据库shop = baseMapper.getByCode(shopCode);// 8.判断数据库是否有数据if (shop == null) {// 9.无,则将空数据写入redisstringRedisTemplate.opsForValue().set(key, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);return null;}// 10.有,则将数据写入redisstringRedisTemplate.opsForValue().set(key, JSONObject.toJSONString(shop), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);} catch (Exception e) {throw new RuntimeException(e);} finally {// 11.释放锁unLock(lockKey);}// 12.返回数据return shop;}

关于两种方案,各有各的优缺点

  • 逻辑过期: 及时性高,但数据不是最新数据,适合最终一致性的业务
  • 互斥锁: 一致性高,但会有数据延迟,适合强一致性的业务

四、缓存雪崩

1. 问题描述

缓存雪崩可以简单的理解为大范围的缓存击穿。

有两个可能引起缓存雪崩问题:

  • 有大量的热门缓存同时失效。会导致大量的请求,访问数据库。而数据库很有可能因为扛不住压力,而直接挂掉。
  • 缓存服务器down机了,可能是机器硬件问题,或者机房网络问题。造成了整个缓存的不可用。
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/332236c568114666ae63f7ad3e20ea9e.png

2. 问题解决

2.1 设置随机过期时间

为了解决缓存雪崩问题,我们首先要尽量避免缓存同时失效的情况发生。

这就要求我们不要设置相同的过期时间。

可以在设置的过期时间基础上,再加个1~60秒的随机数。

实际过期时间 = 过期时间 + 1~60秒的随机数

这样即使在高并发的情况下,多个请求同时设置过期时间,由于有随机数的存在,也不会出现太多相同的过期key。

2.2 缓存高可用

针对缓存服务器down机的情况,在前期做系统设计时,可以做一些高可用架构

可以使用哨兵机制或者集群模式,当一个Redis宕机,随时会有一个Redis补充上来,避免一个Redis宕机而导致大量请求去访问数据库,而使数据库压力过载。

比如使用哨兵模式之后,当某个master服务下线时,自动将该master下的某个slave服务升级为master服务,替代已下线的master服务继续处理请求。

总结

缓存穿透、缓存击穿和缓存雪崩是三种与缓存相关的常见问题,它们的概念和影响有所不同。

关于Redis缓存三兄弟的问题及解决主要就是以下几个方面:

缓存穿透:

  • 缓存穿透指的是对于一个不存在于缓存和数据库中的数据的请求,每次请求都会穿过缓存层直接访问数据库。

  • 恶意的攻击者可以通过构造不存在的数据请求,导致大量请求直接访问数据库,增加数据库负载压力。

  • 解决缓存穿透可以采用存储空数据和合适的校验技术,例如使用布隆过滤器等技术,在缓存层进行初步过滤,避免无效请求直接访问数据库。

缓存击穿:

  • 缓存击穿指的是在高并发情况下,一个热点数据过期或失效时,大量请求同时涌入数据库,造成数据库压力激增。

  • 由于热点数据没有命中缓存,而直接访问数据库,使得缓存无法发挥作用,增加了数据库的负载。

  • 解决缓存击穿可以采取设置热点数据永不过期,或者使用互斥锁等机制来控制只有一个线程去加载数据。

缓存雪崩:

  • 缓存雪崩指的是在某个时间点,缓存中的大量数据同时失效或过期或者缓存服务宕机,导致大量请求直接访问后端数据库,造成数据库压力过大。
  • 当缓存中的数据集中过期或失效时,没有缓存可用,导致大量请求直接访问数据库,可能引起数据库负载激增甚至崩溃。
  • 解决缓存雪崩可以采用合理的缓存失效时间设置、使用高可用架构等方式来减少缓存失效的风险。

当然能解决的方式有很多,这里只是列举出来常见的解决方法。如果有更好的建议可以发在评论区。


结语

每个人都有自己独特的才华和潜能,在这个广袤的世界上,你的存在是有意义的。无论你是谁,你的背景如何,你所处的环境怎样,只要你敢于跨出舒适区,付出努力,追求卓越,你就能够开创属于自己的辉煌。

我们下期见。

每一次努力都是一次进步,即使进展缓慢,也要坚持不懈。

往期文章推荐

  • 消息中间件相关面试题
  • Java集合相关面试题
  • Java集合详解
  • 微服务相关面试题
  • redis相关面试题
  • 图解 Paxos 算法
  • Spring相关面试题
  • Mysql相关面试题

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

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

相关文章

【JVM 内存结构 | 程序计数器】

内存结构 前言简介程序计数器定义作用特点示例应用场景 主页传送门:📀 传送 前言 Java 虚拟机的内存空间由 堆、栈、方法区、程序计数器和本地方法栈五部分组成。 简介 JVM(Java Virtual Machine)内存结构包括以下几个部分&#…

和鲸 ModelWhale 与中科可控多款服务器完成适配认证,赋能中国云生态

当前世界正处于新一轮技术革命及传统产业数字化转型的关键期,云计算作为重要的技术底座,其产业发展与产业规模对我国数字经济的高质量运行有着不可取代的推动作用。而随着我国数字上云、企业上云加快进入常规化阶段,云计算承载的业务应用越来…

跟随角色镜头时,解决地图黑线/白线缝隙的三种方案

下面一共三个解决方案,这里我推荐第二个方案解决,因为够快速和简单。 现象: 解决方案一: 参考【Unity2D】去除地图中的黑线_unity选中后有线_香菇CST的博客-CSDN博客,博主解释是因为抗锯齿采样导致的问题。 具体到这…

(6)(6.3) 自动任务中的相机控制

文章目录 前言 6.3.1 概述 6.3.2 自动任务类型 6.3.3 创建合成图像 前言 本文介绍 ArduPilot 的相机和云台命令,并说明如何在 Mission Planner 中使用这些命令来定义相机勘测任务。这些说明假定已经连接并配置了相机触发器和云台(camera trigger and gimbal ha…

ArcGIS API开发介绍

本来想自己总结写一下的,但是发现有个网站总结的特别好。所以直接给大家分享一下地址: 起步 - Start | ArcGis中文网 当然系统性的学习和使用还的看官网文档Quick Links | API Reference | ArcGIS Maps SDK for JavaScript 4.27 | ArcGIS Developers …

STM32CubeMX配置STM32G0 Standby模式停止IWDG(HAL库开发)

1.打开STM32CubeMX选择好对应的芯片,打开IWDG 2.打开串口1进行调试 3.配置好时钟 4.写好项目名称,选好开发环境,最后获取代码。 5.打开工程,点击魔术棒,勾选Use Micro LIB 6.修改main.c #include "main.h"…

Hive(一)

一、DDL 1、数据库操作 1)、创建数据库 语法: CREATE DATABASE [IF NOT EXISTS] database_name [COMMENT database_comment] [LOCATION hdfs_path] [WITH DBPROPERTIES (property_nameproperty_value, ...)]; 案例: (1&…

OpenGL —— 2.5、绘制第一个三角形(附源码,glfw+glad)(更新:纹理贴图)

源码效果 C源码 纹理图片 需下载stb_image.h这个解码图片的库,该库只有一个头文件。 具体代码: vertexShader.glsl #version 330 corelayout(location 0) in vec3 aPos; layout(location 1) in vec3 aColor; layout(location 2) in vec2 aUV;out ve…

【Linux】socket编程(二)

目录 前言 TCP通信流程 TCP通信的代码实现 tcp_server.hpp编写 tcp_server.cc服务端的编写 tcp_client.cc客户端的编写 整体代码 前言 上一章我们主要讲解了UDP之间的通信,本章我们将来讲述如何使用TCP来进行网络间通信,主要是使用socket API进…

【ElasticSearch】一键安装ElasticSearch与Kibana以及解决遇到的问题

目录 一、安装ES 二、安装Kibana 三、遇到的问题 一、安装ES 按顺序复制即可 docker network create es-net # 创建网络 docker pull images:7.12.1 # 拉取镜像 mkdir -p /root/es/data # 创建数据卷 mkdir -p /root/es/plugins # 创建数据卷 chmod 777 /root/es/** # 设置权…

yolo笔记

目录 输入端Mosaic数据增强数据增强Copy-paste数据增强- MixUp数据增强- Albumentations数据增强- Augment HSV (Hue, Saturation, Value)色度、饱和度、浓度数据增强- Random horizontal flip自适应锚框计算自适应图片缩放 BackboneFocus结构CSP结构CSP结构Neck 损失函数IOU_L…

GitHub的PUSH显示网络超时,小乌龟网络代理办法

前言 (1)我能够正常访问GitHub,但是每次将代码提交到GitHub常常显示网络超时。这是因为提交是走的国内的网络,对GitHub访问会被进行限速。 (2)为了让小乌龟也拥有魔法,我们可以使用代理工具。注…

[golang gin框架] 46.Gin商城项目-微服务实战之后台Rbac客户端调用微服务权限验证以及Rbac微服务数据库抽离

一. 根据用户的权限动态显示左侧菜单微服务 1.引入 后台Rbac客户端调用微服务权限验证功能主要是: 登录后显示用户名称、根据用户的权限动态显示左侧菜单,判断当前登录用户的权限 、没有权限访问则拒绝,参考[golang gin框架] 14.Gin 商城项目-RBAC管理,该微服务功能和上一节[g…

快速指南:使用Termux SFTP通过远程进行文件传输——”cpolar内网穿透“

文章目录 1. 安装openSSH2. 安装cpolar3. 远程SFTP连接配置4. 远程SFTP访问4. 配置固定远程连接地址 SFTP(SSH File Transfer Protocol)是一种基于SSH(Secure Shell)安全协议的文件传输协议。与FTP协议相比,SFTP使用了…

IDEA创建Spring,Maven项目没有resources文件夹

有时新建Spring或Maven项目时,会出现目录中main下无resources文件夹的情况,来一起解决一下: FIles|Project Structure 在Modules模块找到对应路径,在main下创建resources,右键main,选择新文件夹 输入文件…

【Spring】一次性打包学透 Spring | 阿Q送书第五期

文章目录 如何竭尽可能确保大家学透Spring1. 内容全面且细致2. 主题实用且本土化3. 案例系统且完善4. 知识有趣且深刻 关于作者丁雪丰业内专家推图书热卖留言提前获赠书 不知从何时开始,Spring 这个词开始频繁地出现在 Java 服务端开发者的日常工作中,很…

js判断用户当前网络状态和判断网速

前端判断用户当前网络状态和判断网速 一、第一种是通过 HTML5 提供的 navigator 去检测网络(1)、原理介绍:(2)、兼容性 二、监听window.ononline和window.onoffline事件:三、通过ajax进行请求判断(兼容性好-推荐)(1)、原理介绍:(2)、注意: 四、navigator.connection方法监听网络…

使用本地电脑搭建可以远程访问的SFTP服务器

文章目录 1. 搭建SFTP服务器1.1 下载 freesshd 服务器软件1.3 启动SFTP服务1.4 添加用户1.5 保存所有配置 2. 安装SFTP客户端FileZilla测试2.1 配置一个本地SFTP站点2.2 内网连接测试成功 3. 使用cpolar内网穿透3.1 创建SFTP隧道3.2 查看在线隧道列表 4. 使用SFTP客户端&#x…

小程序定位到 胶囊的三个点大概中间

话不多说,先上效果图 这个功能实现思路: 首先先拿到这一张整图(快捷,精确)然后获取整个导航栏高度(自定义导航栏,非自定义导航栏忽略这一步)获取三个点的做偏移量,把高度和偏移量给到一个定位到盒子,这个盒子里就放这个图片&…

【C语言】扫雷游戏(可展开)——超细教学

🚩纸上得来终觉浅, 绝知此事要躬行。 🌟主页:June-Frost 🚀专栏:C语言 🔥该篇将运用数组来实现 扫雷游戏。 目录: 🌟思路框架测试游戏 🌟测试部分函数实现&am…