尚品汇-sku存入Redis缓存(二十三)

目录:

(1)分布式锁改造获取sku信息

(2)使用Redisson

分布式锁 + AOP实现缓存

(3)定义缓存aop注解

(1)分布式锁改造获取sku信息

前面学习了本地锁的弊端,和Redis实现分布式锁和Redisson实现分布式锁的案例,都是为了搞糟sku

使用redis

RedisConst 类中追加一个变量

// 商品如果在数据库中不存在那么会缓存一个空对象进去,但是这个对象是没有用的,所以这个对象的过期时间应该不能太长,
// 如果太长会占用内存。
// 定义变量,记录空对象的缓存过期时间
public static final long SKUKEY_TEMPORARY_TIMEOUT = 10 * 60;
package com.atguigu.gmall.common.constant;/*** Redis常量配置类* set name admin*/
public class RedisConst {public static final String SKUKEY_PREFIX = "sku:";public static final String SKUKEY_SUFFIX = ":info";//单位:秒public static final long SKUKEY_TIMEOUT = 24 * 60 * 60;// 定义变量,记录空对象的缓存过期时间public static final long SKUKEY_TEMPORARY_TIMEOUT = 10 * 60;//单位:秒 尝试获取锁的最大等待时间public static final long SKULOCK_EXPIRE_PX1 = 100;//单位:秒 锁的持有时间public static final long SKULOCK_EXPIRE_PX2 = 1;public static final String SKULOCK_SUFFIX = ":lock";public static final String USER_KEY_PREFIX = "user:";public static final String USER_CART_KEY_SUFFIX = ":cart";public static final long USER_CART_EXPIRE = 60 * 60 * 24 * 30;//用户登录public static final String USER_LOGIN_KEY_PREFIX = "user:login:";//    public static final String userinfoKey_suffix = ":info";public static final int USERKEY_TIMEOUT = 60 * 60 * 24 * 7;//秒杀商品前缀public static final String SECKILL_GOODS = "seckill:goods";public static final String SECKILL_ORDERS = "seckill:orders";public static final String SECKILL_ORDERS_USERS = "seckill:orders:users";public static final String SECKILL_STOCK_PREFIX = "seckill:stock:";public static final String SECKILL_USER = "seckill:user:";//用户锁定时间 单位:秒public static final int SECKILL__TIMEOUT = 60 * 60 * 1;}

在ManagerServiceImpl:中改造getSkuInfo方法:

把原来里面的代码抽取出来形成一个方法:Ctrl+Alt+M

 

然后再添加一个方法去缓存中查询数据:此时要解决高并发情况下,key不存在,这个时候需加锁,一个去查询,其他请求隔离

在实现类中引入
@Autowired
private RedisTemplate redisTemplate;
// 使用redis' 做分布式锁
private SkuInfo getSkuInfoRedis(Long skuId) {SkuInfo skuInfo = null;try {// 缓存存储数据:key-value// 定义key sku:skuId:infoString skuKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;// 获取里面的数据? redis 有五种数据类型 那么我们存储商品详情 使用哪种数据类型?// 获取缓存数据skuInfo = (SkuInfo) redisTemplate.opsForValue().get(skuKey);// 如果从缓存中获取的数据是空if (skuInfo==null){// 直接获取数据库中的数据,可能会造成缓存击穿。所以在这个位置,应该添加锁。// 第一种:redis ,第二种:redisson// 定义锁的key sku:skuId:lock  set k1 v1 px 10000 nxString lockKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKULOCK_SUFFIX;// 定义锁的值String uuid = UUID.randomUUID().toString().replace("-","");// 上锁Boolean isExist = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);if (isExist){// 执行成功的话,则上锁。System.out.println("获取到分布式锁!");// 真正获取数据库中的数据 {数据库中到底有没有这个数据 = 防止缓存穿透}skuInfo = getSkuInfoDB(skuId);// 从数据库中获取的数据就是空if (skuInfo==null){// 为了避免缓存穿透 应该给空的对象放入缓存SkuInfo skuInfo1 = new SkuInfo(); //对象的地址redisTemplate.opsForValue().set(skuKey,skuInfo1,RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);return skuInfo1;}// 查询数据库的时候,有值redisTemplate.opsForValue().set(skuKey,skuInfo,RedisConst.SKUKEY_TIMEOUT,TimeUnit.SECONDS);// 解锁:使用lua 脚本解锁String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";// 设置lua脚本返回的数据类型DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();// 设置lua脚本返回类型为LongredisScript.setResultType(Long.class);redisScript.setScriptText(script);// 删除key 所对应的 valueredisTemplate.execute(redisScript, Arrays.asList(lockKey),uuid);return skuInfo;}else {// 其他线程等待Thread.sleep(1000);return getSkuInfo(skuId);}}else {return skuInfo;}} catch (InterruptedException e) {e.printStackTrace();}// 为了防止缓存宕机:从数据库中获取数据return getSkuInfoDB(skuId);
}

10s后锁释放:

(2)使用Redisson

@Autowired
private RedissonClient redissonClient;private SkuInfo getSkuInfoRedisson(Long skuId) {SkuInfo skuInfo = null;try {// 缓存存储数据:key-value// 定义key sku:skuId:infoString skuKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;// 获取里面的数据? redis 有五种数据类型 那么我们存储商品详情 使用哪种数据类型?// 获取缓存数据skuInfo = (SkuInfo) redisTemplate.opsForValue().get(skuKey);// 如果从缓存中获取的数据是空if (skuInfo==null){// 直接获取数据库中的数据,可能会造成缓存击穿。所以在这个位置,应该添加锁。// 第二种:redisson// 定义锁的key sku:skuId:lock  set k1 v1 px 10000 nxString lockKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKULOCK_SUFFIX;RLock lock = redissonClient.getLock(lockKey);/*第一种: lock.lock();第二种:  lock.lock(10,TimeUnit.SECONDS);第三种: lock.tryLock(100,10,TimeUnit.SECONDS);*/// 尝试加锁boolean res = lock.tryLock(RedisConst.SKULOCK_EXPIRE_PX1, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);if (res){try {// 处理业务逻辑 获取数据库中的数据// 真正获取数据库中的数据 {数据库中到底有没有这个数据 = 防止缓存穿透}skuInfo = getSkuInfoDB(skuId);// 从数据库中获取的数据就是空if (skuInfo==null){// 为了避免缓存穿透 应该给空的对象放入缓存SkuInfo skuInfo1 = new SkuInfo(); //对象的地址redisTemplate.opsForValue().set(skuKey,skuInfo1,RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);return skuInfo1;}// 查询数据库的时候,有值redisTemplate.opsForValue().set(skuKey,skuInfo,RedisConst.SKUKEY_TIMEOUT,TimeUnit.SECONDS);// 使用redis 用的是lua 脚本删除 ,但是现在用么? lock.unlockreturn skuInfo;}catch (Exception e){e.printStackTrace();}finally {// 解锁:lock.unlock();}}else {// 其他线程等待Thread.sleep(1000);return getSkuInfo(skuId);}}else {return skuInfo;}} catch (InterruptedException e) {e.printStackTrace();}// 为了防止缓存宕机:从数据库中获取数据return getSkuInfoDB(skuId);
}

测试结果跟上面的一样.

我们用页面测试一下:,分别点击一下各个销售属性的sku: 

各个sku数据就进行 了缓存 

这些缓存的数据,我们也可以进行设置一个缓存的时间:再添加缓存的时候设置缓存时间

分布式锁 + AOP实现缓存

上面的缓存代码我们发现添加了很多代码,如果其他地方也用到了缓存每个地方都需要添加很多代码,这是我们不想看到的,像事务一样我们需要事务了,我们加一个注解就实现了,我们也想这样,我们想要缓存我们加一个注解就解决了,怎么实现呢?、

我们把缓存代码抽取出来,最终进切入,利用aop实现 

随着业务中缓存及分布式锁的加入,业务代码变的复杂起来,除了需要考虑业务逻辑本身,还要考虑缓存及分布式锁的问题,增加了程序员的工作量及开发难度。而缓存的玩法套路特别类似于事务,而声明式事务就是用了aop的思想实现的

 

1. @Transactional 注解为植入点的切点,这样才能知道@Transactional注解标注的方法需要被代理。

2. @Transactional注解的切面逻辑类似于@Around

模拟事务,缓存可以这样实现:

1. 自定义缓存注解@GmallCache(类似于事务@Transactional

2. 编写切面类,使用环绕通知实现缓存的逻辑封装

 

(3)定义缓存aop注解

 

定义一个注解

 

 后面两个注解可加可不加

package com.atguigu.gmall.common.cache;import java.lang.annotation.*;@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface GmallCache {/*** 缓存key的前缀* @return*/String prefix() default "cache";
}

 

定义一个切面类加上注解

package com.atguigu.gmall.common.cache;import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.constant.RedisConst;
import lombok.SneakyThrows;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;import java.util.Arrays;
import java.util.concurrent.TimeUnit;/*** @author atguigu-mqx* 处理环绕通知*/
@Component
@Aspect
public class GmallCacheAspect {@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate RedissonClient redissonClient;//  切GmallCache注解@SneakyThrows@Around("@annotation(com.atguigu.gmall.common.cache.GmallCache)")public Object cacheAroundAdvice(ProceedingJoinPoint joinPoint){//  声明一个对象Object object = new Object();//  在环绕通知中处理业务逻辑 {实现分布式锁}//  获取到注解,注解使用在方法上!MethodSignature signature = (MethodSignature) joinPoint.getSignature();GmallCache gmallCache = signature.getMethod().getAnnotation(GmallCache.class);//  获取到注解上的前缀String prefix = gmallCache.prefix(); // sku//  方法传入的参数Object[] args = joinPoint.getArgs();//  组成缓存的key 需要前缀+方法传入的参数String key = prefix+ Arrays.asList(args).toString();//  防止redis ,redisson 出现问题!try {//  从缓存中获取数据//  类似于skuInfo = (SkuInfo) redisTemplate.opsForValue().get(skuKey);object = cacheHit(key,signature);//  判断缓存中的数据是否为空!if (object==null){//  从数据库中获取数据,并放入缓存,防止缓存击穿必须上锁//  perfix = sku  index1 skuId = 32 , index2 skuId = 33//  public SkuInfo getSkuInfo(Long skuId)//  key+":lock"String lockKey = prefix + ":lock";//  准备上锁RLock lock = redissonClient.getLock(lockKey);boolean result = lock.tryLock(RedisConst.SKULOCK_EXPIRE_PX1, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);//  上锁成功if (result){try {//  表示执行方法体 getSkuInfoDB(skuId);object = joinPoint.proceed(joinPoint.getArgs());//  判断object 是否为空if (object==null){//  防止缓存穿透Object object1 = new Object();redisTemplate.opsForValue().set(key, JSON.toJSONString(object1),RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);//  返回数据return object1;}//  放入缓存redisTemplate.opsForValue().set(key, JSON.toJSONString(object),RedisConst.SKUKEY_TIMEOUT,TimeUnit.SECONDS);//  返回数据return object;} finally {lock.unlock();}}else{//  上锁失败,睡眠自旋Thread.sleep(1000);return cacheAroundAdvice(joinPoint);//  理想状态//                  return object;}return cacheHit(key, signature);}}else {} catch (Throwable throwable) {throwable.printStackTrace();}//  如果出现问题数据库兜底return joinPoint.proceed(joinPoint.getArgs());}/***  表示从缓存中获取数据* @param key 缓存的key* @param signature 获取方法的返回值类型* @return*/private Object cacheHit(String key, MethodSignature signature) {//  通过key 来获取缓存的数据String strJson = (String) redisTemplate.opsForValue().get(key);//  表示从缓存中获取到了数据if (!StringUtils.isEmpty(strJson)){//  字符串存储的数据是什么?   就是方法的返回值类型Class returnType = signature.getReturnType();//  将字符串变为当前的返回值类型return JSON.parseObject(strJson,returnType);}return null;}
}

使用注解完成缓存

@GmallCache(prefix = RedisConst.SKUKEY_PREFIX)
@Override
public SkuInfo getSkuInfo(Long skuId) {return getSkuInfoDB(skuId);
}

其他地方:

@GmallCache(prefix = "saleAttrValuesBySpu:")
public Map getSaleAttrValuesBySpu(Long spuId) {
....
}
@GmallCache(prefix = "spuSaleAttrListCheckBySku:")
public List<SpuSaleAttr> getSpuSaleAttrListCheckBySku(Long skuId, Long spuId) {
....
}
@Override
@GmallCache(prefix = "SpuPosterList:")
public List<SpuPoster> getSpuPosterList(Long spuId) {//  select * from spu_poster where spu_id = spuId;return spuPosterMapper.selectList(new QueryWrapper<SpuPoster>().eq("spu_id",spuId));
}

根据三级分类id获取获取分类信息 

@GmallCache(prefix = "categoryViewByCategory3Id:")
public BaseCategoryView getCategoryViewByCategory3Id(Long category3Id) {
....
}

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

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

相关文章

NFTScan 浏览器现已支持 .mint 域名搜索功能!

近日&#xff0c;NFT 数据基础设施 NFTScan 浏览器现已支持用户输入 .mint 域名进行 Mint Blockchain 网络钱包地址的搜索查询&#xff0c; NFTScan 用户能够轻松地使用域名追踪 NFT 交易&#xff0c;为 NFT 钱包地址相关的搜索查询功能增加透明度和便利性。 NFTScan explorer…

MATLAB算法实战应用案例精讲-【数模应用】Poisson 回归分析(附R语言、python和MATLAB代码实现)

目录 前言 知识储备 常见回归方法 一、 回归分析方法概述 二、 分类 1.应用领域分类 (1)通用型 (2)统计学角度 (3)计量角度 (4)社科学角度 (5)医学角度 (6)数学建模 (7)专业型 Poisson回归和负二项回归 1.前提条件 2.分析流程图 3.案例分析 算…

通讯规约协议

通讯规约协议&#xff08;Communication Protocol&#xff09;&#xff0c;又称为通信规程&#xff0c;是随着现代通信技术和计算机网络技术的发展而发展的规约。它是通信双方对数据传送控制的一种约定&#xff0c;包括对数据格式、同步方式、传送速度、传送步骤、检纠错方式以…

Java8-求两个集合取交集

在Java8中&#xff0c;求两个集合的交集可以使用不同的三种方式&#xff1a;传统的循环遍历、使用Stream API的filter操作和使用Stream API的Collection操作。 方法一&#xff1a;传统的循环遍历 首先&#xff0c;我们创建两个集合list1和list2&#xff0c;并给它们添加一些元…

vue + xterm 前端终端terminal

引入 import {Terminal} from "xterm"; import {FitAddon} from "xterm-addon-fit"; import "xterm/css/xterm.css";html <div id"terminal"></div>vue onMounted(() > {nextTick(() > {initTerm();}) })const i…

规划决策算法(四)---Frenet坐标系

知乎&#xff1a;坐标系转换 1.Frenet 坐标系 什么是 Frenet 坐标系&#xff1a; 为什么使用 Frenet 坐标系&#xff1a; 通常情况&#xff0c;我们只会关注车辆当前距离左右车道线的距离&#xff0c;来判断是否偏离车道&#xff0c;是否需要打方向盘进行方向微调。而不是基于…

腾讯云k8s相关

1.某个服务腾讯云内网地址&#xff1f; 比如&#xff1a;spiderflow-web正式环境&#xff1a;http://spiderflow-web.sd-backend:30001 试一试&#xff1a;

python实现批量化查询耗时SQL

python实现批量化查询耗时SQL 今天发现最近多了一些耗时SQL的查询&#xff0c;例如我去数据库一张千万级表查询一天的数据需要耗时20分钟&#xff0c;我总共需要查询一年的数据&#xff0c;我总不能一条一条的手动执行吧&#xff0c;这样也太伤身体&#xff0c;属实难崩啊。就…

笔记——C语言

C语言是一种结构化的程序设计语言 空格的ASCII码值是32&#xff0c;空格是属于可打印字符 \0是字符串结束的标志&#xff0c;字符串的末尾隐藏了一个\0 printf()在打印字符串或者strlen在计算字符串长度的时候&#xff0c;遇到\0的时候就自动停止。 strlen返回的值是size_t…

flutter开发实战-AssetBundle读取指定packagename的文件

flutter开发实战-AssetBundle读取指定packagename的文件 在Flutter中&#xff0c;如果你想从特定的包名中读取资源文件&#xff0c;你可以使用rootBundle类来访问packages目录下的资源。下面使用rootBundle来读取包名为my_package的资源文件user.json 一、pubspec.yaml配置 …

MongoDB教程(二十二):MongoDB固定集合

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言一、固定集…

FastGPT 源码调试配置

目录 一、添加 launch.json 文件 二、调试 本文简单介绍如何通过 vscode 对 FastGPT 进行调试。 这里假设已经安装 vsocde 和 FastGPT本地部署。 一、添加 launch.json 文件 vscode 打开 FastGPT 项目,点击 调试 -> 显示所有自动调试配置 -> 添加配置 -> Node.j…

笔记分类的烦恼

前言 你是否为笔记的分类而苦恼&#xff0c;是否迷失在市面上纷繁复杂的笔记分类法&#xff1f; 不用再烦恼了&#xff0c;本文将介绍一个适用于个人笔记的终极分类办法&#xff0c;只需三刀&#xff0c;尘埃落定。 &#x1f52a; 第一刀 笔记场景 &#x1f370; 也就是笔记…

通用网络验证系统,承载能力强,支持高并发、高承载、多线路

这个网络验证系统基于PhpMySql数据库架构的网络验证系统&#xff0c;安全稳定、性能强悍、 承载能力强&#xff0c;支持高并发、高承载、多线路&#xff0c;支持服务器集群架设,高性能设计&#xff0c;速度非常快&#xff0c;效率非常高。 客户端支持VC、VB、DELPHI、易语言、…

Kotlin Flows 流 — 基础

Kotlin Flows 流 — 基础 上一篇研究了 Kotlin 协程。上一篇文章重点介绍了协程的一些基础知识&#xff0c;如协程上下文&#xff08;CoroutineContext&#xff09;、协程作用域&#xff08;CoroutineScope&#xff09;、协程构建器等。如承诺的那样&#xff0c;这是关于流&…

C++内存管理(候捷)第四讲 笔记

上中下三个classes分析 Loki allocator的三个类&#xff0c;从低阶到高阶分别为&#xff1a;Chunk, FixedAllocator, SmallObjAllocator Chunk&#xff1a;pData指针&#xff0c;指向分配的一个chunk&#xff0c;firstAvailableBlock_索引&#xff0c;指向第一个可用区块是第几…

数据结构day3(双向链表操作)

链式存储&#xff1a; 双向链表 线性表的链式存储&#xff1a;解决了顺序存储的缺点&#xff0c;插入和删除。动态存储问题。 数据域 指针 ---> 节点 注意&#xff1a;用自己结构的指针一般就是 链表。 DoubleLInk.h文件 #ifndef DOULINK_H #define DOULINK_H…

自动导入unplugin-auto-import+unplugin-vue-components

文章介绍 接下来将会以Vite Vue3 TS的项目来举例实现 在我们进行项目开发时&#xff0c;无论是声明响应式数据使用的ref、reactive&#xff0c;或是各种生命周期&#xff0c;又或是computed、watch、watchEffect、provide-inject。这些都需要前置引入才能使用&#xff1a; …

基于PSO粒子群优化的GroupCNN分组卷积网络时间序列预测算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 粒子群优化算法&#xff08;PSO&#xff09; 4.2 分组卷积神经网络&#xff08;GroupCNN&#xff09; 4.3 PSO优化GroupCNN 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行…

【已解决】Python ValueError: math domain error 详解

【已解决】Python ValueError: math domain error 详解 在Python编程中&#xff0c;遇到ValueError: math domain error是一个相对常见的问题。此错误通常表明传递给数学函数的参数超出了其定义域。本文将深入探讨此错误的根源、解决思路、具体解决方法、常见场景分析以及扩展…