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){//不存在,返回404return 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,那么在拆箱时可能会抛出NullReferenceExceptionreturn 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);//封装RedisDataRedisData 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)) {//缓存未命中不存在,直接返回nullreturn 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)) {//缓存未命中不存在,直接返回nullreturn 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,那么在拆箱时可能会抛出NullReferenceExceptionreturn 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.rhkb.cn/news/282433.html

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

相关文章

深度学习500问——Chapter04:经典网络解读(1)

文章目录 4.1 LeNet-5 4.1.1 模型介绍 4.1.2 模型结构 4.1.3 模型特性 4.2 AlexNet 4.2.1 模型介绍 4.2.2 模型结构 4.2.3 模型特性 4.3 ZFNet 4.3.1 模型介绍 4.3.2 模型结构 4.3.3 模型特性 4.4 Network in Network 4.4.1 模型介绍 4.4.2 模型结构 4.4.3 模型特性 4.1 LeNet-…

MySQL的基本操作

目录 引言 一、SQL语句简介 &#xff08;一&#xff09;SQL通用语法 &#xff08;二&#xff09;SQL分类 &#xff08;三&#xff09;数据类型 1.数值类型 2.字符串类型 3.日期/时间类型 4.修饰符 二、登录mysql服务 三、SQL语句操作 &#xff08;一&#xff09;DD…

canopen使用

CAN 总线协议 1&#xff09;、启动SDO 下载协议 ccs&#xff1a;客户端 指定命令 1&#xff1a;开始下载请求 •scs&#xff1a;服务器命令符 3&#xff1a;开始下载响应 •n&#xff1a;只有当e 1和s1&#xff0c;否则为0。如果有效则表示字节在d不这样做的数量有效包…

【Selenium(一)】

简介 Selenium是一个开源的自动化测试工具&#xff0c;主要用于Web应用程序的自动化测试。它支持多种浏览器&#xff0c;包括Chrome、Firefox、Internet Explorer等&#xff0c;以及多种编程语言&#xff0c;如Java、Python、C#、Ruby等&#xff0c;使得它成为Web自动化测试中…

AI PPT生成工具 V1.0.0

AI PPT是一款高效快速的PPT生成工具&#xff0c;能够一键生成符合相关主题的PPT文件&#xff0c;大大提高工作效率。生成的PPT内容专业、细致、实用。 软件特点 免费无广告&#xff0c;简单易用&#xff0c;快速高效&#xff0c;提高工作效率 一键生成相关主题的标题、大纲、…

Linux 系统是如何收发⽹络包的

Linux 系统是如何收发⽹络包的&#xff1f; ⽹络模型 为了使得多种设备能通过⽹络相互通信&#xff0c;和为了解决各种不同设备在⽹络互联中的兼容性问题&#xff0c;国际标准化组织制定了开放式系统互联通信参考模型&#xff08;Open System Interconnection Reference Mode…

吴恩达机器学习笔记 二十六 决策树学习过程 独热编码one-hot

决策树的学习过程 1. 所有样本都在根结点 2.计算所有可能的特征的信息增益&#xff0c;选择信息增益最大的那个 3.根据选择的特征分离数据集&#xff0c;创造左右两支子树 4.继续进行分裂直到达到停止标准。停止标准有&#xff1a;一个节点只有一类样本&#xff1b;分裂一…

比堆垛机方案省电65% 实施快50% 四向车系统柔性化建设进程异军突起

对物流企业来说&#xff0c;供应链的数智化升级并非“赶时髦”&#xff0c;它需要找到一个既懂物流行业&#xff0c;又有数字化技术作基础的仓储方案提供商。而河北沃克基于AI底层技术、软硬一体化产品体系和技术创新行业经验双轮驱动的业务团队等“技术产品人才”三位一体优势…

(附源码)基于Spring Boot + Vue 在线网课学习系统的设计与实现

前言 &#x1f497;博主介绍&#xff1a;✌专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f447;&#x1f3fb; 精彩专栏 推荐订阅&#x1f447;&#x1f3fb; 2024年Java精品实战案例《100套》 &#x1f345;文末获取源码联系&#x1f345; &#x1f31…

[HackMyVM]靶场 XMAS

kali:192.168.56.104 靶机:192.168.56.126 注意在/etc/hosts 添加 192.168.56.126 christmas.hmv # cat /etc/hosts 127.0.0.1 localhost 127.0.1.1 kali2 192.168.223.131 dc-2 192.168.223.134 wordy 192.168.56.105 midn…

Vue3中基本数据类型为什么需要.value,,,引用类型不需要.value

1、在v3中使用基本数据类型&#xff08;如数字、字符串、布尔值&#xff09;时&#xff0c;如果你希望响应式地更新数据并触发视图更新,需要使用ref包裹基本数据类型,然后将基本数据类型转化为响应式对象;- - - 因此当你使用ref包裹基本数据类型时,实际上得到的是一个包含.valu…

3D开发工具HOOPS如何助力3D项目实现扩展现实技术?

在当今数字化时代&#xff0c;扩展现实&#xff08;Augmented Reality&#xff0c;AR&#xff09;技术的应用已经逐渐深入到各行各业&#xff0c;为用户带来了前所未有的沉浸式体验。而在实现这种技术的开发过程中&#xff0c;HOOPS技术的运用无疑是一种强大的助力。HOOPS是一种…

git push出错: src refspec dev/xxx does not match any

使用如下命令gitp push出错: git push origin 远端分支名 git push origin dev/xxxx error: src refspec dev/xxxx does not match any error: failed to push some refs to https://git.woa.com/..... 解决方案 1: git push origin 本地分支名:远端分支名 解决方案2&#…

ROS机器人虚拟仿真挑战赛本地电脑环境配置测试

预备基础 此案例需要完成&#xff1a; ROS机器人虚拟仿真挑战赛本地电脑环境配置记录-CSDN博客 ROS机器人虚拟仿真挑战赛本地电脑环境配置个人问题汇总-CSDN博客 命令测试 在不同的终端窗口分别输入&#xff1a; 标签1&#xff1a; roslaunch tianracer_gazebo demo_tian…

【GIT】最好用的git可视化教程网站推荐

最好用可视化学习git 网站:https://learngitbranching.js.org/?demo&localezh_CN 玩遍所有关卡&#xff0c;花半天时间便能掌握git &#x1f603; 本地仓库 基础命令介绍 git commit 提交 git branch <分支名> 创建分支 git checkout <分支名> 切换分支 git…

堆叠与集群

8.1堆叠与集群概述 随着企业的发展&#xff0c;企业网络的规模越来越大&#xff0c;这对企业网络提出了更高的要求&#xff1a;更高的可靠性、更低的故障恢复时间、设备更加易于管理等。传统的园区网高可靠性技术出现故障时切换时间很难做到毫秒级别、实现可靠性的方案通常为一…

Windows11安装Msql8.0版本详细安装步骤!

文章目录 前言一、下载Mysql二、安装Mysql三、登录验证三、环境变量配置总结 前言 每次搭建新环境的时候&#xff0c;都需要网上搜寻安装的步骤教程&#xff01;为了以后方便查阅&#xff01;那么本次就记录一下Windows11安装Msql8.0的详细步骤&#xff01;也希望能帮助到有需…

手写简易操作系统(十三)--编写简单C库

前情提要 因为马上要涉及到一个非常重要的部分&#xff0c;内存管理&#xff0c;所以这里我们编写一个简单的C库&#xff0c;用于支持我们后续的C语言开发 一、Assert断言 assert其实如果大家对C语言比较熟悉的话并不陌生&#xff0c;这个函数被称为断言&#xff0c;也就是程…

HTTP 工作流程请求响应 - 面试常问

文章目录 HTTP 工作流程请求和响应格式HTTP请求格式请求行&#xff1a;请求头部字段&#xff1a;空行&#xff1a;消息正文&#xff08;请求正文&#xff09;&#xff1a; HTTP响应格式状态行&#xff1a;响应头部字段&#xff1a;空行&#xff1a; HTTP方法HTTP状态码常用HTTP…

消息队列—RabbitMQ如何保证消息可靠性?

1. 如何保证消息的可靠性&#xff1f; 先来看看我们的万年老图&#xff0c;从图上我们大概可以看出来一个消息会经历四个节点&#xff0c;只有保证这四个节点的可靠性才能保证整个系统的可靠性。 生产者发出后保证到达了MQ。MQ收到消息保证分发到了消息对应的Exchange。Exchan…