【Redis】封装Redis缓存工具解决缓存穿透与缓存击穿问题

news2024/11/17 9:45:30

基于StringRedisTemplate封装一个缓存工具,主要有一下几个方法

方法1:将任意Java对象序列化为json并存储在String的指定key中且设置TTL

方法2:将任意Java对象序列化为json并存储在String的指定key中,并可以设置逻辑过期时间,用户处理缓存击穿问题

方法3:根据指定的key进行查询缓存,并反序列化为指定类型,利用缓存空值的办法解决缓存穿透问题

方法4:根据指定的key进行查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题

此处使用到的互斥锁加锁解锁方法,在前面的文章有提及【Redis】Java通过redis实现简单的互斥锁_1373i的博客-CSDN博客icon-default.png?t=N3I4https://blog.csdn.net/qq_61903414/article/details/130509874?spm=1001.2014.3001.5501



import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

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

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

    public RedisData(LocalDateTime expireTime, Object data) {
        this.expireTime = expireTime;
        this.data = data;
    }
}

/**
 * 封装解决redis 缓存击穿、缓存雪崩、缓存穿透等问题的方法
 */
@Component
@Slf4j
public class RedisUtil {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 存入时设置过期时间
     * @param key
     * @param value
     * @param time
     * @param unit
     */
    public void set(String key, Object value, Long time, TimeUnit unit) throws JsonProcessingException {
        stringRedisTemplate.opsForValue().set(key,objectMapper.writeValueAsString(value),time,unit);
    }

    /**
     * 存入时设置逻辑过期
     * @param key
     * @param value
     * @param time
     * @param unit
     */
    public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) throws JsonProcessingException {
        RedisData data = new RedisData(LocalDateTime.now().plusSeconds(unit.toSeconds(time)),value);
        stringRedisTemplate.opsForValue().set(key,objectMapper.writeValueAsString(data),time,unit);
    }

    /**
     * 查询缓存,缓存穿透时缓存null解决
     * @param id  要查询的id
     * @param keyPrefix   redis key的前缀
     * @param type        要返回的类型
     * @param dbFallback  查询数据库的方法的结果
     * @param <R>
     * @param <I>
     * @return
     * @throws JsonProcessingException
     */
    public <R,I> R getWithPassThrough(I id, String keyPrefix, Class<R> type, Function<I,R> dbFallback,Long time,TimeUnit unit) throws JsonProcessingException {
        // 生成redis中的key
        String key = keyPrefix + id;

        // 查询redis
        String json = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(json)) {
            // 存在缓存,则返回
            return objectMapper.readValue(json,type);
        }

        // 不存在时,判断是否是空值
        if (json != null) {
            // 返回错误信息
            return null;
        }

        // 查询数据库
        R result = dbFallback.apply(id);
        // 判空
        if (result == null) {
            // 发送缓存穿透,缓存null
            stringRedisTemplate.opsForValue().set(key,"",time,unit);
            return null;
        }

        // 存在则重建缓存
        stringRedisTemplate.opsForValue().set(key,objectMapper.writeValueAsString(result),time,unit);
        return result;
    }

    /**
     * 查询缓存,逻辑过期后异步重建缓存
     * @param id
     * @param keyPrefix
     * @param type
     * @param dbFallback
     * @param time
     * @param unit
     * @param <R>
     * @param <I>
     * @return
     * @throws JsonProcessingException
     */
    public <R,I> R get(I id, String keyPrefix,Class<R> type,Function<I,R> dbFallback,Long time,TimeUnit unit) throws JsonProcessingException {
        // 构建查询redis的key
        String key = keyPrefix + id;

        // 查询redis
        String json = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(json)) {
            // 存在该缓存,直接返回
            return objectMapper.readValue(json,type);
        }

        // 反序列化转为RedisData对象
        RedisData data = objectMapper.readValue(json,RedisData.class);
        // 获取数据
        R r = JSONUtil.toBean((JSONObject) data.getData(),type);
        // 获取逻辑时间
        LocalDateTime expireTime = data.getExpireTime();

        // 判断是否过期
        if (expireTime.isAfter(LocalDateTime.now())) {
            // 没有过期
            return r;
        }

        // 到达此处已过期,重建缓存:加互斥锁--》查询数据库--》重建--》释放互斥锁
        // 1.获取互斥锁
        String lockKey = id + "_lock";
        boolean isLock = LockByRedis.tryLock(lockKey);

        if (isLock) {
            // 2.加锁成功,开启线程重建缓存
            new Thread(){
                @Override
                public void run() {

                    try {
                        // 3.查询数据库
                        R db = dbFallback.apply(id);
                        // 4.重建
                        stringRedisTemplate.opsForValue().set(key,objectMapper.writeValueAsString(db),time,unit);
                    } catch (JsonProcessingException e) {
                        e.printStackTrace();
                    } finally {
                        // 5.释放互斥锁
                        LockByRedis.unlock(lockKey);
                    }
                }
            }.start();
        }

        // 返回逻辑过期的数据
        return r;
    }
}

难点:在重建缓存时,我们需要去查询数据库,而查询数据库不同的reids缓存重建所需要查询的数据库表可能不同,其方法也可能不同。

解决:通过Function<参数类型,返回值>来把该查询数据库的方法交给方法的调用者进行传入

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

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

相关文章

【python可视化】常用数据类型

&#x1f64b;‍ 哈喽大家好&#xff0c;本次是python数据分析、挖掘与可视化专栏第二期 ⭐本期内容&#xff1a;常用数据类型 &#x1f3c6;系列专栏&#xff1a;Python数据分析、挖掘与可视化 &#x1f44d;欢迎大佬指正&#xff0c;一起学习&#xff0c;一起加油&#xff01…

【Frida-实战】EA游戏平台的文件监控(PsExec.exe提权)

▒ 目录 ▒ &#x1f6eb; 问题描述环境 1️⃣ 代码编写开源代码搜索自己撸代码procexp确定句柄对应的文件名并过滤 2️⃣ PsExec.exe提权定位找不到EABackgroundService.exe的问题 PsExec.exe提权PsExec.exe原理 &#x1f6ec; 结论&#x1f4d6; 参考资料 &#x1f6eb; 问题…

4年Android开发,面试通过全靠狂刷这份面试题,从11K涨到25K+(内含答案)

在博主认为&#xff0c;对于Android面试以及进阶的最佳学习方法莫过于刷题博客书籍总结&#xff0c;前三者博主将淋漓尽致地挥毫于这篇博客文章中&#xff0c;至于总结在于个人&#xff0c;实际上越到后面你会发现面试并不难&#xff0c;其次就是在刷题的过程中有没有去思考&am…

【Python】序列类型③-集合

文章目录 1.集合(set)简介2.集合的定义3.集合的遍历4.集合的常用方法 1.集合(set)简介 集合是一种无序可变的容器对象 集合最大的特点:同一个集合内元素是不允许有重复的,因此集合自带"去重"效果 2.集合的定义 集合的定义有两种方式: 使用{}进行定义,这种方式不能定…

【TCP 重传、滑动窗口、流量控制、拥塞控制】

文章目录 重传机制超时重传快速重传SACK方法Duplicate SACK 滑动窗口流量控制那操作系统的缓冲区&#xff0c;是如何影响发送窗口和接收窗口的呢&#xff1f;窗口关闭 拥塞控制慢启动拥塞避免拥塞发生快速恢复 重传机制 TCP 实现可靠传输的方式之一&#xff0c;是通过序列号与…

chatgpt可以降重论文吗-chatgpt降重论文软件

chatgpt可以降重论文吗 ChatGPT是一种自然语言处理技术&#xff0c;可以生成符合指定条件的文本。因此&#xff0c;理论上可以使用ChatGPT来降重论文。但是&#xff0c;需要注意以下几点&#xff1a; 是否符合学术道德要求&#xff1a;学术论文的降重需要严格遵守学术道德准则…

mfc140u.dll丢失怎么解决?,哪种方法更简单?

如果您在运行 Windows 操作系统时遇到了“mfc140u.dll 丢失”或“找不到 mfc140u.dll”等错误提示&#xff0c;那么这意味着您的计算机遗失了该文件。mfc140u.dll 文件是 Microsoft Visual C 的一部分&#xff0c;是支持应用程序运行所必需的。无论是什么原因导致了 mfc140u.dl…

PointNetGPD<论文>

摘要 提出了一种端到端的抓取位置预测模型&#xff0c;能够从点云中估计出机器人的抓取位姿。网络以原始点云作为输入&#xff0c;能够捕捉到抓取器闭合区域点云的复杂几何结构&#xff0c;即使这些点云很稀疏。 PointNetGPD是一种轻量级的网络模型&#xff0c;能够处理抓取器…

手把手教你在Centos7.6系统安装mysql5.7

文章目录 1 查看linux系统版本2 官网获取文件3 wget下载4 安装yum源5 查看是否正常工作6 安装mysql服务6.1出错6.2解决方法 7 检查配置文件8 启动mysql服务9 root第一次登录10 其他设置10.1 修改密码10.2 开启开机自启动10.3 配置my.conf 1 查看linux系统版本 需要安装对应系统…

Docker安装、Docker基本操作

一、Dokcer安装 1.安装 # 1、yum 包更新到最新,需要几分钟时间(注意:也可以直接跨过) sudo yum update # 2、作用&#xff1a;安装需要的软件包&#xff0c; yum-util 提供yum-config-manager功能&#xff0c;另外两个是devicemapper驱动依赖的 sudo yum install -y yum-util…

从文字到语义:文本分词和词性标注的原理与实现

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

个人博客系统调试详细过程

系统功能的详细说明和源代码见以下链接:https://blog.csdn.net/shooter7/article/details/121180333相关的源码数据库文件、软件安装包可以联系博主koukou(壹壹23七2五六98) 调试过程如下&#xff1a; 文章目录 调试过程如下&#xff1a;一、数据库安装二、sql数据文件的导入三…

java调用cmd命令

1.首先&#xff0c;我们需要了解一下 java是如何调用 cmd的&#xff1a; 6.在实际的开发中&#xff0c;我们有可能会遇到 java调用 cmd命令的情况&#xff1a; 7.对于一些特定的环境下&#xff0c;例如在嵌入式系统中&#xff0c;那么我们可以使用下面这种方式来调用 cmd命令&a…

【Redis】Redis常用基本命令

数据结构 常用基本命令 keys * 查看当前库所有的key exists key 判断某个key是否存在 type key 查看key的类型 del key 删除指定的key数据 unlink key :非阻塞删除&#xff0c;仅仅将keys从key元数据删除&#xff0c;真正的删除会异步操作 6.ttl key 查看key多少秒过期…

AIGC:ColossalChat(基于LLM和RLHF技术的类似ChatGPT的聊天机器人)的简介、安装、使用方法之详细攻略

AIGC&#xff1a;ColossalChat(基于LLM和RLHF技术的类似ChatGPT的聊天机器人)的简介、安装、使用方法之详细攻略 目录 ColossalChat的简介 1、局限性 LLaMA-finetuned 模型的限制 数据集的限制 2、在线演示 3、Coati7B examples Generation Open QA ColossalChat的安装…

功率电子开关的高边开关和低边开关high -side power switch

下图为使用NMOS&#xff0c;最简单的开关电路。&#xff08;低侧驱动&#xff09; CONTROL为控制信号&#xff0c;电平一般为3~12V。负载一端接电源正极&#xff0c;另一端接NMOS的D&#xff08;漏极&#xff09;。CONTROL电平为高时&#xff0c;Vgs>NMOS的Vgs导通阀值&…

数据库基础应用——概念模型

1、实体(Entity) 客观存在并可相互区别的事物称为实体。实体可以是人、物、对象、概念、事物本身、事物之间的联系。&#xff08;例如一名员工、一个部门、一辆汽车等等。&#xff09; 2、属性(Attributre) 实体所具有的每个特性称为属性。&#xff08;例如&#xff1a;员工由员…

基于OpenCV的haar分类器实现人脸检测分析

基于OpenCV的haar分类器实现人脸检测分析 文章目录 基于OpenCV的haar分类器实现人脸检测分析一、基于OpenCV的haar分类器实现笑脸检测1、Haar分类器介绍2、haar分类器的静态使用&#xff08;处理图片&#xff09;3、haar分类器的动态使用&#xff08;对摄像头视频进行处理&…

JAVA 17新特性

JAVA 17新特性 概述 JDK 16 刚发布半年&#xff08;2021/03/16&#xff09;&#xff0c;JDK 17 又如期而至&#xff08;2021/09/14&#xff09;&#xff0c;这个时间点特殊&#xff0c;蹭苹果发布会的热度&#xff1f;记得当年 JDK 15 的发布也是同天 Oracle 宣布&#xff0…