Redis缓存穿透雪崩击穿及解决

 

封装缓存空对象解决缓存穿透与逻辑过期解决缓存击穿工具类

@Slf4j
@Component
public class CacheClient {private final StringRedisTemplate stringRedisTemplate;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)));//写入RedisstringRedisTemplate.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);}//判断命中的是否是空值,不为null 就为“” 因为存入的为“”  解决缓存穿透if(json != null){return null;}//4.不存在,根据id查询数据库QueryWrapper<Shop> queryWrapper = new QueryWrapper<>();queryWrapper.eq("id", id);R r = dbFallback.apply(id);//5.不存在,返回错误if(r == null){//将空值写入redisstringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);return null;}//6.存在,写入redisthis.set(key, r, time, unit);//7.返回return r;}//创建指定上线的线程池private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);//逻辑过期解决缓存击穿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)) {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获取互斥锁boolean isLock = tryLock(LOCK_SHOP_KEY + id);//6.2判断是否获取互斥锁成功if(isLock){//6.3成功,开启独立线程,实现缓存重建CACHE_REBUILD_EXECUTOR.submit(() ->{try {//查询数据库R r1 = dbFallback.apply(id);//写入redisthis.setWithLogicalExpire(key, r1, time, unit);} catch (Exception e) {throw new RuntimeException(e);} finally {//释放锁unlock(LOCK_SHOP_KEY + id);}});}//6.4失败与成功,都返回过期的商铺信息return r;}//获取锁方法private boolean tryLock(String key){Boolean b = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);//方法的返回值为基本类型,需将b进行拆箱,拆箱过程中可能会出现空指针异常,所以要使用工具类return BooleanUtil.isTrue(b);//会进行自动拆箱(当传入的值是 null 时,它会返回 false。可以避免空指针异常,这里没用到)}//释放锁方法private void unlock(String key){stringRedisTemplate.delete(key);}
}

控制层调用

    @GetMapping("/{id}")public Result queryShopById(@PathVariable("id") Long id) {return shopService.queryById(id);}

服务层调用

Result queryById(Long id);
    @Overridepublic Result queryById(Long id){//缓存穿透
//        Shop shop = cacheClient.queryWithPassThrough(CACHE_SHOP_KEY, id ,Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);//互斥锁解决缓存击穿//Shop shop = queryWithMutex(id);//逻辑过期解决缓存击穿//缓存击穿测试时,需先用测试类添加数据到数据库Shop shop = cacheClient.queryWithLogicalExpire(CACHE_SHOP_KEY, id , Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);if(shop == null){return Result.fail("店铺不存在");}//返回return Result.ok(shop);}

常量工具类

public class RedisConstants {public static final String LOGIN_CODE_KEY = "login:code:";public static final Long LOGIN_CODE_TTL = 2L;public static final String LOGIN_USER_KEY = "login:token:";public static final Long LOGIN_USER_TTL = 36000L;public static final Long CACHE_NULL_TTL = 2L;public static final Long CACHE_SHOP_TTL = 30L;public static final String CACHE_SHOP_KEY = "cache:shop:";public static final String LOCK_SHOP_KEY = "lock:shop:";public static final Long LOCK_SHOP_TTL = 10L;
}

数据工具类

@Data
public class RedisData {private LocalDateTime expireTime;private Object data;
}

互斥锁解决缓存击穿

//互斥所解决缓存击穿public Shop queryWithMutex(Long id) {//1.从redis查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);//2.判断是否存在if (StrUtil.isNotBlank(shopJson)) {//3.存在,直接返回return JSONUtil.toBean(shopJson, Shop.class);}//判断命中的是否是空值,不为null 就为“”  解决缓存穿透if(shopJson != null){return null;}Shop shop = null;try {//4.实现缓存重建//4.1 获取互斥锁boolean isLock = tryLock(LOCK_SHOP_KEY + id);//4.2 判断是否获取成功if(!isLock){//4.3 失败,则休眠并重试Thread.sleep(50);return queryWithMutex(id);}//4.4 成功,根据id查询数据库QueryWrapper<Shop> queryWrapper = new QueryWrapper<>();queryWrapper.eq("id", id);shop = shopMapper.selectOne(queryWrapper);//模拟重建的延时Thread.sleep(200);//5.不存在,返回错误if(shop == null){//将空值写入redisstringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id,"",CACHE_NULL_TTL,TimeUnit.MINUTES);return null;}//6.存在,写入redisstringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);} catch (InterruptedException e) {throw new RuntimeException(e);} finally {//7.释放互斥锁unlock(LOCK_SHOP_KEY + id);}//7.返回return shop;}//获取锁方法private boolean tryLock(String key){Boolean b = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);//方法的返回值为基本类型,需将b进行拆箱,拆箱过程中可能会出现空指针异常,所以要使用工具类return BooleanUtil.isTrue(b);//会进行自动拆箱(当传入的值是 null 时,它会返回 false。可以避免空指针异常,这里没用到)}//释放锁方法private void unlock(String key){stringRedisTemplate.delete(key);}

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

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

相关文章

Word办公自动化的一些方法

1.Word部分内容介绍 word本身是带有格式的一种文档&#xff0c;有人说它本质是XML&#xff0c;所以一定要充分利用标记了【样式】的特性来迅速调整【格式】&#xff0c;从而专心编辑文档内容本身。 样式&#xff08;集&#xff09; 编号&#xff08;多级关联样式编号&#xff…

操作系统 | 学习笔记 | 王道 | 3.1 内存管理概念

3 内存管理 3.1 内存管理概念 3.1.1 内存管理的基本原理和要求 内存可以存放数据&#xff0c;程序执行前需要先放到内存中才能被CPU处理—缓和cpu和磁盘之间的速度矛盾 内存管理的概念 虽然计算机技术飞速发展&#xff0c;内存容量也在不断扩大&#xff0c;但仍然不可能将所有…

Kubernetes-环境篇-02-ubuntu开发环境搭建

1、ubuntu基础环境 # 更新apt软件源 sudo apt update# 安装git sudo apt install git# 安装python3 sudo apt install -y python3 python3-pip# 安装vim sudo apt install vim2、安装go 2.1 下载go安装包 wget https://golang.google.cn/dl/go1.23.2.linux-amd64.tar.gz2.2 …

【Qt】控件概述(7)—— 布局管理器

布局管理器 1. 布局管理器2. QVBoxLayout——垂直布局3. QHBoxLayout——水平布局4. QGridLayout——网格布局5. QFormLayout——表单布局6. QSpacer 1. 布局管理器 在我们之前值ui界面进行拖拽设置控件时&#xff0c;都是通过手动的控制控件的位置的。同时每个控件的位置都是…

OpenGL ES 纹理(7)

OpenGL ES 纹理(7) 简述 通过前面几章的学习&#xff0c;我们已经可以绘制渲染我们想要的逻辑图形了&#xff0c;但是如果我们想要渲染一张本地图片&#xff0c;这就需要纹理了。 纹理其实是一个可以用于采样的数据集&#xff0c;比较典型的就是图片了&#xff0c;我们知道我…

【STM32开发之寄存器版】(六)-通用定时器中断

一、前言 STM32定时器分类 STM32103ZET6具备8个定时器TIMx(x 1,2,...,8)。其中&#xff0c;TIM1和TIM8为高级定时器&#xff0c;TIM2-TIM6为通用定时器&#xff0c;TIM6和TIM7为基本定时器&#xff0c;本文将以TIM3通用定时器为例&#xff0c;分析STM32定时器工作的底层寄存器…

深度学习基础—残差网络ResNets

1.残差网络结构 当网络训练的很深很深的时候&#xff0c;效果是否会很好&#xff1f;在这篇论文中&#xff0c;作者给出了答案&#xff1a;Deep Residual Learning for Image Recognitionhttps://www.cv-foundation.org/openaccess/content_cvpr_2016/papers/He_Deep_Residual_…

EmEditor传奇脚本编辑器

主程序&#xff1a;EmEditor.exe 目前已有功能 可以自己指定一个快捷键 实现以下功能&#xff08;默认快捷键为&#xff1a;F1&#xff09; 以下全功能 都是鼠标所在行 按快捷键 &#xff08;默认快捷键&#xff1a;F1&#xff09; 1.在Merchant.txt中 一键打开NPC 没有…

Linux 外设驱动 应用 1 IO口输出

从这里开始外设驱动介绍&#xff0c;这里使用的IMX8的芯片作为驱动介绍 开发流程&#xff1a; 修改设备树&#xff0c;配置 GPIO1_IO07 为 GPIO 输出。使用 sysfs 接口或编写驱动程序控制 GPIO 引脚。编译并测试。 这里假设设备树&#xff0c;已经配置好了。不在论述这个问题…

Steam Deck掌机可装“黑苹果” 开发者成功安装macOS 15 Sequoia

在Steam Deck掌机上运行Windows 11相对轻松&#xff0c;但要让其成功搭载“黑苹果”系统则颇具挑战性。近日&#xff0c;有博主勇于尝试&#xff0c;将macOS 15 Sequoia安装到了Steam Deck上。 开发者kaitlyn在X平台上分享道&#xff1a;“在朋友们的鼎力相助下&#xff0c;我…

SQL专项练习第六天

Hive 在处理不同数据需求时的灵活性和强大功能&#xff0c;包括间隔连续问题的处理、行列转换、交易数据查询、用户登录统计以及专利数据分析等方面。本文将介绍五个 Hive 数据处理问题的解决方案&#xff0c;并通过实际案例进行演示。 先在home文件夹下建一个hivedata文件夹&a…

Unity Shader Graph基础包200+节点及术语解释

目录 Master Stack: Vertex block&#xff1a; Fragment block​编辑 Alpha Clip Threshold Dither transparency Graph Inspector Texture 2d Array/Texture 3d Virtual Texture Sampler State Keywords Boolean keyword 右键显示所有节点 简化测试系列节点&#x…

初入网络学习第一篇

引言 不磨磨唧唧&#xff0c;跟着学就好了&#xff0c;这个是我个人整理的学习内容梳理&#xff0c;学完百分百有收获。 1、使用的网络平台:eNSP 下载方法以及内容参考这篇文章 华为 eNSP 模拟器安装教程&#xff08;内含下载地址&#xff09;_ensp下载-CSDN博客https://b…

DBMS-3.4 SQL(4)——存储过程和函数触发器

本文章的素材与知识来自李国良老师和王珊老师。 存储过程和函数 一.存储过程 1.语法 2.示例 &#xff08;1&#xff09; 使用DELIMITER更换终止符后用于编写存储过程语句后&#xff0c;在下次执行SQL语句时记得再使用DELIMITER将终止符再换回分号。 使用DELIMITER更换终止符…

数据分布过于集中 怎么办,python 人工智能 ,数据分析,机器学习pytorch tensorflow ,

数据分布过于集中&#xff0c;意味着数据的大部分值都聚集在某个特定区间内&#xff0c;这可能会导致统计分析的结果不够稳健&#xff0c;或者模型训练时出现过拟合等问题。针对这种情况&#xff0c;可以考虑以下几种方法来处理&#xff1a; 变换成 1. **数据转换**&#xff1…

MySQL多表查询:列子查询

先看我的表数据 dept表 emp表 列子查询&#xff0c;也就是多列作为子查询去寻找一些问题 常用操作符&#xff1a;IN, NOT IN, ANY, SOME, ALL 1.查询 "销售部" 和 "市场部" 的所有员工的信息&#xff08;拆分成以下两个问题&#xff09; a. 查询"销…

基于STM32的数字温度传感器设计与实现

引言 STM32 是由意法半导体&#xff08;STMicroelectronics&#xff09;开发的基于 ARM Cortex-M 内核的微控制器系列&#xff0c;以其强大的处理能力、丰富的外设接口和低功耗著称&#xff0c;广泛应用于嵌入式系统设计中。在这篇文章中&#xff0c;我们将介绍如何基于 STM32…

如何用python抓取豆瓣电影TOP250

1.如何获取网站信息&#xff1f; &#xff08;1&#xff09;调用requests库、bs4库 #检查库是否下载好的方法&#xff1a;打开终端界面&#xff08;terminal&#xff09;输入pip install bs4, 如果返回的信息里有Successfully installed bs4 说明安装成功&#xff08;request…

OJ在线评测系统 微服务 OpenFeign调整后端下 nacos注册中心配置 不给前端调用的代码 全局引入负载均衡器

OpenFeign内部调用二 4.修改各业务服务的调用代码为feignClient 开启nacos注册 把Client变成bean 该服务仅内部调用&#xff0c;不是给前端的 将某个服务标记为“内部调用”的目的主要有以下几个方面&#xff1a; 安全性: 内部API通常不对外部用户公开&#xff0c;这样可以防止…

Linux操作系统——概念扫盲I

目录 虚拟机概念刨析 在那之前&#xff0c;询问什么是虚拟化&#xff1f; 现在来看看什么是虚拟机 虚拟机有啥好的 小差&#xff1a;那JVM也叫Java Virtual Machine&#xff0c;有啥区别呢&#xff1f; Reference 虚拟机概念刨析 我们下面来简单聊聊虚拟机这个概念。对于…