尚品汇-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.xdnf.cn/news/1487344.html

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

相关文章

怎么使用github上传XXX内所有文件

要将 目录中的所有文件上传到 GitHub&#xff0c;你可以按照以下步骤进行&#xff1a; 创建一个新的 GitHub 仓库 登录到你的 GitHub 账户。 点击右上角的加号&#xff08;&#xff09;&#xff0c;选择 “New repository”。 输入仓库名称&#xff08;例如&#xff1a;202407…

走难而正确的路并持之以恒

走难而正确的路并持之以恒 接近八月&#xff0c;台风频繁。气象台说台风“格美”今夜将至&#xff0c;往粤北走&#xff0c;而留在粤东的将是持续的高温。高温的广州&#xff0c;这几晚的天空惊喜不断&#xff0c;成片的火烧云&#xff0c;站在猎德大桥观望&#xff0c;丹红的凤…

数字图像处理笔记(一)---- 图像数字化与显示

系列文章目录 数字图像处理学习笔记&#xff08;一&#xff09;---- 图像数字化与显示 数字图像处理笔记&#xff08;二&#xff09;---- 像素加图像统计特征 数字图像处理笔记&#xff08;三) ---- 傅里叶变换的基本原理 文章目录 系列文章目录前言一、数字图像处理二、图像数…

字符串相加(leetcode算法题)

各位老铁早上好&#xff0c;今天来分享一下我前两天做的leetcode的题目&#xff0c;我个人觉得这两道题目挺经典的&#xff0c;所以打算写这篇博客进行总结&#xff0c;希望各位老铁看完我这篇博客能有所收获。 字符串相加 题目链接 题目要求&#xff1a;你不能使用任何內建…

【Vue实战教程】之 Vue3 新特性详解

1 为什么要用Vue3 在学习Vue3的新特性之前&#xff0c;我们先来看一下Vue3设计的目的是什么&#xff0c;为什么要对Vue2做出很大的改变&#xff0c;以及Vue3到底解决了什么问题。像Vue这样全球闻名的前端框架&#xff0c;在任何一次改动时&#xff0c;设计者都是经过深思熟虑的…

Kithara和Halcon (二)

Kithara使用Halcon QT 进行二维码实时识别 目录 Kithara使用Halcon QT 进行二维码实时识别Halcon 简介以及二维码检测的简要说明Halcon 简介Halcon的二维码检测功能 Qt应用框架简介项目说明关键代码抖动测试测试平台&#xff1a;测试结果&#xff1a; 开源源码 Halcon 简介以…

vue3+vite纯前端实现自动触发浏览器刷新更新版本内容,并在打包时生成版本号文件

前言 在前端项目中&#xff0c;有时候为了实现自动触发浏览器刷新并更新版本内容&#xff0c;可以采取一系列巧妙的措施。我的项目中是需要在打包时候生成一个version.js文件&#xff0c;用当前打包时间作为版本的唯一标识&#xff0c;然后打包发版 &#xff0c;从实现对版本更…

基于SpringBoot的矩形范围面时空分析-以震中附近历史地震为例

目录 前言 1、分析的必要性 2、分析的紧迫性 一、数据库物理模型及空间分析实现 1、数据库物理模型 2、空间数据库中的空间查询分析 二、Java后台程序开发 1、模型层设计 2、业务层的设计与实现 三、WebGIS功能设计与实现 1、同时展示4幅地图 2、初始化地图 3、展示…

动态创建标签jQuery效果

动态创建标签jQuery效果https://www.bootstrapmb.com/item/14832 使用jQuery来动态创建HTML标签并添加效果是一种常见的方法。以下是一个简单的示例&#xff0c;说明如何使用jQuery来动态创建<div>标签&#xff0c;并给它们添加一些基本的效果。 1. 创建一个新的<di…

go语言day17 通道channel

Golang-100-Days/Day16-20(Go语言基础进阶)/day18_channel通道.md at master rubyhan1314/Golang-100-Days (github.com) go语言day09 通道 协程的死锁-CSDN博客 channel for range 循环通道对象 单向通道 单项通道常用于函数参数&#xff0c;只是用来限定在函数中只能进行通道…

Langchain核心模块与实战[8]:RAG检索增强生成[loader机制、文本切割方法、长文本信息处理技巧]

Langchain核心模块与实战[8]:RAG(Retrieval Augmented Generation,检索增强生成) RAG(Retrieval-Augmented Generation)技术是一种结合检索和生成功能的自然语言处理(NLP)技术。该技术通过从大型外部数据库中检索与输入问题相关的信息,来辅助生成模型回答问题。其核心…

Mysql中(基于GTID方式)实现主从复制,单主复制详细教程

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f427;Linux基础知识(初学)&#xff1a;点击&#xff01; &#x1f427;Linux高级管理防护和群集专栏&#xff1a;点击&#xff01; &#x1f510;Linux中firewalld防火墙&#xff1a;点击&#xff01; ⏰️创作…

《深入探秘Java中的枚举:掌握Enum的魔力》

目录 &#x1f4dd; 枚举枚举的定义枚举的使用1、表示一组固定常量2、实现接口3、枚举与策略模式4、EnumSet5、EnumMap &#x1f4ce; 参考文章 &#x1f600; 准备好了吗&#xff1f;让我们一起步入这座Java神奇的城堡&#xff0c;探寻枚举&#xff08;Enum&#xff09;这个强…

grafana对接zabbix数据展示

目录 1、初始化、安装grafana 2、浏览器访问 3、安装zabbix 4、zabbix数据对接grafana 5、如何导入模板&#xff1f; ① 设置键值 ② 在zabbix web端完成自定义监控项 ③ garafana里添加nginx上面的的三个监控项 6、如何自定义监控项&#xff1f; 以下实验沿用上一篇z…

Python学习笔记44:游戏篇之外星人入侵(五)

前言 上一篇文章中&#xff0c;我们成功的设置好了游戏窗口的背景颜色&#xff0c;并且在窗口底部中间位置将飞船加载出来了。 今天&#xff0c;我们将通过代码让飞船移动。 移动飞船 想要移动飞船&#xff0c;先要明白飞船位置变化的本质是什么。 通过上一篇文章&#xff0…

vue上传Excel文件并直接点击文件列表进行预览

本文主要内容&#xff1a;用elementui的Upload 组件上传Excel文件&#xff0c;上传后的列表采用xlsx插件实现点击预览表格内容效果。 在项目中可能会有这样的需求&#xff0c;有很多种方法实现。但是不想要跳转外部地址&#xff0c;所以用了xlsx插件来解析表格&#xff0c;并展…

使用 vSphere vCenter 管理 ESXi

使用 vSphere vCenter 管理 ESXi 1、新建数据中心 在 vSphere Client 中&#xff0c;左上角图标&#xff0c;进入 “清单”&#xff0c;鼠标右键名称&#xff0c;新建数据中心。 输入数据中心名称&#xff0c;我这里直接使用默认值&#xff0c;点击确定。 2、往数据中心中添加…

Linux epoll 机制——原理图解与源码实现分析

epoll概述 epoll是Linux内核为处理大批量文件描述符而作了改进的poll&#xff0c;它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。 epoll可以理解为event poll&#xff0c;它是一种事件驱动的I/O模型&#xff0c;可以用来替代传统的select和poll模型…

leetcode-98. 验证二叉搜索树

题目描述 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左 子树 只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。 示例 1&…

功能性的安全性保障:TOKEN鉴权校验

1. 引言 在软件开发过程中&#xff0c;确保系统的安全性是至关重要的一环。它不仅关乎保护用户数据的完整性和隐私性&#xff0c;也是维护系统稳定运行的基石。我认为&#xff0c;从宏观角度审视&#xff0c;软件开发的安全性保障主要可分为两大类&#xff1a;功能性的安全性保…