【Redis】缓存(下)

news2024/12/27 10:18:17

经过缓存这篇文章的概述,已经对缓存有了初步的了解和认知。在本篇文章中,主要是通过代码来实现缓存的应用,以及在使用缓存过程中出现的经典问题。

简单应用

需求:根据菜品id来查询缓存

流程:① 从缓存中查询,如果菜品存在,那么直接返回;② 缓存中不存在,从数据库中查询;③ 如果存在,那么写入缓存并返回;④ 不存在就直接返回一个null。

@Service
public class DishService extends ServiceImpl<DishMapper, Dish> {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    public Dish queryDishById(Long id) {
        // TODO 在Redis中查询该菜品是否存在
        String dishJson = this.stringRedisTemplate.opsForValue().get(Constants.DISH_KRY + id);
        // TODO 判断是否存在
        if(StrUtil.isNotBlank(dishJson)) { // StrUtil.isNotBlank() 表示不为null,不为空字符串,不为不可见字符
            // 存在直接返回
            return JSONUtil.toBean(dishJson, Dish.class);
        }
        // TODO 不存在查询数据库
        Dish dish = this.getById(id);
        // TODO 数据库也不存在就直接返回,看业务需求
        if(dish == null) {
            return null;
        }
        // TODO 数据库存在就写入缓存
        this.stringRedisTemplate.opsForValue().set(Constants.DISH_KRY + id, JSONUtil.toJsonStr(dish));
        return dish;
    }

}

缓存预热

设想某个服务运行很长时间之后,为了优化该服务,将Redis接入服务之中。此时,Redis服务器之中是没有任何数据的。因此服务都会跳过Redis直接打入到MySQL之中。随着时间的积累,Redis上积累足够多的数据之后,MySQL的压力才会减少。

缓存预热就是把定期生产和实时生成两种更新策略进行结合,当Redis未接入系统之前,就先统计一些热点数据,将其导入到Redis中,然后再把Reids服务给接入到整个系统之中。通过这种方式,即使Redis之中的数据不是最热的,但是也可以减少很大一部分MySQL的压力。随着时间的推移,Redis之中旧的热点数据就会被淘汰,新的热点数据就会生成。

缓存穿透

客户端发来的查询请求,先会在Redis之中进行查询。如果发现没有,就会去MySQL之中进行查询。如果MySQL之中也没有,并且像这样的请求很多,那么就会给MySQL造成不小的影响。

产生原因

1. 业务设计不合理,比如缺少必要的参数验证,导致非法的key也被删除了。

2. 开发/运维操作错误,不小心把部分数据给删除了。

3. 黑客恶意攻击。

解决方案

1. 当客户端发送的请求在Redis和MySQL都不存在时,仍然写入Redis,但是把value设定成一个非法值。

    优点是实现简单,维护方便。缺点是造成Redis额外的内存消耗(毕竟把没有用的数据给插入到了Reids之中),并且可能造成短期的不一致(例如在Redis中设置了一个非法值,但是此时MySQL更新了数据,这就导致了数据库和Redis的值不一致的情况出现。不过对于这种情况,可以给非法值设置一个过期时间,并且在更新数据库的同时把Reids之中的数据给删除,等到再次查询时再写入缓存)。

2. 引入布隆过滤器:每次查询Redis之前,都先判断一下key是否再布隆过滤器中存在。所谓的布隆过滤器,就是使用hash + bitmap的思想,因此可以使用比较小的空间开销,以及比较快的速度,来判断某个key是否存在于布隆过滤器之中。对于该业务背景来说,就是把所有的key插入布隆过滤器之中,然后进行查询时,先判断是否存在于布隆过滤器之中即可。

    优点是内存占用较少,不存在多余的key。缺点则是实现复杂,并且存在误判的可能。

简单案例

该代码案例中,使用的是第一种解决方法,把key写入到缓存中,并给value设定一个非法值。

public Dish queryDishByIdOfCachePenetration(Long id) {
        // TODO 在Redis之中查询该菜品是否存在
        String dishJson = this.stringRedisTemplate.opsForValue().get(Constants.DISH_KRY + id);
        // TODO 判断是否存在
        if(StrUtil.isNotBlank(dishJson)) {
            // 菜品存在,直接返回缓存信息
            return JSONUtil.toBean(dishJson, Dish.class);
        }
        // TODO 判断获取到的是否是非法值,从而解决缓存穿透问题
        if(dishJson != null) {
            // 由于在isNotBlank方法之中已经判断其不为null,不为空字符串,不为不可见字符进而明白其是菜品信息
            // 因此,当走到这一步并且不为null时,就可以判断,其是非法值
            return null;
        }
        // TODO 从数据库之中获取菜品
        Dish dish = this.getById(id);
        // TODO 判断数据库返回的是否是空值
        if(dish == null) {
            // 数据库之中菜品也为空时,表示该id是一个非法值,存到Redis之中,并返回
            this.stringRedisTemplate.opsForValue().set(Constants.DISH_KRY + id, "", 2, TimeUnit.MINUTES);
            return null;
        }
        // 数据库不为空时,将其写入缓存之中,返回菜品内容
        this.stringRedisTemplate.opsForValue().set(Constants.DISH_KRY + id, JSONUtil.toJsonStr(dish), 30, TimeUnit.SECONDS);
        return dish;
    }

 缓存雪崩

缓存雪崩是指在短时间内,Redis服务上的key大规模失效或Redis服务宕机,导致缓存命中率陡然下降。这就肯定会使得MySQL压力陡然上升,甚至直接宕机。

产生原因

1. Redis服务宕机了

2. Redis服务中大量的key到达过期时间,然后失效了

解决方案

1. 加强监控报警,从而增加Redis服务的可用性。

2. 给key设置过期时间时添加随机因子,从而避免同一时期过期。

3. 利用Redis集群提高服务的可用性。

4. 给缓存业务添加降级限流策略。

5. 给业务添加多级缓存。

缓存击穿

缓存击穿问题也称作热点key问题,即一个被大量请求访问且缓存重建业务较复杂的key突然失效了,导致无数的请求直接打到数据库之中,在瞬间给与了数据巨大的冲击。

解决方案

1. 基于统计的方式发现热点key,并设置永不过期。

2. 使用互斥锁的方式来进行查询。即当查询缓存未命中时,先获取互斥锁,然后再去访问数据库进行重建缓存。这样就不会造成所有的请求都直接打到数据库之中,从而避免了数据库的宕机。

    优点是没有额外的内存消耗,保证一致性,实现简单。缺点是线程需要额外等待,性能受到影响,同时存在死锁的情况。

3. 采用逻辑过期的方式来进行查询。即当查询缓存时,发现已经到了逻辑过期时间之后,这时就先获取互斥锁,然后开启新的线程去进行缓存重建并重新设置过期时间。在缓存重建的过程中,有其他请求来访问时,就返回已经过期的数据。

    优点是线程无需等待,性能较好。缺点是不保证一致性,有额外的内存消耗,实现复杂。

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

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

相关文章

『功能项目』怪物的信息显示【15】

本章项目成果展示 我们打开上一篇14怪物反击主角复活的项目&#xff0c; 本章要做的事情是当主角进入怪物的攻击范围之内时显示怪物的血量信息 在Canvas创建一个空物体 将空物体GameObject钉到视角上方 重命名为KingInfoUI 在子级创建一个Image 在资源文件夹下创建一个空文件命…

【数据结构 | 每日一题】图的概念辨析

图的概念辨析 考点分析&#xff1a;我们学习数据结构图的第一小节就是&#xff1a;图的基本概念&#xff0c;我们会发现图的概念非常多且有些概念之间又很像&#xff0c;而对于初学者来说&#xff0c;相比树的概念是不好理解的&#xff0c;很容易搞混&#xff0c;因此做了这么…

Tapd敏捷开发平台的使用心得

Tapd敏捷开发平台的使用心得 一、Tapd 简介 TAPD(Tencent Agile Product Development),腾讯敏捷产品研发平台行业领先的敏捷协作方案,贯穿敏捷产品研发生命周期的一站式服务,了解敏捷如下图 二、几个核心模块概念 需求迭代缺陷故事墙前期项目需求的管理,可以按类别建…

如何交叉编译 Linux v4l-utils 依赖库?

本文将以在 Ubuntu 20.04 系统的 x86_64 机器上&#xff0c;为 aarch64 架构的嵌入式机器&#xff0c;交叉编译 v4l-utils&#xff08;libv4l&#xff09;为例&#xff0c;进行介绍。 安装编译 v4l-utils 所需的依赖库。 $ apt update $ apt install make automake libtool安…

【docker】docker 简介

docker 简介 是什么虚拟化、容器化案例为什么要虚拟化、容器化&#xff1f; 是什么虚拟化、容器化 物理机&#xff1a;实际的服务器或者计算机。相对于虚拟机而言的对实体计算机的称呼。物理机提供给虚拟机以硬件环境&#xff0c;有时也称为“寄主”或“宿主”。 虚拟化&…

【时间盒子】-【3.新建项目】创建元服务工程,选择默认模板Empty Ability

一、新建项目 1、打开DevEco Studio >>文件>>新建>>新建项目&#xff0c;选择元服务 Atomic Service>>默认模板Empty Ability。 2、设置元服务的工程名称、Bundle name等&#xff0c;模型选Stage&#xff0c;语言是ArkTS&#xff0c;支持的设备类型只…

异步并发处理利器:在 Jupyter Notebook 中玩转 asyncio

asyncio.run报错 RuntimeError: asyncio.run() cannot be called from a running event loop 在jupyter notebook中运行下述代码就会出现上述报错 import uvicorn from fastapi import FastAPI from fastapi.responses import JSONResponse app FastAPI() app.get(“/”) d…

Openwrt 安装 AX210 无线网卡

安装 TTYD 我安装的是官方原版的 Openwrt&#xff0c;首先需要安装 YYTD 来从网页控制 Openwrt。 安装驱动 参考这个链接&#xff0c;跟着做。 iwlwifi-firmware-ax210 我买设备技术提供代码如下&#xff0c;但是没有安装 成功&#xff0c;中间报错。 opkg update opkg i…

<el-table> 把表格内同一列相同的数据合并为一行

<el-table> 把表格内同一列相同的数据合并为一行 具体效果如下图&#xff1a; 参考代码&#xff08;可直接运行&#xff09;&#xff1a; <!DOCTYPE html> <html> <head><meta charset"UTF-8"/><meta name"viewport" co…

SpringBoot中基于MongoDB的findAndModify原子操作实现分布式锁原理详解

❃博主首页 &#xff1a; 「码到三十五」 &#xff0c;同名公众号 :「码到三十五」&#xff0c;wx号 : 「liwu0213」 ☠博主专栏 &#xff1a; <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关> ♝博主的话 &#xff1a…

html简单注册页面

简单的html注册页面&#xff0c;代码如下&#xff0c;效果如下 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0">&l…

Linux | 匿名管道和命名管道:进程间通信数据流的桥梁

目录 1、进程间通信目的 2、管道——匿名管道和命名管道 匿名管道 匿名管道的示例代码&#xff1a;将数据写入管道、子进程从管道读取数据并将其输出到bash中 父子进程通过匿名管道建立通信 重点&#xff1a;管道的五个特点 命名管道&#xff08;也称为FIFO&#xff09;…

Java项目:128 基于Spring Boot的装饰工程管理系统

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 本系统包含管理员、员工和客户角色 管理员权限操作的功能包括管理合同信息&#xff0c;管理合同报价&#xff0c;管理立项项目&#xff0c;管…

机器学习:opencv--图像边缘检测

目录 前言 一、图像边缘检测 1.边缘检测 2.边缘检测的方法 二、Sobel算子 1.Sobel算子 2.计算 3.代码实现 4.代码步骤解析 1.导入图片 2.处理x轴和y轴的边缘并相加 三、Scharr算子 1.Scharr算子 2.计算 3.代码实现 四、Laplacian算子 1.Lapla…

本地部署训练、测试controlnet的完整过程(包括报错和代码)

文章目录 参考内容一、训练流程下载相关文件① 需要下载的权重文件② 下载数据集 二、训练代码两种训练方式&#xff1a;①采用.sh文件②常规的训练 测试代码生成的结果 四、报错NVIDIA的驱动太老需要更新生成出全黑图 参考内容 diffusers库提供的官方训练代码 利用到的fill50…

Vue(六) render函数、Vue.config.js配置文件,ref属性,props配置项、mixin混入、插件、scoped

文章目录 一、render函数二、Vue.config.js配置文件三. ref属性四. props配置项五. mixin混入1. 局部混入2. 全局混入 六. 插件七. scoped 一、render函数 在main.js文件中&#xff0c;采用了render函数。 import App from ./App.vuenew Vue({// 这句代码的意思是将App组件放…

Springboot整合J2cache实现声明式缓存方案

Springboot整合J2Cache 一、J2Caceh多级缓存 ​ J2Cache 是 OSChina 目前正在使用的两级缓存框架&#xff08;要求至少 Java 8&#xff09;。 ​ 第一级缓存使用内存(同时支持 Ehcache 2.x、Ehcache 3.x 和 Caffeine)&#xff0c;第二级缓存使用 Redis(推荐)/Memcached 。 L…

【生日视频制作】红色跑车法拉利提车交车仪式感广告展示牌AE模板修改文字软件生成器教程特效素材【AE模板】

生日视频制作教程红色跑车法拉利提车交车仪式感广告展示牌AE模板修改文字特效广软件告生成神器素材祝福玩法AE模板工程 怎么如何做的【生日视频制作】红色跑车法拉利提车交车仪式感广告展示牌AE模板修改文字软件生成器教程特效素材【AE模板】 生日视频制作步骤&#xff1a; 安…

Java小项目IDEA怎么打成jar包

使用IDEA打jar包 在file选项中找 打开jar包所在位置&#xff1a; 将jar包拿出来 直接点击jar包就可以运行

golang关于slice map函数传参的小问题

问题 函数传参了一个slice&#xff0c;在函数内触发了对长度的修改&#xff08;添加或删除&#xff09;&#xff0c;但是未影响函数外的实参由此产生了另一个问题&#xff0c;我们用map在函数内修改会不会有影响不到实参的情况&#xff1f; 结论 map作为函数参数时是引用传递…