关于分布式的Aop(这里没写延时双删)

发布于:2025-04-07 ⋅ 阅读:(32) ⋅ 点赞:(0)

关于分布式锁的Aop(这里没写延时双删)

1.首先关于分布式锁

分布式锁主流的实现机制(都是为了跨jvm的互斥机制来控制共享资源)

基本思路

1. 获取锁

步骤

  1. 构建锁的键:根据业务需求生成一个唯一的锁键,例如lock:sku:123
  2. 设置锁:使用Redis的SETNX命令(Set if Not Exists)尝试设置锁键的值。如果键不存在,则设置成功,表示获取锁。
  3. 设置过期时间:为了防止锁持有者崩溃导致锁无法释放,设置一个合理的过期时间(TTL),例如30秒。

2. 检查锁状态

步骤

  1. 判断是否获取锁:如果SETNX返回true,表示成功获取锁。
  2. 未获取锁时重试:如果SETNX返回false,表示锁已被其他线程持有,可以选择自旋等待或重试。

3. 释放锁

步骤

  1. 删除锁键:在完成业务逻辑后,删除锁键以释放锁。
  2. 避免误删:为了防止误删其他线程的锁,可以使用Lua脚本确保只有锁的持有者才能删除锁。

1.1redis实现

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);
    }

1.2通过redission

try {
            //定义获取sku信息的key
            String skuKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
            //判断里面是否有
            skuInfo = (SkuInfo) redisTemplate.opsForValue().get(skuKey);

            //判断skuInfo是否有
            if(skuInfo==null){
                //设置skuLock
                String skuLock = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKULOCK_SUFFIX;
                //先注入redisson
                RLock rlLock= redissonClient.getLock(skuLock);
                //设置锁
                boolean res = rlLock.tryLock(RedisConst.SKULOCK_EXPIRE_PX1, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);

                //判断是否拥有锁
                if(res){
                    //拥有锁,查询数据
                    skuInfo = getSkuInfoDB(skuId);
                    try {
                        //判断是否为空
                        if(skuInfo==null){
                            //new一个
                            skuInfo = new SkuInfo();
                            redisTemplate.opsForValue().set(skuKey,skuInfo,RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);

                        }else{
                            //不为空,存储到redis中
                            redisTemplate.opsForValue().set(skuKey,skuInfo,RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);
                            //释放锁
                        }
                    } finally {
                        //释放锁
                        rlLock.unlock();
                    }

                }else{
                    //没有获得锁则自旋
                    Thread.sleep(1000);
                    getSkuInfoByRedisson(skuId);
                }


            }else{
                //有就直接返回
                return skuInfo;
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return getSkuInfoDB(skuId);

2.关于使用AOp来实现

为什么要使用aop的思想:因为我们不可能只缓存一个,有多个类似的,

依旧是基本的思想

可以看看spring官网,上面有

https://docs.spring.io/spring-framework/reference/core/aop/ataspectj/at-aspectj.html

package com.atguigu.gmall.common.cache;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.constant.RedisConst;
import lombok.SneakyThrows;
import org.apache.commons.lang.StringUtils;
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 java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * @author ChenZhou
 * @date 2025-04-01 18:35
 *
 * 高并发
 *  定义Key
 *  根据key获取尝试从redis获取数据
 *  判断是否有缓存  有直接返回
 *  没有的话定义锁的key--->尝试加锁
 *                       ------>睡眠重试
 *                    --->成功的话 又查询
 *                       --->失败的话,赋值成null,
 *              无论有没有值都需要释放锁
 *
 *
 *  释放锁lua脚本
 */
/*需要能顾被扫描到*/
@Component
//这是一个切面
@Aspect
public class GmallCacheAspect {

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RedisTemplate redisTemplate;


    //定义一个环绕通知
    @Around("@annotation(com.atguigu.gmall.common.cache.GmallCache)")
    @SneakyThrows
    //需不需要添加什么参数 需要加入连接点
    public Object mallCacheAspectMethod(ProceedingJoinPoint point){

        //创建一个新对象
        Object object = new Object();

        //拦截
        MethodSignature signature = (MethodSignature) point.getSignature();
        //现在我需要获取方法的注解,因为我需要获取里面的参数和方法名
        //获取 GmallCache 注解实例
        GmallCache annotation = signature.getMethod().getAnnotation(GmallCache.class);
        //获取前缀组成skuKey
        String prefix = annotation.prefix();

        //SkuKey:prefix+skuId(这个是方法里面的参数)
        //point.getArgs().toString(); 这个样子的不对吧
        String key = prefix+ Arrays.asList(point.getArgs()).toString();

        //  从缓存中获取数据
        //  类似于skuInfo = (SkuInfo) redisTemplate.opsForValue().get(skuKey);
        //我做的是切面,所以定义一个方法
        object = this.getRedisDate(key,signature);

        //判断是否获取到了缓存
        if(object ==null){
            //没有缓存,需要加锁
            RLock lock = redissonClient.getLock(key + "lock");
            //获取锁
            boolean res = lock.tryLock(RedisConst.SKULOCK_EXPIRE_PX1, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);

            if(res){
                try {
                    //获取到了锁
                    //执行业务逻辑:直接从数据库获取数据
                    //如何查询数据库---》方法体里面 就是查询数据
                    object = point.proceed(point.getArgs());
                    //如果object为空就得防止缓存穿透问题
                    if(object==null){
                        //Object obj = new Object();

                        //反射
                        Class aClass = signature.getReturnType();
                        Object obj = aClass.newInstance();
                        //放入
                        //不知道类型
                        //  将缓存的数据变为 Json 的 字符串
                        this.redisTemplate.opsForValue().set(key, JSON.toJSONString(object),RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);
                        return obj;

                    }
                    //不为空的话就直接存储到redis中,但是还是需要转换成json
                    this.redisTemplate.opsForValue().set(key, JSON.toJSONString(object),RedisConst.SKUKEY_TIMEOUT,TimeUnit.SECONDS);
                    return object;

                    //释放锁
                } finally {
                    lock.unlock();
                }

            }else{
                //没有获取到锁
                //睡眠
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }


        }else {
            return object;
        }


       //兜底
        return point.proceed(point.getArgs());
    }

    private Object getRedisDate(String key, MethodSignature signature) {
        //   从缓存中获取数据
        //类似于skuInfo = (SkuInfo) redisTemplate.opsForValue().get(skuKey);
        //返回类型--多种
        String string = (String) this.redisTemplate.opsForValue().get(key);
        //判断缓存中有没有数据
        if(!StringUtils.isEmpty(string)){
            //不为空,转换成json
           return  JSON.parseObject(string, signature.getReturnType());

        }

        return null;
    }
}

网站公告

今日签到

点亮在社区的每一天
去签到