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

缓存: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注入是最常用的方式,因为它们可以保证依赖的完整性和初始化的一致性。而字段注入和基于注解的注

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

零基础如何学习Web 安全,如何让普通人快速入门网络安全?、

前言 网络安全现在是朝阳行业&#xff0c;缺口是很大。不过网络安全行业就是需要技术很多的人达不到企业要求才导致人才缺口大 初级的现在有很多的运维人员转网络安全&#xff0c;初级也会慢慢的卷起来&#xff0c;但是岗位多不用怕&#xff0c;以后各大厂也都会要网络安全人…

【linux】CentOS查看系统信息

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

传输大咖15|如何在 PC 客户端中集成镭速高速传输插件?

引言 在当前信息爆炸的时代&#xff0c;快速、安全、稳定地传输数据对于企业的日常运营至关重要。然而&#xff0c;传统的 FTP/HTTP 传输方式存在着传输速度慢、易受网络延时、丢包等问题。而镭速高速传输插件可以帮助企业轻松实现快速、安全的文件传输。本文将详细介绍如何在…

基于Spring Boot的云上水果超市的设计与实现

摘 要 伴随着我国社会的发展&#xff0c;人民生活质量日益提高。于是对云上水果超市进行规范而严格是十分有必要的&#xff0c;所以许许多多的信息管理系统应运而生。此时单靠人力应对这些事务就显得有些力不从心了。所以本论文将设计一套云上水果超市&#xff0c;帮助商家进行…

王老吉药业开拓数字经济“新蓝海”,成立数字经济研究所,科技赋能新品压片糖

3月12日&#xff0c;广州王老吉药业股份有限公司&#xff08;以下简称“王老吉药业”&#xff09;召开第十一届312感恩活动新闻发布会&#xff0c;宣告王老吉数字经济研究所成立&#xff0c;并发布王老吉压片糖新品。一系列重要重要举措&#xff0c;无一不标志着王老吉药业正以…

2.7 ROC曲线相比P-R曲线有什么特点?

2.7 ROC曲线相比P-R曲线有什么特点&#xff1f; 前情提要&#xff1a; P-R曲线详见&#xff1a;2.2 什么是精确率&#xff08;Precision&#xff09;与召回率&#xff08;Recall&#xff09;&#xff1f;二者如何权衡&#xff1f;&#xff09; 2.4 ROC曲线是什么&#xff1f; 2…

目标检测——YOLOv5算法解读

作者&#xff1a;UltralyticsLLC公司 代码&#xff1a;https://github.com/ultralytics/yolov5 YOLO系列算法解读&#xff1a; YOLOv1通俗易懂版解读SSD算法解读YOLOv2算法解读YOLOv3算法解读YOLOv4算法解读YOLOv5算法解读 PP-YOLO系列算法解读&#xff1a; PP-YOLO算法解读…

用 二层口 实现三层口 IP 配置的一个实现方法

我们一般用 undo portswitch 来将二层口转为三层口&#xff0c;但如果设备不支持的话&#xff0c;那么。。。 一、拓朴图&#xff1a; 二、实现方法&#xff1a; 起一个 vlan x&#xff0c;配置 vlanif地址&#xff0c;然后二层口划分到 vlan x 下&#xff0c;对端做同样的配置…

解决jsp request.getParameter乱码问题(兼容Tomcat 6~8三个版本)

JSP页面写法&#xff1a; <% page contentType"text/html; charsetutf-8" language"java" %> <% page import"java.io.*" %> <%! int getServerVersion(HttpServletRequest request) {ServletContext application request.getS…

汽车制造业供应商管理会面临哪些问题?要如何解决?

汽车行业的供应链是及其复杂的&#xff0c;并且呈全球化分布&#xff0c;企业在知识产权方面的优势很可能是阶段性的。企业需要持续保持领先&#xff0c;将面临巨大的挑战&#xff0c;尽快地将产品推向市场是保持领先的唯一途径。然而&#xff0c;如果没有正确的方式去实现安全…

C++基础复习自用--vector

vector底层实现以及动态扩容 array是静态分配&#xff0c;后期无法改变。如果程序需要更大的array只能重新分配一个地址然后把旧空间里的复制过来。 vector是动态分配&#xff0c;他对大小可以合理控并且重新分配是数据移动效率高 关于查找删除插入 array和vector都是连续分…

20 OpenCV像素重映

文章目录 像素重映remap 重映算子代码示例 像素重映 简单点说就是把输入图像中各个像素按照一定的规则映射到另外一张图像的对应位置上去&#xff0c;形成一张新的图像。 g(x,y)是重映射之后的图像&#xff0c;h(x,y)是功能函数&#xff0c;f是源图像 remap 重映算子 Remap…

单例模式与原型模式的深度探索之旅

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 &#x1f680; 转载自&#xff1a;设计模式深度解析&#xff1a;单例模式与原型模式的深度探索之…

鸿蒙Harmony应用开发—ArkTS声明式开发(画布组件:Canvas)

提供画布组件&#xff0c;用于自定义绘制图形。 说明&#xff1a; 该组件从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 不支持。 接口 Canvas(context?: CanvasRenderingContext2D) 从API version 9开始&…

AI实景无人直播系统源代码开发部署流程

一、 开发流程分享 需求分析&#xff1a;与客户明确需求&#xff0c;确定无人直播系统的功能和特性。 设计系统架构&#xff1a;根据需求分析的结果&#xff0c;设计系统的架构&#xff0c;包括前后端的组成和各个模块的功能划分。 编写源代码&#xff1a;根据系统架构设计&a…

一款基于 SpringCloud 开发的AI聊天机器人系统,已对接GPT-4.0,非常强大

简介 一个基于SpringCloud的Chatgpt机器人&#xff0c;已对接GPT-3.5、GPT-4.0、百度文心一言、stable diffusion AI绘图、Midjourney绘图。用户可以在界面上与聊天机器人进行对话&#xff0c;聊天机器人会根据用户的输入自动生成回复。同时也支持画图&#xff0c;用户输入文本…

解决Vue发布后新旧包切换点击路由报错问题

错误截图 解决方案&#xff1a; 1.修改vue.config.js output: {// filename: js/[name].[chunkhash].${timeUpdate}.js,// chunkFilename: js/[id].[chunkhash].${timeUpdate}.jsfilename: [name].[contenthash].js,chunkFilename: [name].[contenthash].chunk.js}2.路由中添…

MRP(VBA系列):1.检查生产计划中的设备是否有BOM

在所有运行的前面&#xff0c;我需要先做一个检查&#xff1a;生产计划中的设备是否有BOM&#xff0c;如果有的设备没有BOM&#xff0c;我不希望程序继续&#xff01; Tips&#xff1a;所有代码都是为目前任职公司编写&#xff0c;极大概率不适合其他公司&#xff0c;在这里发…

C++ 11

目录 1. 统一的列表初始化 1.1 &#xff5b;&#xff5d;初始化 1.2 std::initializer_list 2. decltype 3. 右值引用和移动语义 3.1 左值引用和右值引用 3.2 左值引用与右值引用比较 3.3 右值引用使用场景和意义 3.4 右值引用引用左值及其一些更深入的使用场景分析 3…

长安链开源社区发布2023年度长安链优秀应用案例

1月27日结束的“长安链发布三周年庆暨生态年会”上&#xff0c;在国家区块链技术创新中心的指导下&#xff0c;长安链开源社区联合长安链生态联盟正式发布2023年度长安链行业示范案例、领域精品案例及特色创新案例。 本次评选面向2023年度应用长安链上线并取得应用成效的案例&…