缓存穿透、缓存击穿、缓存雪崩区别和解决方案

news2025/1/16 18:51:52

生命无罪,健康万岁,我是laity。

我曾七次鄙视自己的灵魂:

第一次,当它本可进取时,却故作谦卑;

第二次,当它在空虚时,用爱欲来填充;

第三次,在困难和容易之间,它选择了容易;

第四次,它犯了错,却借由别人也会犯错来宽慰自己;

第五次,它自由软弱,却把它认为是生命的坚韧;

第六次,当它鄙夷一张丑恶的嘴脸时,却不知那正是自己面具中的一副;

第七次,它侧身于生活的污泥中,虽不甘心,却又畏首畏尾。

前言

关于缓存异常,我们常见的有三个问题:缓存雪崩、缓存击穿、缓存穿透。 这三个问题一旦发生,会导致大量请求直接落到数据库层面。如果请求的并发量很大,会影响数据库的运行,严重的会导致数据库宕机。

为了避免异常带来的损失,我们需要了解每种异常的原因以及解决方案,提高系统的可靠性。

高并发下缓存雪崩

Redis中的数据大面积失效(时间过期)的情景

  • 缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到 DB,DB 瞬时压力过重雪崩。
  • 解决方案:
    • 均匀过期:给热点数据设置不同的过期时间,给每个key的失效时间加一个随机值;
      • 原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
    • 设置热点数据永不过期:不设置失效时间,有更新的话,需要更新缓存;
    • 服务降级:指服务针对不同的数据采用不同的处理方式:
      • 业务访问的是非核心数据,直接返回预定义信息、空值或者报错;
      • 业务访问核心数据,则允许访问缓存,如果缓存缺失,可以读取数据库。

Redis 缓存实例发生故障宕机的场景

  • 实现服务熔断或者请求限流机制
    • 通过监测Redis以及数据库实例所在服务器负载指标,如果发现Redis服务宕机,导致数据库的负载压力增大,我们可以启动服务熔断机制,暂停对缓存服务的访问。
    • 但是这种方法对业务应用的影响比较大,我们也可以通过限流的方式降低这种影响。

举个例子:比如业务系统正常运行时,请求入口每秒最大允许进入的请求数是1万个,其中9000请求个可以被缓存处理,余下1000个会发送给数据库处理。一旦发生雪崩,数据库每秒处理的请求突然增加到1万个,此时我们就可以启动限流机制。在前端请求入口处,只允许每秒进入1000个请求,其他的直接拒绝掉。这样就可以避免大量并发请求发送给数据库。

  • 事前预防
    • 通过主从节点的方式构建 Redis 缓存高可靠集群。 如果 Redis 缓存的主节点故障宕机了,从节点还可以切换成为主节点,继续提供缓存服务,避免了由于缓存实例宕机而导致的缓存雪崩问题。

高并发下缓存穿透

就是去查询一个一定不存在的数据

  • 缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也无此记录,我们没有将这次查询的 null 写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
  • 在流量大时,可能 DB 就挂掉了,要是有人利用不存在的 key 频繁攻击我们的应用,这就是漏洞。
  • 解决方案:
    • 缓存空结果、并且设置短的过期时间。(null结果进行缓存)
      • 如果有大量的Key穿透,缓存空对象会占用宝贵的内存空间。针对这种情况可以给空对象设置过期时间。
      • 设置过期时间之后,可能会有缓存与数据库不一致的情况(后期可能又有这个条数据了)。
    • 布隆过滤:快速判断数据是否存在,避免从数据库中查询数据是否存在,减轻数据库压力。
    • 接口层增加校验:用户鉴权、参数校验(请求参数是否合法、请求字段是否不存在等等。

高并发下缓存击穿

一个key过期,但是被高访问

  • 对于一些设置了过期时间的 key,如果这些 key 可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。
  • 这个时候,需要考虑一个问题:如果这个 key 在大量请求同时进来前正好失效,那么所有对这个 key 的数据查询都落到 db,我们称为缓存击穿。
  • 解决方案:
    • 设置热点数据永不过期:不设置失效时间,有更新的话,需要更新缓存;
    • 加互斥锁:单机可以使用synchronized、lock,分布式可以使用lua脚本或分布式锁Redisson。

zookeeper与etcd,用来做锁都比redis要好很多

关于缓存异常有兴趣的同学可以看下这篇:关于缓存异常:缓存雪崩、击穿、穿透的解决方案
关于分布式锁有兴趣的同学可以看下这篇:Redis分布式锁的正确打开方式

布隆过滤器

布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。

  • 优点:空间效率和查询时间都远远超过一般的算法。
  • 缺点:有一定的误识别率,删除困难。

布隆过滤器原理

布隆过滤器本质上是一个初始值都为 0 的 二进制数组N 个哈希函数组成。
当我们想标记某个数据存在时(例如,数据x已被写入数据库),布隆过滤器会通过三个操作完成标记:

  • 首先,使用 N 个哈希函数,分别计算这个数据的哈希值,得到 N 个哈希值。
  • 然后,我们把这 N 个哈希值对 bit 数组的长度取模,得到每个哈希值在数组中的对应位置。
  • 最后,我们把对应位置的 bit 位设置为 1,这就完成了在布隆过滤器中标记数据的操作。

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

三次哈希,对应的二进制数组下标分别是 1、3、7,将原始数据从 0 变为 1。

同样的,我们标记数据y的逻辑也是一样的。如下图:
在这里插入图片描述

三次哈希,对应的二进制数组下标分别是 1、5、9,将原始数据从 0 变为 1。

下标 1,之前已经被操作设置成 1,则本次认为是哈希冲突,不需要改动。

Hash 规则:如果在 Hash 后,原始位它是 0 的话,将其从 0 变为 1;如果本身这一位就是 1 的话,则保持不变。

正是因为存在哈希冲突,导致布隆过滤器的判断存在误差。

也因为哈希冲突的存在,导致布隆过滤器不能轻易删除数据,存在误删的风险。

布隆过滤器减少误差的方法:

  • 增加二进制位数组的长度,这样hash后的数据会更加离散化,出现冲突的概率会大大降低;
  • 增加Hash的次数,变相的增加数据特征,特征越多,冲突的概率越小。

布隆过滤器如何使用

比如,当查询一件商品的缓存信息时,我们首先要判断这件商品是否存在。

  • 通过三个哈希函数对商品id计算哈希值;
  • 然后,在布隆数组中查找访问对应的位值,0或1;
  • 判断,三个值中,只要有一个不是1,那么我们认为数据是不存在的。

如下图:
在这里插入图片描述
注意:布隆过滤器只能精确判断数据不存在情况,对于存在我们只能说是可能,因为存在Hash冲突情况,当然这个概率非常低。

在Java中使用布隆过滤器

至于在Java应用中使用布隆过滤器,我们可以通过Redisson实现,它内置了布隆过滤器。

整合redisson做为分布式锁等功能框架

首先引入依赖

        <!-- Redis- 红锁:分布式锁,分布式对象等功能的框架 -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.17.7</version>
        </dependency>

代码示例:

package com.laity.product;

import org.junit.jupiter.api.Test;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

/**
 * @author: Laity
 * @Project: JavaLaity
 * @Package: com.laity.product.config.MyRedissonConfig
 * @Date: 2022年10月18日 01:39
 * @Description: 测试基于redisson使用布隆过滤
 */
@SpringBootTest
public class RedissonBloomFilter {

    @Autowired
    private RedissonClient redissonClient;

    @Test
    void redissonBloom(){
        RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("test-bloom-filter");
        // 初始化布隆过滤器,数组长度100W,误判率 1%
        bloomFilter.tryInit(1000000L, 0.01);
        // 添加数据
        bloomFilter.add("Shawn");
        // 判断是否存在
        System.out.println(bloomFilter.contains("laity"));
        System.out.println(bloomFilter.contains("itlaity"));
    }
}

运行结果:

false   // 肯定不存在
true    // 可能存在,有1%的误判率

与其让平凡与胆怯缚住自己的手脚,不如倾生命的全部能量奋力一搏。我是Laity,正在前行的Laity。

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

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

相关文章

【c++】类和对象

目录 1.面向过程和面向对象初步认识 2.类的引入 3.类的定义 3.1类的两种定义方式&#xff1a; 4.类的访问限定符及封装 4.1访问限定符 5.类的作用域 6.类的实例化 7.类对象模型 7.1 如何计算类对象的大小 8.this指针 8.1 this指针的引出 8.2 this指针的特性 9.类的6个默认成员函…

深度学习简介及反向传播

Datawhale开源学习&#xff0c;机器学习课程&#xff0c;项目地址&#xff1a;https://github.com/datawhalechina/leeml-notes 之前学习机器学习的时候&#xff0c;总结了三步&#xff1a; define a set of functiongoodness of functionpick the best function 这三步所做的…

机器学习之决策树【西瓜书】

当一个有经验的老农看一个瓜是不是好瓜时&#xff0c;他可能会先看下瓜的颜色&#xff0c;一看是青绿的&#xff0c;心想有可能是好瓜&#xff1b;接着他又看了下根蒂&#xff0c;发现是蜷缩着的&#xff0c;老农微微点头&#xff0c;寻思着五成以上是好瓜&#xff1b;最后他又…

基于51单片机的客车辆超载报警Proteus仿真

资料编号&#xff1a;111 下面是相关功能视频演示&#xff1a; 111-基于51单片机的客车辆超载报警Proteus仿真&#xff08;源码仿真原理图全套资料&#xff09;功能如下&#xff1a; 利用单片机设计一个超载检测控制电路。 通过 2 组红外对管(类似电路模拟)检测上车人数和下车…

Flink-水位线的设置以及传递

6.2 水位线 6.2.1 概述 分类 有序流 无序流 判断的时间延迟 延迟时间判定 6.2.2 水位线的设置 分析 DataStream下的assignTimstampsAndWatermarks方法&#xff0c;返回SingleOutputStreamOperator本质还是个算子&#xff0c;传入的参数是WatermarkStrategy的生成策略 但…

C51 - 中断系统

Contents1> 定义2> 作用3> 组成3.1> 中断系统结构3.2> 8个中断源3.3> 中断向量3.4> 中断优先级4> 原理5> 应用1> 定义 中断&#xff08;interrupt&#xff09;是指&#xff1a; CPU执行某一程序过程中&#xff0c;由于系统内&#xff0c;或外部某…

【操作文件的系统调用】

目录文件操作系统调用的基本库函数打开文件读取文件写入文件关闭文件应用文件操作代码举例文件操作与进程复制的结合先打开文件再复制进程先进程复制&#xff0c;再进行打开文件缓冲区的知识回顾在上一篇讲述僵尸进程的文章中对文件的系统调用做了一点点的代码讲述&#xff0c;…

day08 微服务保护

1、JMeter压力测试 1.1、安装启动 JMeter 依赖于JDK&#xff0c;所以必须确保当前计算机上已经安装了 JDK&#xff0c;并且配置了环境变量。 Apache Jmeter官网下载&#xff0c;地址&#xff1a;http://jmeter.apache.org/download_jmeter.cgi 解压缩即可使用&#xff0c;目…

【Acwing—单源最短路:建图】

y总说&#xff0c;图论题的难点不在于打板子&#xff0c;而是建图的过程 个人觉得&#xff0c;建图的过程分成以下阶段&#xff1a; 1.确定结点的意义 2.确定边权的意义 结点一般都很显然&#xff0c;但是边权的意义我们一般把它设成对答案&#xff08;或需要维护的东西&am…

C++入门知识(二)

最近太忙了&#xff0c;发论文写开题&#xff0c;有两周时间没有学习C了&#xff0c;因为都是抽时间来学习&#xff0c;所以本篇博客也是零零散散的&#xff0c;接下来尽量抽时间吧 目录 六、引用 6.1 引用概念 6.2 引用特性 6.3 常引用 6.4 使用场景 6.5 传值、传引用…

并发编程(一)可见性

【并发编程三大特性】&#xff1a; 可见性 有序性 原子性&#xff08; 较复杂 &#xff09; 【线程的可见性】&#xff1a; 【一个例子认识线程的可见性】&#xff1a; import Utils.SleepHelper; import java.io.IOException;public class T01_HelloVolatile {private sta…

Go中的泛型和反射以及序列化

嗨喽,小伙伴们,好几没有更新了,最近在搞一些云原生的东西,docker , k8s 搞得我暂时迷失了方向,不过我举得搞IT吗,就是在不断尝试,搞一下当下最新的技术,不然 … GO中的泛型与继承 搞过java的都知道泛型与继承,在go中也开始搞泛型与继承了(在go1.8之后) 先看代码–>> p…

【记录】PyCharm 安装 preprocess 模块(库)|| 在 PyCharm 中安装 preprocess 失败,故而在 终端 安装

preprocess.py 针对的是处理许多 文件类型。它工作的语言包括&#xff1a;C、Python、 perl、tcl、xml、javascript、css、idl、tex、fortran、php、java、shell 脚本&#xff08;bash、csh等&#xff09;和c。预处理可以作为 命令行应用程序和作为python 模块。 目录一、在 Py…

矩阵论复习提纲

矩阵论复习提纲 第一章 矩阵相似变化 1、特征值与特征向量 A ∈ Cnxn 若存在 λ ∈ C 满足 Ax λx 则 λ 为 A 的特征值 可转换为 &#xff08;λI - A&#xff09;x 0 特征多项式 &#xff1a;det(λI - A) 特征矩阵&#xff1a; λI - A 2、相似对角化 1. 判断可对角化…

VMware Fusion 13 正式版终于来了

千呼万唤&#xff0c;经历两年之久&#xff0c;VMware终于在Fusion 13正式版中支持了Apple Silicon 版Mac&#xff0c;此次发布的Fusion是Universal版本&#xff0c;也就是一个安装包同时适配Intel Mac及Apple Silicon &#xff08;M1&#xff0c;M2&#xff09;Mac。想起我两年…

疑难杂症集合(备忘)

sshd&#xff1a;no hostkeys available 解决过程: #ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key #ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key #/usr/sbin/sshd 如果上述两个文件存在&#xff0c;仍然出现这个错误&#xff0c;那么试试 chmod 600 上述两个文件。之…

01-Python的基本概念

01-Python的基本概念 Python是一种直译式&#xff08;Interpreted&#xff09;、面向对象&#xff08;Object Oriented&#xff09;的程序语言’它拥有完整的函数库’可以协助轻松地完成许多常见的工作。 所谓的直译式语言是指’直译器&#xff08;InteIpretor&#xff09;会将…

诊断故障码(Diagnostic Trouble Code-DTC)

诊断协议那些事儿 诊断协议那些事儿专栏系列文章&#xff0c;本文将由浅入深的介绍DTC&#xff08;Diagnostic Trouble Code&#xff09;。 关联文章&#xff1a; $19服务:DTCStatusMask和statusofDTC bit 定义 19服务List 文章目录诊断协议那些事儿DTC&#xff08;Diagnos…

Python pyenv install 下载安装慢(失败)完美解决

pyenv 下载原理就是将例如 Python-3.10.3.tar.xz 这样的压缩文件下载到本地缓存文件或临时文件&#xff0c;然后解压出来使用。 由于下载速度或者网不行&#xff0c;那么就跳过 pyenv 下载&#xff0c;自己下载&#xff0c;然后放到它的缓存文件&#xff0c;这样不就行了。 1…

3.np.random

1. np.random.seed np.random.seed()函数用于生成指定随机数。 seed()被设置了之后&#xff0c;np.random.random()可以按顺序产生一组固定的数组&#xff0c;如果使用相同的seed()值&#xff0c;则每次生成的随机数都相同。 如果不设置这个值&#xff0c;那么每次生成的随机数…