尚品汇之通过自定义注解实现分布式锁

        由于分布式锁的业务逻辑大致都相似,我们选择通过注解来实现分布式锁,类似于spring中的@Transactional。

        首先,我们先自定义一个注解叫做gmallCache,自定义一个注解时需要指定两个元注解,@Target:注解标志的位置 @Rention:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在。接着我们在注解中定义了一个属性prefix,用来指定分布式锁中key的前缀。

/**
 * @author lilg
 * @date 2022/11/25
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GmallCache {
    /**
     * 分布式锁的前缀
     * @return
     */
    String prefix() default "cache";
}

        接着,开始进行切的编写,首先我们需要将这个切方法交给spring管理,所以要加上@compent,并且,由于它是切方法,所以要加上@Aspect注解。经过思考后,我觉得这里应该使用环绕增强,@around注解。并对我们之前自定义的注解进行了切的操作。

@Around(value = "@annotation(com.atguigu.gmall.common.cache.GmallCache)")

        接着,进行具体业务逻辑的编写,首先我们通过joinPoint对象的getSignature方法获取到MethodSignature对象,在该对象中获取到GMallCache这个注解对象来获得key的前缀。接着通过joinpoint的getArgs方法来获取参数,用来拼接redisKey。

@Around(value = "@annotation(com.atguigu.gmall.common.cache.GmallCache)")
    public Object cacheAroundAdvice(ProceedingJoinPoint joinPoint){
        /**
         * 1.获取方法上的注解
         * 2.获取注解上的参数
         * 3.拼接key
         * 4.redis中操作(分布式锁)
         */
        Object object  = new Object();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        GmallCache gmallCache = signature.getMethod().getAnnotation(GmallCache.class);
        //前缀
        String prefix = gmallCache.prefix();
        //获取到方法上的参数
        Object[] args = joinPoint.getArgs();
        //拼接key
        String key = prefix + Arrays.asList(args) + RedisConst.SKUKEY_SUFFIX;

        接着,开始走缓存常见的三种情况的逻辑。缓存穿透(通过返回空值来解决),缓存雪崩(通过随机过期时间来解决),缓存击穿(通过分布式锁来解决)。

        在这里,我们抽出了一个方法gitCache来判定缓存中是否有对象。这里通过之前的MethodSignature对象来获取到返回值Class对象。并在返回时进行转化。为通用性考虑,这里返回值为Object类型。并且由于自定义了redis的序列化器,返回值必定为String类型。

   private Object gitCache(String key,MethodSignature signature) {
        String sObject = (String) redisTemplate.opsForValue().get(key);
        Class returnType = signature.getReturnType();
        if (!StringUtils.isEmpty(sObject)){
            return JSON.parseObject(sObject,returnType);
        }
        return null;
    }

        如果 gitCache返回值为空,开始走分布式锁的逻辑,这里我们通过redission来实现分布式锁,通过getLock的方法获取锁对象,通过tryLock的方法获取锁。

 // 判断是否获取到数据
            if (null == object){
                //分布式锁业务逻辑
                //从数据库中获取数据(执行代码块中的代码)
                //先获得锁
                RLock lock = redissonClient.getLock(key + ":lock");
                //尝试获取锁
                boolean flag = lock.tryLock(RedisConst.SKULOCK_EXPIRE_PX1, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);

       如果获取锁成功。 通过joinPoint.proceed(args);这句话代码运行之前方法体中的代码,而方法体中的代码为查询数据库中是否存在。

  如果数据库中返回值为空,走缓存穿透逻辑。这里,为防止意外情况发送,我们还是调用了之前的MethodSignature对象来获取他的返回值(Class),最后通过Class的无参构造来创建一个Object类型(具体参考我上一篇博客)。

 //防止缓存穿透
                        if (null == object){
                            //通过分布式锁调用无参构造
                            Class returnType = signature.getReturnType();
                            Object o = returnType.getConstructor().newInstance();
                            redisTemplate.opsForValue().set(key, JSON.toJSONString(o),
                                    RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);
                            return o;
                        }

        如果不为空,则直接将其放入缓存中。最后通过try finally进行释放锁操作,防止死锁。如果没有获取到锁,则等待一秒后重写获取,实现自旋操作。

 else {
                    Thread.sleep(1000);
                    //自旋转
                    return cacheAroundAdvice(joinPoint);
                }

         接着,需要对整个方法内容进行try catch,如果其中出现问题,直接从数据库中查询,用做兜底方案。完整代码如下

**
 * @author lilg
 * @date 2022/11/25
 */
@Component
@Aspect
public class GmallCacheAspect {
    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RedisTemplate redisTemplate;


    //切注解
    @SneakyThrows
    @Around(value = "@annotation(com.atguigu.gmall.common.cache.GmallCache)")
    public Object cacheAroundAdvice(ProceedingJoinPoint joinPoint){
        /**
         * 1.获取方法上的注解
         * 2.获取注解上的参数
         * 3.拼接key
         * 4.redis中操作(分布式锁)
         */
        Object object  = new Object();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        GmallCache gmallCache = signature.getMethod().getAnnotation(GmallCache.class);
        //前缀
        String prefix = gmallCache.prefix();
        //获取到方法上的参数
        Object[] args = joinPoint.getArgs();
        //拼接key
        String key = prefix + Arrays.asList(args) + RedisConst.SKUKEY_SUFFIX;
        try {
            // 从缓存获取
            object = gitCache(key,signature);
            // 判断是否获取到数据
            if (null == object){
                //分布式锁业务逻辑
                //从数据库中获取数据(执行代码块中的代码)
                //先获得锁
                RLock lock = redissonClient.getLock(key + ":lock");
                //尝试获取锁
                boolean flag = lock.tryLock(RedisConst.SKULOCK_EXPIRE_PX1, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);
                //如果获取锁成功
                if (flag){
                    try {
                        object = joinPoint.proceed(args);
                        //防止缓存穿透
                        if (null == object){
                            //通过分布式锁调用无参构造
                            Class returnType = signature.getReturnType();
                            Object o = returnType.getConstructor().newInstance();
                            redisTemplate.opsForValue().set(key, JSON.toJSONString(o),
                                    RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);
                            return o;
                        }
                        //不为空 将数据存入缓存
                        redisTemplate.opsForValue().set(key, JSON.toJSONString(object),
                                RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);
                        return object;
                    } finally {
                        lock.unlock();
                    }
                } else {
                    Thread.sleep(1000);
                    //自旋转
                    return cacheAroundAdvice(joinPoint);
                }
            }else {
                return object;
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
        //如果最后出现问题 数据库方案兜底
        return joinPoint.proceed(args);
    }

    private Object gitCache(String key,MethodSignature signature) {
        String sObject = (String) redisTemplate.opsForValue().get(key);
        Class returnType = signature.getReturnType();
        if (!StringUtils.isEmpty(sObject)){
            return JSON.parseObject(sObject,returnType);
        }
        return null;
    }
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lil G.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值