自定义Redis工具类(解决缓存穿透和击穿)

自己封装了一个Redis工具类,帮助完成开发中的缓存穿透,缓存击穿的问题。

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.time.LocalDateTime;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;/*** @description: Redis缓存工具类* @author: gunala* @date: 2024/3/23 20:02*/
@Component
public class RedisCache {private final StringRedisTemplate redisTemplate;public RedisCache(StringRedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}public static final Long CACHE_NULL_TTL = 1L;public static final String CACHE_LOCK_KEY = "lock:";//自定义线程池private final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5,10,500L,TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>(5),new ThreadPoolExecutor.CallerRunsPolicy());/*** 设置缓存数据,并指定过期时间* @param key 缓存键* @param value 缓存值* @param time 过期时间(单位:秒)*/public void set(String key,Object value,long time){redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value),time, TimeUnit.SECONDS);}/*** 设置缓存数据,并指定过期时间* @param key 缓存键* @param value 缓存值* @param time 过期时间* @param unit 时间单位*/public void set(String key,Object value,long time,TimeUnit unit){redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value),time,unit);}/*** 从 Redis 缓存中根据指定的键获取存储的对象* @param key 缓存键* @param type 对象的类型* @return 获取到的对象,如果不存在则返回null*/public <T> T get(String key,Class<T> type){String objectJson = redisTemplate.opsForValue().get(key);if (StrUtil.isBlank(objectJson)){return null;}return JSONUtil.toBean(objectJson,type);}/*** 删除指定key对应的缓存数据* @param key 待删除的缓存数据的key*/public void delete(String key){redisTemplate.delete(key);}/*** 缓存穿透处理方法,用于从缓存中获取数据,如果缓存中不存在,则调用指定函数生成数据并存入缓存* @param prefix 缓存键前缀* @param id 缓存数据的唯一标识* @param time 缓存过期时间(单位:分钟)* @param type 返回结果的类型* @param function 生成数据的函数* @return 获取到的数据,如果缓存中不存在且生成函数返回null,则返回null*/public <T,R> T cachePenetration (String prefix, R id, Long time, Class<T> type, Function<R,T> function){// 拼接缓存键名String key = prefix + id;// 从 Redis 中获取缓存数据String objectJson = redisTemplate.opsForValue().get(key);// 如果缓存数据不为空,则将 JSON 转换为指定类型对象并返回if (StrUtil.isNotBlank(objectJson)){return JSONUtil.toBean(objectJson, type);}// 如果缓存数据为空字符串,则返回 nullif ("".equals(objectJson)){return null;}// 调用传入的函数生成结果T result = function.apply(id);// 将结果转换为 JSON 格式并存入 Redis 缓存中,设置过期时间redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(result), time, TimeUnit.MINUTES);return result;}/*** 缓存击穿处理方法* @param prefix 缓存前缀* @param id 缓存标识* @param time 缓存过期时间* @param type 返回对象类型* @param function 处理函数* @return T 返回结果对象*/public <T,R> T cacheBreakdown (String prefix,R id, Long time, Class<T> type, Function<R,T> function){String key = prefix + id; // 拼接缓存keyString objectJson = redisTemplate.opsForValue().get(key); // 获取缓存中的数据// 判断缓存中是否有数据if (StrUtil.isNotBlank(objectJson)){return JSONUtil.toBean(objectJson,type); // 将缓存数据转换为指定类型并返回}// 缓存中无数据时的处理if ("".equals(objectJson)){return null; // 返回空值}T result = null; // 初始化返回结果对象try{Boolean lock = lock(CACHE_LOCK_KEY, CACHE_NULL_TTL); // 获取缓存锁if (lock){if (redisTemplate.opsForValue().get(key) != null){return JSONUtil.toBean(redisTemplate.opsForValue().get(key),type); // 返回缓存中的数据}result = function.apply(id); // 调用处理函数处理数据if (result == null){redisTemplate.opsForValue().set(key,"",time,TimeUnit.MINUTES); // 将空值缓存起来return null; // 返回空值}redisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(result),time,TimeUnit.MINUTES); // 将处理结果缓存起来}else {try {Thread.sleep(100); // 等待一段时间result = cacheBreakdown(prefix,id,time,type,function); // 递归调用缓存击穿处理方法} catch (InterruptedException e) {throw new RuntimeException(e); // 抛出异常}}}finally {unlock(CACHE_LOCK_KEY); // 释放缓存锁}return result; // 返回处理结果对象}/*** 根据逻辑时间进行缓存处理* @param prefix 缓存键前缀* @param id 缓存对象的唯一标识* @param time 缓存时间* @param type 返回结果的类型* @param function 处理缓存数据的函数* @return 返回处理后的缓存数据,如果缓存未命中或已过期则返回null*/public <T,R> T cacheBreakdownByLogicalTime (String prefix,R id,Long time, Class<T> type, Function<R,T> function){// 拼接缓存键String key = prefix + id;// 从Redis中获取缓存数据String objectJson = redisTemplate.opsForValue().get(key);if (StrUtil.isNotBlank(objectJson)){// 如果缓存数据不为空RedisData data = JSONUtil.toBean(objectJson, RedisData.class);if (data.getExpireTime().isAfter(LocalDateTime.now())){// 如果缓存未过期,则直接返回缓存数据return JSONUtil.toBean((JSONObject) data.getData(),type);}else {// 如果缓存已过期,则尝试获取缓存写锁boolean lock = lock(CACHE_LOCK_KEY, CACHE_NULL_TTL);if (lock){// 如果成功获取到锁,则在线程池中异步处理缓存数据threadPool.submit(()->{try {// 调用处理缓存数据的函数T result = function.apply(id);if (result == null){// 如果处理结果为空,则将空字符串写入缓存redisTemplate.opsForValue().set(key,"",time,TimeUnit.MINUTES);}else {// 如果处理结果不为空,则更新缓存数据RedisData redisData = new RedisData();redisData.setData(result);redisData.setExpireTime(LocalDateTime.now().plusSeconds(time));redisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(redisData),time,TimeUnit.MINUTES);}} catch (Exception e) {// 捕获异常并抛出运行时异常throw new RuntimeException(e);}finally {// 释放缓存写锁unlock(CACHE_LOCK_KEY);}});}}}// 返回null表示缓存未命中或已过期return null;}/*** 尝试在 Redis 中对指定 key 加锁* @param key 锁的键名* @param time 锁的超时时间(单位:分钟)* @return 加锁是否成功*/public boolean lock(String key, long time){// 调用 RedisTemplate 的 opsForValue 方法获取 Value 操作对象,然后调用 setIfAbsent 方法尝试在 Redis 中设置指定 key 的值为 "1",并设置超时时间Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", time, TimeUnit.MINUTES);// 返回加锁是否成功的布尔值,使用 BooleanUtil.isTrue 方法将 Boolean 对象转换为 boolean 类型return BooleanUtil.isTrue(flag);}/*** 从 Redis 缓存中删除指定的 key 对应的数据* @param key 要删除的数据的 key*/public void unlock(String key){redisTemplate.delete(key);}
}

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

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

相关文章

【模糊逻辑】Type-1 Fuzzy Systems-2

【模糊逻辑】Type-1 Fuzzy Systems 3.4.3 模糊化及其推理的影响3.4.3.1 Singleton Fuzzifier例3.5例3.6 3.4.3.2 Non-Singleton Fuzzifier例3.7 Non-Singleton Fuzzifier 量化求解 Firing Level 3.5 对规则触发&#xff08;Fired-Rule&#xff09;的输出集进行组合3.5.1Mamdani…

【linux】CentOS查看系统信息

一、查看版本号 在CentOS中&#xff0c;可以通过多种方法来查看版本号。以下是几种常用的方法&#xff1a; 使用cat命令查看/etc/centos-release文件&#xff1a; CentOS的版本信息存储在/etc/centos-release文件中。可以使用cat命令来显示该文件的内容&#xff0c;从而获得C…

力扣hot100:153. 寻找旋转排序数组中的最小值(二分的理解)

由力扣hot100&#xff1a;33. 搜索旋转排序数组&#xff08;二分的理解&#xff09;-CSDN博客&#xff0c;我们知道二分实际上就是找到一个策略将区间“均分”。对于旋转数组问题&#xff0c;在任何位置分开两个区间&#xff0c;如果原区间不是顺序的&#xff0c;分开后必然有一…

BRAM底层原理详细解释(1)

目录 一、原语 二、端口简述 2.1 端口简介 2.2 SDP端口映射 三、端口信号含义补充说明 3.1 字节写使能&#xff08;Byte-Write Enable&#xff09;- WEA and WEBWE&#xff1a; 3.2 地址总线—ADDRARDADDR and ADDRBWRADDR 3.3 数据总线—DIADI, DIPADIP, DIBDI, and D…

【c++初阶】C++入门(下)

✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨ &#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1…

AI元年,这5款AI写作能为你提供帮助

自从人工智能技术的迅猛发展以来&#xff0c;AI在各个领域都取得了巨大的进步。其中&#xff0c;AI写作工具成为越来越多人关注的焦点。在这个AI元年&#xff0c;小编想向大家分享5款可能对你有帮助的AI写作工具&#xff0c;如果你也想找AI写作相关的工具&#xff0c;那么来看看…

【数据结构基础】之八大排序(C语言实现)

【数据结构基础】之八大排序(C语言实现&#xff09; &#x1f427; 冒泡排序♈️ 冒泡排序原理及代码实现♈️ 稳定性分析 &#x1f427; 选择排序♈️ 选择排序原理及代码实现♈️ 稳定性分析 &#x1f427; 插入排序♈️ 插入排序的原理及代码实现♈️ 稳定性分析 &#x1f4…

《单例模式(极简c++)》

本文章属于专栏- 概述 - 《设计模式&#xff08;极简c版&#xff09;》-CSDN博客 本章简要说明单例模式。本文分为模式说明、本质思想、实践建议、代码示例四个部分。 模式说明 方案&#xff1a; 单例模式确保一个类只有一个实例&#xff0c;并提供一个全局访问点。优点&…

素数问题 python

# 输出1-100所有质数 import math count 0 for i in range(2, 101):is_prime True # 假设当前数是素数for j in range(2, int(math.sqrt(i))1):if i % j 0:is_prime False # 如果能被整除&#xff0c;不是素数break # 直接中断内层循环if is_prime:count 1print(i)prin…

(附源码)基于Spring Boot和Vue的智能订餐与外卖系统设计与实现

1. 引言 这部分通常包含了研究背景、研究意义、国内外研究现状、本文研究内容以及论文结构安排。 研究背景&#xff1a;介绍当前外卖市场的快速发展&#xff0c;以及智能订餐系统对改善人们生活的影响。研究意义&#xff1a;强调这类系统在现代生活中的作用和开发的创新点。国…

Kubernetes一文上手【手把手系列】

目录 Kubernetes前言部署方式的演变 K8S概述K8S架构Master节点1. API Server2. Etcd3. Controller Manager4. Scheduler Node节点1. kubelet2. kube-proxy3. 容器运行时 组件与插件1. Kubernetes DNS2. Dashboard3. Heapster4. Ingress Controller K8S核心概念PodSerivceNamesp…

CodeSys创建自定义的html5控件

文章目录 背景创建html5control.xml文件控件界面以及逻辑的实现使用的资源安装自定义的html5控件库 背景 查看官方的资料&#xff1a;https://content.helpme-codesys.com/en/CODESYS%20Visualization/_visu_html5_dev.html 官方的例子&#xff1a;https://forge.codesys.com/…

使用 PyOpenGL 进行 2D 图形渲染总结

一、说明 OpenGL是一个广泛使用的开放式跨平台实时 3D 图形库&#xff0c;开发于二十多年前。它提供了一个低级API&#xff0c;允许开发人员以统一的方式访问图形硬件。在开发需要硬件加速且需要在不同平台上运行的复杂 2D 或 3D 应用程序时&#xff0c;它是首选平台。它可以在…

liunx centos7 下通过yum删除安装已经安装的php

执行下面命令查看php相关的包 rpm -qa | grep php 只需要卸载几个名为common的包即可&#xff0c;其他同版本依赖会被全部删除&#xff0c;删除php71w-common&#xff0c;71w版本的依赖包全部会被删除。 查看php包的命令 rpm -qa | grep php 或 yum list installed | gre…

unity编辑器扩展高级用法

在PropertyDrawer中&#xff0c;您不能使用来自GUILayout或EditorGUILayout的自动布局API&#xff0c;而只能使用来自GUI和EditorGUI的绝对Rect API始终传递相应的起始位置和维度。 你需要 计算显示嵌套内容所需的总高度将此高度添加到public override float GetPropertyHeig…

实用工具推荐:适用于 TypeScript 网络爬取的常用爬虫框架与库

随着互联网的迅猛发展&#xff0c;网络爬虫在信息收集、数据分析等领域扮演着重要角色。而在当前的技术环境下&#xff0c;使用TypeScript编写网络爬虫程序成为越来越流行的选择。TypeScript作为JavaScript的超集&#xff0c;通过类型检查和面向对象的特性&#xff0c;提高了代…

uniapp ios端使用fixed定位导致输入时页面滚动简单解决方法

当移动端使用fixed定位自定义nav栏时&#xff0c;安卓端正常固定在可视窗顶部&#xff0c;但是ios端当有input输入&#xff0c;弹出软键盘时&#xff0c;会将nav顶出可视区&#xff0c;因为在ios上&#xff0c;不是相对于浏览器窗口定位的&#xff0c;而是相对于最近的可滚动区…

Vue常用指令介绍

Vue指令&#xff1a; 指令带有前缀 v- 开头&#xff0c;以表示它们是 Vue 提供的特殊属性。 v-text&#xff0c;v-html&#xff1a; html&#xff1a; <div id"ddd"><!-- {{插值表达插入变量&#xff0c;不会覆盖标签体中的内容}}v-text,v-html会覆盖掉标…

Linux :环境基础开发工具

目录: 1. Linux 软件包管理器 yum 1. 什么是软件包 2. 查看软件包 3. 如何安装软件 4. 如何卸载软件 2. Linux开发工具 1. Linux编辑器-vim的基本概念 2. vim使用 3. vim的基本操作 4. vim正常模式命令集 5. vim末行模式命令集 6. 简单vim配置 3. Linux编译器-gcc/…

博世全球首个高阶智能驾驶项目量产 ,由腾讯云提供专有云支持

近日&#xff0c;博世全球首个高阶智能驾驶项目——奇瑞星途星纪元项目成功量产。在奇瑞星途星纪元ES最新向用户推送的OTA内容中&#xff0c;NEP高速领航系统正式上线。该系统采用全新人机共驾策略&#xff0c;可实现高速端到端的自动驾驶。 该系统由博世智能驾驶与控制系统事…