springboot2.3 integrated redisson distributed lock custom annotation form

This does not introduce springboot integration and redis configuration integration, only redisson distributed locks 

Mainly rely on

 <!--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 configuration file

application-single-dev.yml

Stand-alone mode

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"

Cluster mode:

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"

 

Inject the configuration file

The configuration file read here is read according to the environment

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

 

Test distributed lock code

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

 

 

Start to integrate annotation mode

Definition annotation: 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;
}

Define the annotation aspect 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);
    }

}

 

The test code should be tested at the service layer. I will test it at the controller layer.

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

Visit url

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

ps: After accessing the test lock, you can increase the time a bit, comment out the release lock,  and then look at the redis lock status and the URL access lock status. The main one is the time to wait for the lock and the other is the time to release the lock.

result

Guess you like

Origin blog.csdn.net/qq_39313596/article/details/110630733