springboot2.3 集成redisson分布式锁 自定义注解形式

这里不介绍springboot集成和redis的配置集成只介绍redisson分布式锁 

主要依赖

 <!--redis-->
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

 <!--分布式锁redisson-->
<dependency>
      <groupId>org.redisson</groupId>
      <artifactId>redisson</artifactId>
      <version>3.5.0</version>
    </dependency>
 <!--aop-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

redission配置文件

application-single-dev.yml

单机模式

singleServerConfig:
  idleConnectionTimeout: 10000 #连接空闲超时(毫秒),默认10000
  connectTimeout: 10000 #连接空闲超时(毫秒),默认10000
  timeout: 3000 #命令等待超时(毫秒),默认3000
  retryAttempts: 3 #命令失败重试次数
  retryInterval: 1500 #命令重试发送时间间隔(毫秒),默认1500
  password: null
  subscriptionsPerConnection: 5 #单个连接最大订阅数量,默认5
  clientName: null #客户端名称
  address: "redis://127.0.0.1:6379"
  subscriptionConnectionMinimumIdleSize: 1 #发布和订阅连接的最小空闲连接数,默认1
  subscriptionConnectionPoolSize: 50 #发布和订阅连接池大小,默认50
  connectionMinimumIdleSize: 24 #最小空闲连接数,默认32
  connectionPoolSize: 64 #连接池大小,默认64
  database: 0
  dnsMonitoringInterval: 5000 #DNS监测时间间隔(毫秒),默认5000
threads: 16
nettyThreads: 32
codec: !<org.redisson.codec.JsonJacksonCodec> {}
#"transportMode": "NIO"

集群模式:

clusterServersConfig:
  idleConnectionTimeout: 10000
  pingTimeout: 1000
  connectTimeout: 10000
  timeout: 3000
  retryAttempts: 3
  retryInterval: 1500
  reconnectionTimeout: 3000
  failedAttempts: 3
  password: null
  subscriptionsPerConnection: 5
  clientName: null
  loadBalancer: !<org.redisson.connection.balancer.RoundRobinLoadBalancer> {}
  slaveSubscriptionConnectionMinimumIdleSize: 1
  slaveSubscriptionConnectionPoolSize: 50
  slaveConnectionMinimumIdleSize: 32
  slaveConnectionPoolSize: 64
  masterConnectionMinimumIdleSize: 32
  masterConnectionPoolSize: 64
  readMode: "SLAVE"
  nodeAddresses:
  - "redis://127.0.0.1:7004"
  - "redis://127.0.0.1:7001"
  - "redis://127.0.0.1:7000"
  scanInterval: 1000
threads: 0
nettyThreads: 0
codec: !<org.redisson.codec.JsonJacksonCodec> {}
#"transportMode":"NIO"

注入配置文件

这里读取配置文件根据环境读取的

RedissionConfig
package com.dzhjj.dzhjjapi.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.SingleServerConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.redisson.config.Config;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;

import java.io.IOException;

/**
 * @Auther: heng
 * @Date: 2020/12/3 13:56
 * @Description: redission分布式锁
 * @Version 1.0.0
 */
@Order(2)
@Slf4j
@Configuration
public class RedissionConfig {
  //@Autowired
  //  private RedisProperties redisProperties;
    @Autowired
    private Environment env;
    /**
     * 单机模式
     * @return
     *//*
    @Bean
    public RedissonClient redissonClient() {
        RedissonClient redissonClient;
        Config config = new Config();
        String url = "redis://" + redisProperties.getHost() + ":" + redisProperties.getPort();
        SingleServerConfig serverConfig = config.useSingleServer()
                .setAddress(url)
                .setTimeout(3000)
                .setDatabase(redisProperties.getDatabase())
                .setConnectionPoolSize(64)
                .setConnectionMinimumIdleSize(50);
        if (StringUtils.isNotBlank(redisProperties.getPassword())) {
            serverConfig.setPassword(redisProperties.getPassword());
        }
        try {
            redissonClient = Redisson.create(config);
            return redissonClient;
        } catch (Exception e) {
            log.error("RedissonClient init redis url:[{}], Exception:", url, e);
            return null;
        }
    }*/
    @Bean(destroyMethod = "shutdown")
    public RedissonClient redisson() throws IOException {
        Config config = Config.fromYAML(new ClassPathResource("application-single-"+env.getActiveProfiles()[0]+".yml").getInputStream());
        RedissonClient redisson = Redisson.create(config);
        return redisson;
    }
}

测试分布式锁代码

@Autowired
private RedissonClient redissonClient; 

public Object testRedisLoak(){
        Boolean result=false;
        final String lockKey= "RedissonLock";
        RLock lock=redissonClient.getLock(lockKey);
        try {
            //TODO:第一个参数30s=表示尝试获取分布式锁,并且最大的等待获取锁的时间为30s
            //TODO:第二个参数10s=表示上锁之后,10s内操作完毕将自动释放锁
            Boolean cacheRes=lock.tryLock(10,10, TimeUnit.SECONDS);
            return cacheRes;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //TODO:释放锁
            lock.unlock();
        }
        return result;
    }

开始集成注解模式

定义注解: RsionLock


import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.*;

/**
 * Description:
 *
 * <p>
 *      分布式锁的注解
 * </p>
 * 在同一个注解中成对使用即可,比如示例代码中,value和path就是互为别名。
 * 但是要注意一点,@AliasFor标签有一些使用限制,但是这应该能想到的,比如要求互为别名的属性属性值类型,默认值,都是相同的,互为别名的注解必须成对出现,比如value属性添加了@AliasFor(“path”),
 * 那么path属性就必须添加@AliasFor(“value”),另外还有一点,互为别名的属性必须定义默认值。
 *
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface RsionLock {

    /**
     * 支持spring El表达式
     * 锁的资源,key。
     */
    //@AliasFor("value")
    String key() default "";

    /**
     *  支持spring El表达式
     * 锁的资源,value。
     * 如果不为空这回拼接key
     */
    //@AliasFor("key")
    String value() default "";

    /**
     * 持锁时间,单位:秒
     * TODO:10s=表示上锁之后,10s内操作完毕将自动释放锁
     */
    long lockTime() default 10;

    /**
     * 当获取失败时候动作
     */
   // LockFailAction action() default LockFailAction.CONTINUE;

    /*public enum LockFailAction{
     *//** 放弃 *//*
        GIVEUP,
        *//** 继续 *//*
        CONTINUE;
    }*/


    /**
     * 等待时间,单位:秒
     * TODO:10s=表示尝试获取分布式锁,并且最大的等待获取锁的时间为10s
     * @return
     */
    int waitTime() default 10;

    /**
     * 是否自动延期机制
     * 默认不自动延期
     * @return
     */
    boolean isWatchDog() default false;

    /**
     * 延期时间 单位:秒
     * @return
     */
    int watchDogTime() default 2;
}

定义该注解切面 LockAspectConfiguration


import com.dzhjj.dzhjjapi.annotations.RsionLock;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
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.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @Auther: heng
 * @Date: 2020/12/3 17:50
 * @Description: LockAspectConfiguration
 * @Version 1.0.0
 */
@Slf4j
@Aspect
@Configuration
public class LockAspectConfiguration {

    private ExpressionParser parser = new SpelExpressionParser();

    private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();


    @Autowired
    private RedissonClient redissonClient;

    /**
     * 定义切入点
     */
    @Pointcut("@annotation(com.dzhjj.dzhjjapi.annotations.RsionLock)")
    private void lockPoint() {
    }

    /**
     * 环绕通知
     *
     * @param pjp pjp
     * @return  方法返回结果
     * @throws Throwable throwable
     */
    @Around("lockPoint()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        RsionLock lockAction = method.getAnnotation(RsionLock.class);
        String logKey = getLogKey(lockAction, pjp, method);
        RLock lock=redissonClient.getLock(logKey);
        //TODO:第一个参数30s=表示尝试获取分布式锁,并且最大的等待获取锁的时间为30s
        //TODO:第二个参数10s=表示上锁之后,10s内操作完毕将自动释放锁
        Boolean cacheRes=lock.tryLock(lockAction.waitTime(),lockAction.lockTime(), TimeUnit.SECONDS);
        if (!cacheRes) {
            log.debug("get lock failed : " + logKey);
            return null;
        }
        //得到锁,执行方法,释放锁
        log.debug("get lock success : " + logKey);
        try {
            return pjp.proceed();
        } catch (Exception e) {
            log.error("execute locked method occured an exception", e);
        } finally {
           lock.unlock();
           log.debug("release lock : " + logKey + (cacheRes ? " success" : " failed"));
        }
        return null;
    }

    /**
     * 获得分布式缓存的key
     *
     * @param lockAction 注解对象
     * @param pjp        pjp
     * @param method     method
     * @return String
     */
    private String getLogKey(RsionLock lockAction, ProceedingJoinPoint pjp, Method method) {
        String key = lockAction.key();
        String value = lockAction.value();
        Object[] args = pjp.getArgs();
        return parse(key, method, args) + "_" + parse(value, method, args);
    }

    /**
     * 解析spring EL表达式
     *
     * @param key    key
     * @param method method
     * @param args   args
     * @return parse result
     */
    private String parse(String key, Method method, Object[] args) {
        String[] params = discoverer.getParameterNames(method);
        if (null == params || params.length == 0 || !key.contains("#")) {
            return key;
        }
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < params.length; i++) {
            if (null != args[i]){
                context.setVariable(params[i], args[i]);
            }else {
                context.setVariable(params[i],"");
            }
        }
        return parser.parseExpression(key).getValue(context, String.class);
    }

}

测试代码  理应在service层测试的 我这就在controller层测试了

 @RsionLock(key = "TestNewController_readlock", value = "#key",lockTime = 6,waitTime = 6)
  @RequestMapping("/readlock")
  public Object readlock(String key){
      return key;
  }

访问url

http://localhost:8010/dzhjjapi/testnew/readlock?key=test

ps: 访问之后测试锁 可以把时间加大一点把 释放锁注释掉  然后看看redis持锁状态和url访问拿锁状态 主要是一个是等待锁的时间一个是释放锁的时间

结果

猜你喜欢

转载自blog.csdn.net/qq_39313596/article/details/110630733