redis-黑马点评-商户查询缓存

news2024/11/15 14:01:48

缓存:cache

public Result queryById(Long id) {
        //根据id在redis中查询数据
        String s = redisTemplate.opsForValue().get(CACHE_SHOP_KEY +id);

        //判断是否存在
        if (!StrUtil.isBlank(s)) {
            //将字符串转为bean
            //存在,直接返回
            Shop shop = JSONUtil.toBean(s, Shop.class);
            return Result.ok(shop);
        }
        //不存在,查询数据库
        Shop shop = getById(id);
        if (shop==null){
            //不存在,返回404
            return Result.fail("店铺不存在");
        }
        //数据库中是否存在,存在则写入缓存,并返回
        redisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(shop));
        return Result.ok(shop);
    }

JSONUtil.toBean(s, Shop.class);

JSONUtil.toJsonStr(shop);

缓存更新策略:

先删除数据库后删除缓存的线程安全可能性低。

缓存穿透:

1.查询店铺在数据库和redis中都不存在时,写入空值到redis中

2.查询数据为空值时,直接返回不要查询数据库。

public Result queryById(Long id) {
    //根据id在redis中查询数据
    String s = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY +id);
    //判断是否存在
    if (!StrUtil.isBlank(s)) {
        //将字符串转为bean
        //存在,直接返回
        Shop shop = JSONUtil.toBean(s, Shop.class);
        return Result.ok(shop);
    }
    //判断是否店铺是否存在,缓存中的空数据
    if (s!=null){
        //返回空值
        return Result.fail("店铺信息不存在");
    }
    //不存在,查询数据库
    Shop shop = getById(id);
    if (shop==null){
        //不存在,返回404
        //缓存空值到数据库中
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
        return Result.fail("店铺不存在");
    }
    //数据库中是否存在,存在则写入缓存,并返回
    stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
    return Result.ok(shop);
}

互斥锁解决缓存击穿:

使用redis中的setnx实现互斥锁,如果key不存在则创建,存在则无法创建。

//使用redis中的setnx实现互斥锁。
    private boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        //潜在的NullReferenceException,如果装箱的对象是null,那么在拆箱时可能会抛出NullReferenceException
        return BooleanUtil.isTrue(flag);
    }
    private void unLock(String key){

        stringRedisTemplate.delete(key);
    }
拆箱和装箱操作可能会产生的问题:

1.性能开销:装箱和拆箱涉及内存分配和复制,这会增加额外的性能开销。

2.类型安全丢失:装箱操作会将值类型包装在一个对象中,这样原本在栈上的值类型数据现在位于堆中,可能导致类型安全检查丢失。

3.垃圾回收压力:装箱操作会创建新的对象实例,这可能增加垃圾回收器的压力,尤其是在频繁进行装箱和拆箱操作的情况下。

4.潜在的NullReferenceException,如果装箱的对象是null,那么在拆箱时可能会抛出NullReferenceException异常。

/**
     * 互斥锁解决缓存击穿
     * @param id
     * @return
     */
    private Shop queryWithMutex(Long id){
        //根据id在redis中查询数据
        String s = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY +id);
        //判断是否存在
        if (!StrUtil.isBlank(s)) {
            //将字符串转为bean
            //存在,直接返回
            Shop shop = JSONUtil.toBean(s, Shop.class);
            return shop;
        }
        //判断是否店铺是否存在
        if (s!=null){
            //返回空值
            return null;
        }
        //缓存重建
        //尝试获取互斥锁
        String lockKey= LOCK_SHOP_KEY+id;
        Shop shop = null;
        try {
            boolean isLock = tryLock(lockKey);
            //判断是否获取锁成功

            if (!isLock){
                //获取锁失败,休眠一会重试
                Thread.sleep(10);
                return queryWithMutex(id);
            }
            //获取锁成功,再次查询缓存中是否存在数据,如果存在,则不需要缓存重建
            s = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY +id);
            //判断是否存在
            if (!StrUtil.isBlank(s)) {
                //将字符串转为bean
                //存在,直接返回
                shop = JSONUtil.toBean(s, Shop.class);
                unLock(lockKey);
                return shop;
            }
            //判断是否店铺是否存在
            if (s!=null){
                //返回空值
                return null;
            }
            
            //缓存中还是没有数据,查询数据库
            //不存在,查询数据库
            shop = getById(id);
            if (shop==null){
                //不存在,返回404
                //缓存空值到数据库中
                stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
                return null;
            }
            //数据库中是否存在,存在则写入缓存,并返回
            stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //释放锁
            unLock(lockKey);
        }
        return shop;
    }

逻辑过期解决缓存击穿:

创建一个redisData类,维护不同对象的过期时间:

@Data
public class RedisData {
    private LocalDateTime expireTime;
    private Object data;
}
 /**
     * 缓存预热将店铺数据存入redis中并且设置逻辑过期时间
     *
     */
    private void save2Redis(Long id,Long expireSeconds){
        //查询店铺信息
        Shop shop = getById(id);
        //封装RedisData
        RedisData redisData=new RedisData();
        redisData.setData(shop);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
        //保存数据到redis中
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(redisData));
    }
private Shop queryWithLogicExpire(Long id){
        //根据id在redis中查询数据
        String s = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY +id);
        //判断是否存在
        if (StrUtil.isBlank(s)) {
            //缓存未命中不存在,直接返回null
            return null;
        }
        //缓存命中,判断缓存是否过期
        RedisData redisData = JSONUtil.toBean(s, RedisData.class);
        JSONObject data = (JSONObject) redisData.getData();
        Shop shop = JSONUtil.toBean(data, Shop.class);
        LocalDateTime expireTime = redisData.getExpireTime();
        if (expireTime.isAfter(LocalDateTime.now())){
            //未过期
            return shop;
        }
        //过期,尝试获取互斥锁
        String lockKey= LOCK_SHOP_KEY+id;
        boolean isLock = tryLock(lockKey);
        if (isLock){
            //TODO 获取互斥锁成功,重新查询缓存是否过期,如果未过期,不用再缓存重建,如果过期,则重建缓存
            CACHE_REBUILD_EXECUTOR.submit(()->{
                try {
                    save2Redis(id,20L);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    unLock(lockKey);
                }
            });
        }
        //获取互斥锁失败,返回过期店铺信息
        return shop;
    }

封装Redis缓存工具类,包含四个方法:


@Component
@Slf4j
public class CacheClient {
    private final StringRedisTemplate stringRedisTemplate;
//    基于构造函数注入,不太使用,不太理解
    public CacheClient(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }
//缓存数据到redis中,并设置过期时间
    public void set(String key, Object value, Long time, TimeUnit unit){
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value),time,unit);
    }
//缓存数据到redis中,设置逻辑过期时间
    public void setLogicExpire(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),time,unit);
    }
//缓存穿透解决方案,
    public  <R,ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID,R> dbFallback,Long time, TimeUnit unit){
        String key=keyPrefix+id;
        //根据id在redis中查询数据
        String json = stringRedisTemplate.opsForValue().get(key);
        //判断是否存在
        if (!StrUtil.isBlank(json)) {
            //将字符串转为bean
            //存在,直接返回
            R r= JSONUtil.toBean(json, type);
            return r;
        }
        //判断是否店铺是否存在
        if (json!=null){
            //返回空值
            return null;
        }
        //不存在,查询数据库
        R r = dbFallback.apply(id);
        if (r==null){
            //不存在,返回404
            //缓存空值到数据库中
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
            return null;
        }
        //数据库中是否存在,存在则写入缓存,并返回
        this.set(key,r,time,unit);
        return r;
    }
//缓存击穿解决方案:逻辑过期
    private static final ExecutorService CACHE_REBUILD_EXECUTOR= Executors.newFixedThreadPool(10);
    /**
     * 使用逻辑过期解决缓存击穿
     * @param id
     * @return
     */
    public  <R,ID> R queryWithLogicExpire(String keyPrefix, ID id, Class<R> type, Function<ID,R> dbFallback,Long time, TimeUnit unit){
        //根据id在redis中查询数据
        String key=keyPrefix+id;
        String json = stringRedisTemplate.opsForValue().get(key);
        //判断是否存在
        if (StrUtil.isBlank(json)) {
            //缓存未命中不存在,直接返回null
            return null;
        }
        //缓存命中,判断缓存是否过期
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        JSONObject data = (JSONObject) redisData.getData();
        R r=JSONUtil.toBean(data, type);
        LocalDateTime expireTime = redisData.getExpireTime();
        if (expireTime.isAfter(LocalDateTime.now())){
            //未过期
            return r;
        }
        //过期,尝试获取互斥锁
        String lockKey= LOCK_SHOP_KEY+id;
        boolean isLock = tryLock(lockKey);
        if (isLock){
            //TODO 获取互斥锁成功,重新查询缓存是否过期,如果未过期,不用再缓存重建,如果过期,则重建缓存
            CACHE_REBUILD_EXECUTOR.submit(()->{
                try {
                    //重建缓存
                    //查询数据库
                    R r1= dbFallback.apply(id);
                    //缓存数据
                    this.setLogicExpire(key,r1,time,unit);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    unLock(lockKey);
                }
            });
        }
        //获取互斥锁失败,返回过期店铺信息
        return r;
    }
    //使用redis中的setnx实现互斥锁。
    private boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        //潜在的NullReferenceException,如果装箱的对象是null,那么在拆箱时可能会抛出NullReferenceException
        return BooleanUtil.isTrue(flag);
    }
    private void unLock(String key){
        stringRedisTemplate.delete(key);
    }
}
总结:

Spring中bean的注入方式:

在Spring框架中,注入Bean(对象)的方式有多种,以下是一些常见的方法:

1. 构造器注入(Constructor Injection):
通过构造器注入依赖,可以确保在创建对象时,它所依赖的其他对象也被创建。

```
public class ExampleBean {
private final AnotherBean anotherBean;

@Autowired
public ExampleBean(AnotherBean anotherBean) {
this.anotherBean = anotherBean;
}
}
```

2. Setter方法注入(Setter Injection):
通过setter方法注入依赖,可以在对象创建后,再设置依赖的对象。

```java
public class ExampleBean {
private AnotherBean anotherBean;

@Autowired
public void setAnotherBean(AnotherBean anotherBean) {
this.anotherBean = anotherBean;
}
}
```

3. 字段注入(Field Injection):
通过字段注入依赖,直接在字段上使用`@Autowired`注解。

```java
public class ExampleBean {
@Autowired
private AnotherBean anotherBean;
}
```

4. 方法参数注入(Method Parameter Injection):
在方法参数上使用`@Autowired`注解,Spring会注入对应的依赖。

```java
public class ExampleBean {
public void doSomething(@Autowired AnotherBean anotherBean) {
// ...
}
}
```

5. 接口注入(Interface Injection):
通过定义一个接口来标记需要注入的Bean。

```java
public interface InjectedInterface {
void injectDependency(AnotherBean anotherBean);
}

public class ExampleBean implements InjectedInterface {
private AnotherBean anotherBean;

@Override
public void injectDependency(AnotherBean anotherBean) {
this.anotherBean = anotherBean;
}
}
```

6. 基于注解的注入(Annotation-based Injection):
使用`@Autowired`、`@Resource`、`@Inject`等注解来标记需要注入的依赖。

```java
public class ExampleBean {
@Autowired
private AnotherBean anotherBean;
}
```

7. 自动装配(Autowiring):
Spring可以自动装配依赖,无需显式注入。

```java
public class ExampleBean {
@Autowired
private AnotherBean anotherBean;
}
```

8. 基于Java配置的注入:
使用`@Configuration`和`@Bean`注解来定义和注入Bean。

```java
@Configuration
public class AppConfig {
@Bean
public ExampleBean exampleBean() {
return new ExampleBean(anotherBean());
}

@Bean
public AnotherBean anotherBean() {
return new AnotherBean();
}
}
```

选择哪种注入方式取决于你的具体需求和设计偏好。构造器注入和setter注入是最常用的方式,因为它们可以保证依赖的完整性和初始化的一致性。而字段注入和基于注解的注

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

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

相关文章

Linux课程四课---Linux第一个小程序(进度条)

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

Windows影子账户

Windows影子账户 先检查administrator账户有没有被禁用&#xff0c;如果administrator账户被禁用 所属组内的账户都会被禁用&#xff0c;导致影子账户无法登录 创建隐藏用户 加入管理员组 隐藏用户创建方法&#xff1a; net user 用户名$ 密码 /add 输入 regedit打开注册表…

微信小程序简单实现手势左右滑动和点击滑动步骤条功能

使用微信小程序实现左右滑动功能&#xff0c;自定义顶部图案&#xff0c;点击文字滑动和手势触屏滑动&#xff0c;功能简单&#xff0c;具体实现代码如下所示&#xff1a; 1、wxss代码&#xff1a; /* 步骤条 */ .tab-box {display: flex;flex-direction: row;position: fix…

springboot整合springsecurity,从数据库中认证

概述&#xff1a;springsecurity这个东西太容易忘了&#xff0c;这里写点东西&#xff0c;避免忘掉 目录 第一步&#xff1a;引入依赖 第二步&#xff1a;创建user表 第三步&#xff1a;创建一个用户实体类&#xff08;User&#xff09;和一个用于访问用户数据的Repository…

Midjourney 和 Dall-E 的优劣势比较

Midjourney 和 Dall-E 的优劣势比较 Midjourney 和 Dall-E 都是强大的 AI 绘画工具&#xff0c;可以根据文本描述生成图像。 它们都使用深度学习模型来理解文本并将其转换为图像。 但是&#xff0c;它们在功能、可用性和成本方面存在一些差异。 Midjourney 优势: 可以生成更…

yocto编译测试

源码下载 git clone -b gatesgarth git://git.yoctoproject.org/poky lkmaolkmao-virtual-machine:~/yocto$ git clone -b gatesgarth git://git.yoctoproject.org/poky Cloning into poky... remote: Enumerating objects: 640690, done. remote: Counting objects: 100% (13…

【漏洞复现】CVE-2004-2761:使用弱哈希算法签名的 SSL 证书(SSL Certificate Signed Using Weak Hashing Algorithm)

概要&#xff1a;本次复现是针对编号为CVE-2004-2761的漏洞&#xff0c;由于条件有限&#xff0c;本次复现通过创建自签名证书进行操作。 问题描述&#xff1a;证书链中的 SSL 证书使用弱哈希算法进行签名。 1 环境搭建 本次复现环境在Linux平台下使用Nginx进行环境的搭建&…

ModbusTCP转Profinet网关高低字节交换切换

背景&#xff1a;在现场设备与设备通迅之间通常涉及到从一种字节序&#xff08;大端或小端&#xff09;转换到另一种字节序。大端字节序是指高位字节存储在高地址处&#xff0c;而小端字节序是指低位字节存储在低地址处。在不动原有程序而又不想或不能添加程序下可选用ModbusTC…

Java安装及环境配置详细教程

1.1 下载 Java 安装包 官网下载链接[点击跳转] 建议下载202版本&#xff0c;因为202版本之后的 Oracle JDK 是商用收费的&#xff08;个人使用不收费&#xff09; 1.2 勾选红框中内容&#xff0c;然后点击下方下载 1.3 如果没有登录 Oracle 则会跳转到该页面&#xff0c;因为…

爬虫 Day2

resp.close()#关掉resp 一requests入门 &#xff08;一&#xff09; 用到的网页&#xff1a;豆瓣电影分类排行榜 - 喜剧片 import requestsurl "https://movie.douban.com/j/chart/top_list" #参数太长&#xff0c;重新封装参数 param {"type": "…

Python环境下基于机器学习(决策树,随机森林,KNN和SVM)的轴承故障诊断

故障特征提取就是从振动信号中提取时、频域统计特征&#xff0c;并利用能量值、谱峭度、幅值等指标&#xff0c;提取出故障特征集。对故障特征值进行全面准确地提取&#xff0c;是提高诊断精度的关键&#xff0c;也是整个滚动轴承故障诊断过程中较困难的部分。 一些常见的时域…

Redis 过期删除策略和内存淘汰策略有什么区别?

资料来源 : 小林coding 小林官方网站 : 小林coding (xiaolincoding.com) Redis 的「内存淘汰策略」和「过期删除策略」&#xff0c;很多小伙伴容易混淆&#xff0c;这两个机制虽然都是做删除的操作&#xff0c;但是触发的条件和使用的策略都是不同的。 今天就跟大家理一理&…

如何将OpenCV Java 与Eclipse结合使用

返回目录&#xff1a;OpenCV系列文章目录 上一篇&#xff1a;OpenCV-Java 开发简介 下一篇&#xff1a; 正文&#xff1a; 警告&#xff1a;本教程可以包含过时的信息。 从 2.4.4 版本开始&#xff0c;OpenCV 支持 Java。在本教程中&#xff0c;我将解释如何设置开发环境&a…

信息系统项目管理师019:存储和数据库(2信息技术发展—2.1信息技术及其发展—2.1.3存储和数据库)

文章目录 2.1.3 存储和数据库1.存储技术2.数据结构模型3.常用数据库类型4.数据仓库 记忆要点总结 2.1.3 存储和数据库 1.存储技术 存储分类根据服务器类型分为&#xff1a;封闭系统的存储和开放系统的存储。封闭系统主要指大型机等服务器。开放系统指基于包括麒麟、欧拉、UNIX…

使用 ONLYOFFICE API 构建 Java 转换器,在 Word 和 PDF 之间进行转换

文章作者&#xff1a;ajun 随着文档处理需求的增加&#xff0c;格式转换成为了一个重要的需求点。由于PDF格式具有跨平台、不易被篡改的特性&#xff0c;将Word格式(.docx)转换为PDF格式(.pdf)的需求尤为强烈。ONLYOFFICE作为一个强大的办公套件&#xff0c;提供了这样的转换功…

Verilog——信号类型

Verilog HDL 的信号类型有很多种&#xff0c;主要包括两种数据类型&#xff1a;线网类型 (net type) 和寄存器类型 &#xff08; reg type &#xff09;。在进行工程设计的过程中也只会使用到这两个类型的信号。 4.1 信号位宽 定义信号类型的同时&#xff0c;必须定义好信号…

【C#语言入门】22. 接口、依赖反转、单元测试

【C#语言入门】22. 接口、依赖反转、单元测试 一、接口与单元测试 接口的产生&#xff1a;自底向上&#xff08;重构&#xff09;&#xff0c;自顶向下&#xff08;设计&#xff09;C#中接口的实现&#xff08;隐式&#xff0c;显式&#xff0c;多接口&#xff09;语言对面向…

java的成员变量和局部变量

1、什么是成员变量和局部变量&#xff1f; 2、成员变量和局部变量区别 区别 成员变量 局部变量 类中位置不同 类中方法外 方法内或者方法声明上 内存中位置不同 堆内存 栈内存 生命周期不同 随着对象的存在而存在&#xff0c;随着对象的消失而消失 随着方法的调用而…

【目标检测】YOLOv2 网络结构(darknet-19 作为 backbone)

上一篇文章主要是写了一些 YOLOv1 的原版网络结构&#xff0c;这篇文章一样&#xff0c;目标是还原论文中原版的 YOLOv2 的网络结构&#xff0c;而不是后续各种魔改的版本。 YOLOv2 和 YOLOv1 不一样&#xff0c;开始使用 Darknet-19 来作为 backbone 了。论文中给出了 Darkne…

RK3568平台 多点触摸电容屏

一.input事件 对于所有的input设备&#xff0c;报告input事件时候都分这么几部分&#xff0c;首先在probe函数中设置设备发送的事件类型、按键类型&#xff0c;设置设备一些属性信息。然后在发送事件时候要根据probe的设置来发送事件&#xff0c;否则就会被判为无效忽略掉。  …