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

news2024/11/14 6:09:17

目录:

(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:info
        String 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 nx
            String 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脚本返回类型为Long
                redisScript.setResultType(Long.class);
                redisScript.setScriptText(script);
                // 删除key 所对应的 value
                redisTemplate.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:info
        String 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 nx
            String 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.unlock
                    return 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 {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private 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.coloradmin.cn/o/1946040.html

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

相关文章

Springboot validated JSR303校验

1.导入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency> 2.测试类 package com.jmj.gulimall.product.testC;import lombok.Data;import javax.val…

《0基础》学习Python——第二十讲__网络爬虫/<3>

一、用post请求爬取网页 同样与上一节课的get强求的内容差不多&#xff0c;即将requests.get(url,headershead)代码更换成requests.post(url,headershead),其余的即打印获取的内容&#xff0c;如果content-typejson类型的&#xff0c;打印上述代码的请求&#xff0c;则用一个命…

argon主题调整日记

前言 argon主题是一款由solstice23开发的一款简洁美观的WordPress主题&#xff0c;在使用过程中也发现了几个可以优化的点&#xff0c;在查阅主题文档无果后对其进行以下几点修改。 1、使用子主题 为了避免修改源文件而引起主题更新后修改丢失的问题&#xff0c;还是尽量使用子…

一个C++模板工厂的编译问题的解决。针对第三方库的构造函数以及追加了的对象构造函数。牵扯到重载、特化等

一窥模板的替换和匹配方式&#xff1a;偏特化的参数比泛化版本的还要多&#xff1a;判断是不是std::pair&#xff1c;,&#xff1e;。_stdpair模板参数太多-CSDN博客 简介 在一个项目里&#xff0c;调用了第三封的库&#xff0c;这个库里面有个类用的很多&#xff0c;而且其构…

Mindspore框架循环神经网络RNN模型实现情感分类|(六)模型加载和推理(情感分类模型资源下载)

Mindspore框架循环神经网络RNN模型实现情感分类 Mindspore框架循环神经网络RNN模型实现情感分类|&#xff08;一&#xff09;IMDB影评数据集准备 Mindspore框架循环神经网络RNN模型实现情感分类|&#xff08;二&#xff09;预训练词向量 Mindspore框架循环神经网络RNN模型实现…

【Vue实战教程】之 Vue Router 路由详解

Vue Router路由 1 路由基础 1.1 什么是路由 用Vue.js创建的项目是单页面应用&#xff0c;如果想要在项目中模拟出来类似于页面跳转的效果&#xff0c;就要使用路由。其实&#xff0c;我们不能只从字面的意思来理解路由&#xff0c;从字面上来看&#xff0c;很容易把路由联想…

CV13_混淆矩阵、F1分数和ROC曲线

1.1 混淆矩阵Confusion Matrix 混淆矩阵&#xff08;Confusion Matrix&#xff09;是机器学习和统计学中用于描述监督学习算法性能的特定表格布局。它是一种特定类型的误差矩阵&#xff0c;可以非常直观地表示分类模型在测试数据集上的预测结果与实际结果之间的对比。 混淆矩…

谣言检测文献阅读十二—A Convolutional Approach for Misinformation Identification

系列文章目录 谣言检测文献阅读一—A Review on Rumour Prediction and Veracity Assessment in Online Social Network谣言检测文献阅读二—Earlier detection of rumors in online social networks using certainty‑factor‑based convolutional neural networks谣言检测文…

FreeModbus学习——读输入寄存器eMBFuncReadInputRegister

FreeModbus版本&#xff1a;1.6 当功能码为04时&#xff0c;也就是读输入寄存器MB_FUNC_READ_INPUT_REGISTER 看一下它是怎么调用读输入寄存器处理函数的 当功能码为04时&#xff0c;调用读输入寄存器处理函数 这个函数在数组xFuncHandlers中&#xff0c;也就是eMBFuncRead…

Mysql数据库第四次作业

mysql> create table student(sno int primary key auto_increment,sname varchar(30) not null unique,Ssex varchar(2) check (Ssex男 or Ssex女) not null,Sage int not null,Sdept varchar(10) default计算机 not null); mysql> create table Course(Con int primar…

【通信协议-RTCM】MSM语句(2) - RINEXMSM7语句总结(重要!自动化开发计算卫星状态常用)

注释&#xff1a; 在工作中主要负责的是RTCM-MSM7语句相关开发工作&#xff0c;所以主要介绍的就是MSM7语句相关内容 1. 相位校准参考信号 2. MSM1、MSM2、MSM3、MSM4、MSM5、MSM6和MSM7的消息头内容 DATA FIELDDF NUMBERDATA TYPENO. OF BITSNOTES Message Number - 消息编…

1. Docker的介绍和安装 (二)

5 Docker的原理 5.1 Namespace Namespace&#xff08;命名空间&#xff09;提供了一个独立的工作环境&#xff0c;Docker使用Namespace来隔离容器&#xff0c;使得每个容器都有自己独立的系统资源&#xff08;如进程ID、主机名、网络等&#xff09;。 PID Namespace&#xf…

SBTI科学碳目标认证是什么?SBTI科学碳目标的重要性

SBTI科学碳目标认证&#xff0c;作为企业在应对气候变化和追求可持续发展道路上的重要里程碑&#xff0c;其认证过程严谨而系统。以下是获得SBTI科学碳目标认证的详细步骤&#xff1a; 首先&#xff0c;企业需要在线注册并提交承诺书&#xff0c;郑重承诺在未来24个月内提交科学…

Linux网络-配置IP

作者介绍&#xff1a;简历上没有一个精通的运维工程师。希望大家多多关注作者&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 本来IP配置应该放在Linux安装完成的就要配置的&#xff0c;但是由于那个时候对Linux不怎么熟悉&#xff0c;所以单独列了一个…

每日一题 LeetCode03 无重复字符的最长字串

1.题目描述 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的最长字串的长度。 2 思路 可以用两个指针, 滑动窗口的思想来做这道题,即定义两个指针.一个left和一个right 并且用一个set容器,一个length , 一个maxlength来记录, 让right往右走,并且用一个set容器来…

扫雷-C语言

一、前言&#xff1a; 众所周知&#xff0c;扫雷是一款大众类的益智小游戏&#xff0c;它的游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子&#xff0c;同时避免踩雷&#xff0c;踩到一个雷即全盘皆输。 今天&#xff0c;我们的目的就是通过C语言来实现一个简…

Open函数使用 Json与pickle Os模块

一. 文件操作与 open() 函数 Open函数是Python中用于打开文件的内置函数&#xff0c;其基本语法如下&#xff1a; open(file, moder, buffering-1, encodingNone, errorsNone, newlineNone, closefdTrue, openerNone) 各参数说明&#xff1a; file&#xff1a; 要打开的文件…

34_YOLOv5网络详解

1.1 简介 YOLOV5是YOLO&#xff08;You Only Look Once&#xff09;系列目标检测模型的一个重要版本&#xff0c;由 Ultralytics 公司的Glenn Jocher开发并维护。YOLO系列以其快速、准确的目标检测能力而闻名&#xff0c;尤其适合实时应用。YOLOV5在保持高效的同时&#xff0c…

El-Table 表格的表头字段切换

最近写了一个小功能&#xff0c;比较有意思&#xff0c;特此博客记录。 提出需求&#xff1a;需要表头字段变化&#xff0c;但是我在官网上的表格相关上查找&#xff0c;没有发现便捷方法。 于是我有两个想法&#xff1a;1.做三个不同的表格。2.做一个表格使用不同的表头字段。…

2024.7.24 远程连接到另一设备(win)上的vrep时无响应(防火墙!)

Windows防火墙禁止了软件的端口的通信&#xff0c;打开即可 如何设置Windows 7 防火墙端口规则