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.mzph.cn/news/762305.shtml

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

相关文章

专家解读!IMAP的要点助您在旅途中保持邮件无忧!

你是否经常因会议而出差&#xff0c;需要在各种设备上灵活地访问你的电子邮件&#xff1f;如果是的话&#xff0c;你可能会想了解你的电子邮件系统是如何通过使用互联网消息访问协议&#xff08;IMAP&#xff09;来工作的&#xff0c;这样当你不在办公桌前时&#xff0c;你可以…

_.debounce防抖函数 在vue中使用this问题,应该传匿名函数而不是箭头函数

简单理解&#xff1a;_.debounce内部做了apply操作&#xff0c;箭头函数由于没有this,无法绑定this,导致最终this是undefined, 而匿名函数&#xff0c;成功通过applay绑定了this,所以this指向了vue组件实例。 methods: {// 防抖动dSave1: _.debounce(() > {console.log(thi…

你知道弧幕影院如何制作吗?其应用领域竟如此广泛!

“沉浸式”作为如今备受热议的内容展示形式&#xff0c;其有着多种可实现的途径&#xff0c;其中弧幕影院作为一项有着独特视觉效果、沉浸式观影体验的技术类型&#xff0c;便是大多数影院、主题公园等娱乐场景的必备设计展项&#xff0c;这种弧幕影院通常使用大型的半圆形屏幕…

python 爬取杭州小区挂牌均价

下载chrome驱动 通过chrome浏览器的 设置-帮助-关于Google Chrome 查看你所使用的Chrome版本 驱动可以从这两个地方找: 【推荐】https://storage.googleapis.com/chrome-for-testing-publichttp://npm.taobao.org/mirrors/chromedriver import zipfile import os import r…

leetcode 232.用栈实现队列 JAVA

题目 思路 使用两个栈&#xff08;输入栈和输出栈&#xff09;来模拟一个队列。 队列的push操作实现&#xff1a;直接将元素push到输入栈中。 队列的pop操作实现&#xff1a;队列是先入先出&#xff0c;将输入栈的元素全部pop到输出栈中&#xff0c;然后再由输出栈pop&#…

PMP备考时间、出成绩时间有多久?从在威班培训到拿证我用了60天

尽管PMI官方没有对PMP考试通过分数进行具体规定&#xff0c;能否通过也是看成绩页显示的是“PASS”&#xff08;通过&#xff09;还是“FAIL”&#xff08;未通过&#xff09;&#xff0c;没有成绩的数值体现&#xff0c;但有每个领域的等级可以进行查看&#xff0c;比如下图。…

Windows系统服务器宝塔面板打开提示Internal Server Error错误

1、cmd运行bt命令 2、尝试输入16修复程序 3、如果不行&#xff0c;输入17升级程序

STL —— string(1)

目录 1. 模板 1.1 泛型编程 1.2 函数模板 1.2.1 函数模板概念 1.2.2 函数模板格式 1.2.3 函数模板的原理 1.2.4 显式实例化 1.2.5 模板参数的匹配原则 1.3 类模板 1.3.1 类模板定义格式 1.3.2 类模板的实例化 2. STL —— string类 2.1 STL 简介 2.2 标准库中的s…

怎样隐藏查询和分组?

发布查询时&#xff0c;遇到信息量较大需要提前制作好&#xff0c;但不用马上发布的查询&#xff0c;该怎样隐藏查询和分组&#xff1f; &#x1f4cc;使用教程 01“开始”和“暂停”查询 如果想要隐藏查询&#xff0c;可以通过点击绿色开始按钮来暂停查询&#xff0c;暂停后的…

【软考高项】十五、信息系统工程之系统集成

1、集成基础 定义&#xff1a;通过硬件平台、网络通信平台、数据库平台、工具平台、应用软件平台将各类资源有机、高效地集成到一起&#xff0c;形成一个完整的工作台面 基本原则包括:开放性、结构化、先进性和主流化 2、网络集成 包括&#xff1a;传输子系统、交换子系统、…

调试西门子G120STO模式出现O.F1600等一系列报警

目录 一、现象描述 二、 解决经历 三、结果展示 四、总结 一、现象描述 在调试使用西门子G120的STO功能时&#xff0c;一直无法使用&#xff0c;变频器也一直在报警(RDY灯红灯快闪、SAFE灯黄灯快闪)。在博图上查询发现下面一系列的故障报警。 二、 解决经历 也查询了很多网…

Vue中的状态管理Vuex,基本使用

1.什么是Vuex? Vuex是专门为Vue.js设计的状态管理模式;特点:集中式存储和管理应用程序中所有组件状态,保证状态以一种可预测的方式发生变化。 1.1.什么是状态管理模式? 先看一个单向数据流的简单示意图 state:驱动应用的数据源 view:以声明方式将state映射到视图 actions:…

SAP前台处理:物料主数据创建<MM01>之会计视图

一、背景&#xff1a; 终于来到了物料主数据&#xff0c;我觉得物料账是SAP最重要的一项发明&#xff0c;也一直是SAP的一项重要优势&#xff0c;物料账记录了一个个物料的生生不息&#xff1b; 本章主要讲解物料主数据和财务相关的主要内容&#xff1a;这里特别提示由于作者…

c语言扫雷改进版

目录 文章目录 主体 整体架构流程 技术名词解释 技术细节 测试情况 文章目录 概要整体架构流程技术名词解释技术细节测试情况 主体 主体包括菜单&#xff0c;游戏规则简绍&#xff0c;选择进行与否 int main() {int input;srand((unsigned int)time(NULL));do{ menu()…

科技云报道:造完“大模型”,“具身智能”将引领AI下一个浪潮?

科技云报道原创。 资深机器人专家Eric Jang不久前曾预言&#xff1a;“ChatGPT 曾在一夜之间出现。我认为&#xff0c;有智慧的机器人技术也将如此。” 3月13日深夜&#xff0c;一段人形机器人的视频开始热传。 在视频中&#xff0c;Figure的人形机器人&#xff0c;可以完全…

【算法】差分算法详解(模板)

类似于数学中的求导和积分之间的关系&#xff0c;差分可以看成前缀和的逆运算。 差分数组&#xff1a; 首先给定一个原数组a&#xff1a;a[1], a[2], a[3],,,,,, a[n]; 然后我们构造一个数组b &#xff1a; b[1] ,b[2] , b[3],,,,,, b[i]; 使得 a[i] b[1] b[2 ] b[3] ,,,…

CAD建筑版2024 安装教程

CAD建筑版是一种专门用于建筑设计和绘图的CAD软件版本。它提供了专业的建筑设计工具和功能&#xff0c;帮助建筑师、设计师和工程师在建筑领域进行快速、准确和高效的设计工作。 CAD建筑版具备建筑相关的库和元素&#xff0c;用户可以方便地使用预定义的建筑符号和元素进行建筑…

网络基础(二)

1、应用层 程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层&#xff1b; 1.1、再谈 "协议" 协议是一种 "约定". socket api的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的. 如果我们要传输一些 "…

设计模式学习笔记 - 设计原则与思想总结:2.运用学过的设计原则和思想完善之前性能计数器项目

概述 在 《设计原则 - 10.实战&#xff1a;针对非业务的通用框架开发&#xff0c;如何做需求分析和设计及如何实现一个支持各种统计规则的性能计数器》中&#xff0c;我们讲解了如何对一个性能计数器框架进行分析、设计与实现&#xff0c;并且实践了一些设计原则和设计思想。当…

IP代理技术革新:探索数据采集的新路径

引言&#xff1a; 随着全球化进程不断加深&#xff0c;网络数据采集在企业决策和市场分析中扮演着愈发重要的角色。然而&#xff0c;地域限制和IP封锁等问题常常给数据采集工作带来了巨大挑战。亿牛云代理服务凭借其强大的网络覆盖和真实住宅IP资源&#xff0c;成为解决这些问…