Redis缓存穿透、击穿、雪崩到底是个啥?7张图告诉你

news2024/11/17 22:43:49

目录

    • 一、缓存是什么?
    • 二、缓存的作用和成本
      • 1、缓存的作用:
      • 2、缓存的成本:
    • 三、缓存作用模型
      • 1、根据id查询数据缓存流程
    • 四、缓存更新策略
      • 1、内存淘汰
      • 2、超时剔除
      • 3、主动更新
    • 五、缓存穿透
      • 解决方法:
    • 六、缓存雪崩
    • 七、缓存击穿
      • 1、通过互斥锁解决缓存击穿
      • 2、根据id查询商品信息,基于互斥锁解决缓存击穿问题
      • 3、通过逻辑过期解决缓存击穿
    • 八、Redis工具类
      • NoSQL数据库进阶实战
      • 哪吒精品系列文章

一、缓存是什么?

缓存就是数据交换的缓存区,是存储数据的地方,一般读写性能较高。
在这里插入图片描述

二、缓存的作用和成本

1、缓存的作用:

  1. 降低后端负载
  2. 提高读写效率,降低响应时间

2、缓存的成本:

  1. 数据一致性成本
  2. 代码维护成本
  3. 运维成本

三、缓存作用模型

在这里插入图片描述

1、根据id查询数据缓存流程

在这里插入图片描述

四、缓存更新策略

1、内存淘汰

Redis的内存淘汰机制,当内存不足时自动淘汰部分数据,下次查询时更新缓存。

2、超时剔除

当缓存数据设置TTL时间,到期后自动删除缓存,下次查询时更新缓存。

3、主动更新

编写业务逻辑,在修改数据库的同时,更新缓存。

五、缓存穿透

缓存穿透是指客户端请求的数据在Redis和数据库中都不存在,这样就无法进行缓存,这些请求都会打到数据库。

解决方法:

1、缓存空对象

对不存在的数据也在Redis中建立缓存,值为空,并设置一个较短的TTL时间。

  • 优点:实现简单,维护方便;
  • 缺点:额外的内存消耗,可能造成短期的数据不一致;

2、布隆过滤器

利用布隆过滤算法,在请求进入Redis之前,先判断是否存在,如果不存在则直接拒绝访问。

  • 优点:内存占用小
  • 缺点:① 实现复杂;② 存在误判的可能;

六、缓存雪崩

缓存雪崩是指同一时间段大量的缓存key同时失效或者Redis服务宕机,导致大量请求打到数据库,带来巨大压力。

解决方式:

  1. 给不同的key的TTL添加随机值;
  2. 利用Redis集群提高服务的可用性;
  3. 给缓存添加降级限流策略;
  4. 给业务添加多级缓存;

七、缓存击穿

缓存击穿也叫热点key问题,就是一个被高并发访问并且缓存重建业务较复杂的key失效了,无数的请求访问会在瞬间打到数据库,带来巨大压力。

1、通过互斥锁解决缓存击穿

给缓存重建过程加锁,确保重建过程只有一个线程执行,其它线程等待。

互斥锁的最大问题是,线程等待问题,性能较差。
在这里插入图片描述

2、根据id查询商品信息,基于互斥锁解决缓存击穿问题

在这里插入图片描述

3、通过逻辑过期解决缓存击穿

逻辑过期的优点是性能好,缺点是不保证一致性,有额外的内存消耗,实现复杂。
在这里插入图片描述

在这里插入图片描述

八、Redis工具类

// 解决缓存穿透
Goods goods = cacheClient.queryWithPassThrough(CACHE_GOODS_KEY, id, Goods.class, this::getById, CACHE_GOODS_TTL, TimeUnit.MINUTES);

// 互斥锁解决缓存击穿
Goods goods = cacheClient.queryWithMutex(CACHE_GOODS_KEY, id, Goods.class, this::getById, CACHE_GOODS_TTL, TimeUnit.MINUTES);

// 逻辑过期解决缓存击穿
Goods goods = cacheClient.queryWithLogicalExpire(CACHE_GOODS_KEY, id, Goods.class, this::getById, 20L, TimeUnit.SECONDS);
package com.guor.utils;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

@Slf4j
@Component
public class CacheClient {

    private final StringRedisTemplate stringRedisTemplate;

    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    public CacheClient(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public void set(String key, Object value, Long time, TimeUnit unit) {
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
    }

    public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {
        // 设置逻辑过期
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        // 写入Redis
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }

    public <R,ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit){
        String key = keyPrefix + id;
        // 1.从redis查询商铺缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        // 2.判断是否存在
        if (StrUtil.isNotBlank(json)) {
            // 3.存在,直接返回
            return JSONUtil.toBean(json, type);
        }
        // 判断命中的是否是空值
        if (json != null) {
            // 返回一个错误信息
            return null;
        }

        // 4.不存在,根据id查询数据库
        R r = dbFallback.apply(id);
        // 5.不存在,返回错误
        if (r == null) {
            // 将空值写入redis
            stringRedisTemplate.opsForValue().set(key, "", RedisConfig.CACHE_NULL_TTL, TimeUnit.MINUTES);
            // 返回错误信息
            return null;
        }
        // 6.存在,写入redis
        this.set(key, r, time, unit);
        return r;
    }

    public <R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
        String key = keyPrefix + id;
        // 1.从redis查询商铺缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        // 2.判断是否存在
        if (StrUtil.isBlank(json)) {
            // 3.存在,直接返回
            return null;
        }
        // 4.命中,需要先把json反序列化为对象
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
        LocalDateTime expireTime = redisData.getExpireTime();
        // 5.判断是否过期
        if(expireTime.isAfter(LocalDateTime.now())) {
            // 5.1.未过期,直接返回店铺信息
            return r;
        }
        // 5.2.已过期,需要缓存重建
        // 6.缓存重建
        // 6.1.获取互斥锁
        String lockKey = RedisConfig.LOCK_GOODS_KEY + id;
        boolean isLock = tryLock(lockKey);
        // 6.2.判断是否获取锁成功
        if (isLock){
            // 6.3.成功,开启独立线程,实现缓存重建
            CACHE_REBUILD_EXECUTOR.submit(() -> {
                try {
                    // 查询数据库
                    R newR = dbFallback.apply(id);
                    // 重建缓存
                    this.setWithLogicalExpire(key, newR, time, unit);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }finally {
                    // 释放锁
                    unlock(lockKey);
                }
            });
        }
        // 6.4.返回过期的商铺信息
        return r;
    }

    public <R, ID> R queryWithMutex(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
        String key = keyPrefix + id;
        // 1.从redis查询商铺缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        // 2.判断是否存在
        if (StrUtil.isNotBlank(json)) {
            // 3.存在,直接返回
            return JSONUtil.toBean(json, type);
        }
        // 判断命中的是否是空值
        if (json != null) {
            // 返回一个错误信息
            return null;
        }

        // 4.实现缓存重建
        // 4.1.获取互斥锁
        String lockKey = RedisConfig.LOCK_GOODS_KEY + id;
        R r = null;
        try {
            boolean isLock = tryLock(lockKey);
            // 4.2.判断是否获取成功
            if (!isLock) {
                // 4.3.获取锁失败,休眠并重试
                Thread.sleep(50);
                return queryWithMutex(keyPrefix, id, type, dbFallback, time, unit);
            }
            // 4.4.获取锁成功,根据id查询数据库
            r = dbFallback.apply(id);
            // 5.不存在,返回错误
            if (r == null) {
                // 将空值写入redis
                stringRedisTemplate.opsForValue().set(key, "", RedisConfig.CACHE_NULL_TTL, TimeUnit.MINUTES);
                // 返回错误信息
                return null;
            }
            // 6.存在,写入redis
            this.set(key, r, time, unit);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            // 7.释放锁
            unlock(lockKey);
        }
        // 8.返回
        return r;
    }

    private boolean tryLock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

    private void unlock(String key) {
        stringRedisTemplate.delete(key);
    }
}



NoSQL数据库进阶实战

NoSQL数据库进阶实战1,那些年学过的NoSQL基础

NoSQL数据库进阶实战2,NoSQL数据存储模式

哪吒精品系列文章

Java学习路线总结,搬砖工逆袭Java架构师

10万字208道Java经典面试题总结(附答案)

Java基础教程系列

Java高并发编程系列

数据库进阶实战系列
在这里插入图片描述

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

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

相关文章

基于局部特征和引导形状变形的重叠子宫颈细胞自动分割技术

注&#xff1a;该文为Automatic Segmentation of Overlapping Cervical Smear Cells based on Local Distinctive Features and Guided Shape Deformation的相关阅读注释和翻译 基于局部特征和引导形状变形的重叠子宫颈细胞自动分割技术 概述 提出了一种基于独特的局部特征和…

SpringBoot:模块探究之spring-boot-dependencies

在 SpringBoot 开发时&#xff0c;我们常常会发现一个现象&#xff1a;即在 pom 文件中&#xff0c;加入一个新的依赖&#xff0c;往往不需要引入相应的版本号&#xff08;如下代码块所示&#xff09;&#xff0c;就可以正常引入依赖&#xff0c;这其实是因为我们依赖了 spring…

mysql修改字段的长度是否会锁表

结论&#xff1a; 缩小字段长度不能使用inpalce&#xff0c;会锁表。 放大字段长度&#xff1a;取决于变化前和变化后是否跨越255这个长度。以UTF8编码为例&#xff0c;一个字符占3个字节。 字段变化1&#xff1a;varchar&#xff08;50&#xff09;--》varchar&#xff08;80…

嵌入式:ARM多寄存器存取指令详解

文章目录多寄存器存取指令的二进制编码指令汇编格式举例注意事项多寄存器传送指令可以用一条指令将16个可见寄存器&#xff08;R0~R15&#xff09;的任意子集合&#xff08;或全部&#xff09;存储到存储器或从存储器中读取数据到该寄存器集合中。如&#xff1a;可将寄存器列表…

李书福旗下亿咖通纳斯达克上市:作价38亿美元 路演PPT曝光

雷递网 雷建平 12月21日亿咖通科技控股&#xff08;简称“ECARX”、“亿咖通科技”&#xff09;今日与特殊目的公司COVA Acquisition Corp.&#xff08;简称“COVA”&#xff09;完成合并&#xff0c;并在美国纳斯达克股票市场&#xff08;Nasdaq Stock Market LLC&#xff09;…

[UE5]在多个固定摄像机视角间切换,切换多个摄像机,显示不同摄像机所看内容

[UE5]在多个固定摄像机视角间切换&#xff0c;切换多个摄像机&#xff0c;显示不同摄像机所看内容1.写在前面01.作者碎碎念02.结果演示截图演示视频视频教程源码链接03.实现思路实现思路04.同步的博客CSDN掘金博客园知乎2.需要准备的软件3.步骤大神步骤&#xff1a;详细步骤&am…

再写java探针

大家好&#xff0c;我是烤鸭&#xff1a; 以前写过一篇全链路探针实现的文章&#xff0c;最近同事间搞技术分享&#xff0c;再整理一篇。可惜这两年没有继续搞这方面的技术&#xff0c;算是两年前的拓展篇吧。很多技术只放了图&#xff0c;文字就不写了&#xff0c;可以参考…

笔记本加固态小白怎么设置

​最近有用户说电脑硬盘空间不够用了&#xff0c;于是问笔记本加固态小白怎么设置&#xff0c;打算将系统安装在这上面。但由于原先的系统盘有很多重要的数据&#xff0c;该用户就问到有没有什么办法不需要重装&#xff0c;关于笔记本加固态小白操作方法。 工具/原料&#xff…

Redis集群的三种方式详解(附优缺点及原理区别)

Redis提供了三种集群方式&#xff0c;下面我重点详解Redis三种集群方式的原理及优缺点等区别mikechen 目录 Redis主从复制模式Redis哨兵模式Redis集群模式 Redis主从复制模式 1.Redis主从复制定义 主从模式是三种模式中最简单的&#xff0c;主从模式指的是使用一个Redis实例…

Redis入门及Redis基本数据类型的相关命令

1.1、Redis简介 Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache, and message broker&#xff0c;翻译为: Redis是一个开源的内存中的数据结构存储系统&#xff0c;它可以用作∶数据库、缓存和消息中间件。官网: https:…

2023,AIGC能赚到钱吗?

2022年&#xff0c;AIGC&#xff08;生成式AI&#xff09;是当之无愧的网红。AI作画在各大社交平台刷屏&#xff0c;ChatGPT火爆国内外出尽了风头&#xff0c;依靠AI生成语音和表情、动作的数字人也频频露脸。2022年12月&#xff0c;Science杂志发布了2022年度科学十大突破&…

vue-element-template模板

vue-element-template模板 vue-element-admin是一个非常全面的系统&#xff0c;提供了很多现成的方案&#xff0c;我们可以在写项目的时候过来参考&#xff0c;但是也有缺点&#xff1a;不太好进行二次开发&#xff0c;因为要删减的东西太多了。于是就有另外一个子系统vue-elem…

Linux 安装nginx, 搭建nginx文件服务器

在linux安装nginx web 服务器&#xff0c;安装后再修改配置文件&#xff0c;将该主机作为一个文件服务器&#xff0c;最终效果如下图&#xff1a; 安装nginx 安装依赖包 yum install -y gcc pcre-devel zlib-devel openssl openssl-devel &#xff08;若安装的Redhat7&…

JVM数据结构一览及分析

JVM总体一张图&#xff1a; 整体上来看&#xff1a;类文件从类加载子系统&#xff0c;加载完成之后&#xff0c;主要存放在方法区&#xff08;JRockit和H9没有方法区&#xff0c;这里指的是HotSpot&#xff09;。运行时的数据主要是存放在运行时数据区&#xff0c;代码的解释编…

✿✿✿JavaScript --- 常见样式案例

目录 1.下拉框复选框单选框的事件样式 和 禁止喜欢中文字与鼠标右键 2.点击按钮后样式发生变化 3.表格追随鼠标更换颜色 4.点击更换背景 5.点击不同的选择栏&#xff0c;内容展示在一个板块内 6.排他思想&#xff0c;许多按钮&#xff0c;点击谁谁就有样式 7.按钮全选反…

微机原理接口

目录 接口的基本概念 什么是接口&#xff1f;(背会) 接口电路的功能&#xff1f;&#xff08;背会2019年 &#xff09; 接口电路基本结构 什么是端口 &#xff08;背&#xff09; 接口的控制原理 数据的传送方式&#xff08;知道并行串行的特点就可以&#xff09; 并行传…

校园二手交易WEB

开发工具(eclipse/idea/vscode等)&#xff1a; 数据库(sqlite/mysql/sqlserver等)&#xff1a; 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a; 方达影院系统总共分两个模块&#xff0c;用户模块和管理员模块&#xff0c; 用户模块&#xff1a;登录和注册功能&#…

【Pytorch_Geometric】(GCN)基本使用:数据集与邻接矩阵格式,图形(点)的可视化展示

声明:仅学习使用~ 目录 过程记录1、Graph Neural Networks2、数据集描述3、edge_index3、模型定义与训练方法4、输出特征展示5、训练模型(半监督,semi-supervised)过程记录 1、Graph Neural Networks 致力于解决不规则数据结构(图像和文本相对格式都固定,但是社交网络与…

陪诊系统app开发,一个应用可切换不同身份

随着人口老龄化&#xff0c;年轻人工作压力大&#xff0c;经常加班&#xff0c;或在外地定居&#xff0c;出差等原因&#xff0c;没时间陪长辈。对于一些有慢性疾病的困扰&#xff0c;需要长期陪伴而延伸出来的一个新型行业-陪诊。陪诊职业出现就被人认可是因为人们对这个职业有…

什么是多模态深度学习?有哪些应用场景?

深度多模态学习能够更全面地理解数据&#xff0c;在准确性和效率上均有大幅提升。但首先&#xff0c;什么是多模态深度学习&#xff1f;它有哪些应用场景&#xff1f;本文将从定义、应用与前景三个角度来回答这两个问题。随着深度神经网络的发展&#xff0c;深度学习也逐渐向多…