【分布式】注解实现分布式锁

在这里插入图片描述
对于有大流量通过的接口,类似秒杀;为限制用户在一次活动中只能秒杀一件商品,在多机部署秒杀服务的情况下可以使用分布式锁来将用户id与商品id进行绑定并上锁,接下来介绍如何使用注解 + aop + Redisson实现复用性极强的分布式锁

导入必要依赖

<!-- 非必须 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

<!-- 非必须 -->
<dependency>
   <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.8</version>
</dependency>

<!-- 必须 -->
<dependency>
   <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<!-- 必须 -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.5.0</version>
</dependency>

定义注解

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLock {

    /** 防止redis的key发生冲突,所以会对key加上一些统一的前缀,例如:insertXxx, DeleteXxx */
    String lockName() default "";

    /** SPEL 表达式,需要通过 SpelUtil.java 工具类来解析 */
    String key() default  "";

    /** 过期时间 */
    int expire() default 5000;

    /** 时间单位 */
    TimeUnit timeUnit() default TimeUnit.MILLISECONDS;

}

切面处理

@RedisLock 所注解的方法,会被 RedisLockAspect 进行切面管理,代码如下:

import cn.hutool.core.util.StrUtil;
import com.example.redisson.annotation.RedisLock;
import com.example.redisson.util.RedissonUtil;
import com.example.redisson.util.SpelUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Slf4j
@Aspect
@Component
public class RedisLockAspect {

    @Autowired
    private RedissonUtil redissonUtil;


    /** 分布式锁前缀 */
    private static final String REDISSON_LOCK_PREFIX = "redisson_lock:";

    @Around(value = "@annotation(redisLock)", argNames = "joinPoint,redisLock")
    public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
        log.info("进入 RedisLockAspect 切面");

        String lockKey = getRedisKey(joinPoint, redisLock.lockName(), redisLock.key());
        log.info("解析后:{}", lockKey);

        Object result = null;
        try {
            // 尝试获取锁,等待5秒,自己获得锁后一直不解锁则在指定时间后自动解锁
            boolean lock = redissonUtil.tryLock(lockKey, redisLock.timeUnit(), 5000, redisLock.expire());
            if (lock) {
                log.info("线程:{},获取到了锁", Thread.currentThread().getName());
                Thread.sleep(10000);
                log.info("======获得锁后进行相应的操作======" + Thread.currentThread().getName());
                //执行方法
                result = joinPoint.proceed();
                redissonUtil.unlock(lockKey);  //释放锁
                log.info("=========释放锁========" + Thread.currentThread().getName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 将 spel 表达式转换为字符串
     *
     * @param joinPoint 切点
     * @return redisKey
     */
    private String getRedisKey(ProceedingJoinPoint joinPoint, String lockName, String spel) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();
        Object target = joinPoint.getTarget();
        Object[] arguments = joinPoint.getArgs();
        return REDISSON_LOCK_PREFIX + lockName + StrUtil.COLON + SpelUtil.parse(target, spel, targetMethod, arguments);
    }

}

相关工具类

SpelUtil 解析表达式

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;

public class SpelUtil {

    /**
     * 支持 #p0 参数索引的表达式解析
     * @param rootObject 根对象,method 所在的对象
     * @param spel 表达式
     * @param method ,目标方法
     * @param args 方法入参
     * @return 解析后的字符串
     */
    public static String parse(Object rootObject,String spel, Method method, Object[] args) {
        if (StrUtil.isBlank(spel)) {
            return StrUtil.EMPTY;
        }
        //获取被拦截方法参数名列表(使用Spring支持类库)
        LocalVariableTableParameterNameDiscoverer u =
                new LocalVariableTableParameterNameDiscoverer();
        String[] paraNameArr = u.getParameterNames(method);
        if (ArrayUtil.isEmpty(paraNameArr)) {
            return spel;
        }
        //使用SPEL进行key的解析
        ExpressionParser parser = new SpelExpressionParser();
        //SPEL上下文
        StandardEvaluationContext context = new MethodBasedEvaluationContext(rootObject,method,args,u);
        //把方法参数放入SPEL上下文中
        for (int i = 0; i < paraNameArr.length; i++) {
            context.setVariable(paraNameArr[i], args[i]);
        }
        return parser.parseExpression(spel).getValue(context, String.class);
    }
}

RedissonUtil 加锁解锁

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class RedissonUtil {
    @Autowired
    private RedissonClient redissonClient; // RedissonClient已经由配置类生成,这里自动装配即可

    /**
     * 锁住不设置超时时间(拿不到lock就不罢休,不然线程就一直block)
     * @param lockKey
     * @return org.redisson.api.RLock
     */
    public RLock lock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();
        return lock;
    }

    /**
     * leaseTime为加锁时间,单位为秒
     * @param lockKey
     * @param leaseTime
     * @return org.redisson.api.RLock
     */
    public RLock lock(String lockKey, long leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(leaseTime, TimeUnit.SECONDS);
        return null;
    }

    /**
     * timeout为加锁时间,时间单位由unit确定
     * @param lockKey
     * @param unit
     * @param timeout
     * @return org.redisson.api.RLock
     */
    public RLock lock(String lockKey, TimeUnit unit, long timeout) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(timeout, unit);
        return lock;
    }

    /**
     * 尝试获取锁
     * @param lockKey
     * @param unit
     * @param waitTime
     * @param leaseTime
     * @return boolean
     */
    public boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(waitTime, leaseTime, unit);
        } catch (InterruptedException e) {
            return false;
        }
    }

    /**
     * 通过lockKey解锁
     * @param lockKey
     * @return void
     */
    public void unlock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.unlock();
    }

    /**
     * 直接通过锁解锁
     * @param lock
     * @return void
     */
    public void unlock(RLock lock) {
        lock.unlock();
    }

}

配置

RedissonConfig 分布式锁配置

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedissonConfig {

    @Value("${redisson.address}")
    private String addressUrl;

    @Bean
    public RedissonClient getRedisson() throws Exception{
        RedissonClient redisson = null;
        Config config = new Config();
        config.useSingleServer()
                .setAddress(addressUrl);
        redisson = Redisson.create(config);
        System.out.println(redisson.getConfig().toJSON().toString());
        return redisson;
    }

}

application.yml redis连接配置

spring:
  redis:
    database: 1
    host: 127.0.0.1
    port: 6379
redisson:
  address: redis://127.0.0.1:6379

使用

@Service
public class HelloServiceImpl implements HelloService {
    /** 重点:key的格式 */
    @Override
    @RedisLock(lockName = "insert", key = "#annoDemo.id + ':' + #annoDemo.name")
    public void test(AnnoDemo annoDemo) {
        System.out.println(annoDemo.toString());
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值