Spring distributed lock starter package (Redisson)

Due to business rights services, the same data may be operated by multiple people, so there is a problem of competition for shared resource operations. If it is controlled separately for the business, the cost is relatively high. The specific control must be coupled to the business. It is easy to cause competition deadlock problem, so share a start of distributed lock based on AOP. Welcome to discuss together, and welcome suggestions for enhancements.
Here, aop's MethodInterceptor is used for method-level interception, and the entire process is completed with the use of annotations.

1. Main Notes

1. Start the Redission configuration annotation Enable annotation:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({RedissonConfiguration.class, LockMethodHelper.class})
public @interface EnableRedissonLock {

}

2. Whether the method starts the lock

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedLock {

    String fieldKey();
}

3.Key stitching notes:

@Documented
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface KeyParam {
    /**
     * 如果动态参数在command对象中,那么就需要设置columns的值为command对象中的属性名可以为多个,否则不需要设置该值
     * <p>例1:public void test(@KeyParam({"id"}) MemberCommand member)
     * <p>例2:public void test(@KeyParam({"id","loginName"}) MemberCommand member)
     * <p>例3:public void test(@KeyParam String memberId)
     */
    String[] columns() default {};

    /**
     * 获取锁等待时间
     *
     * @return 等待时间
     */
    long waitTime() default 1L;

    /**
     * 锁释放时间
     *
     * @return 自动释放时间
     */
    long leaseTime() default 5L;

}

Second, the configuration of Redisssion

1. Basic configuration parameter encapsulation

@Setter
@Getter
@Configuration
@ConfigurationProperties(prefix = "spring.redisson")
public class RedissonProperties {

    /**
     * redis访问地址
     */
    private String address;
    /**
     * 配置模式
     */
    private String connectModel;
    /**
     * 密码
     */
    private String password;
    /**
     * 超时时间
     */
    private int timeout;
    /**
     *  连接池大小
     */
    private int connectionPoolSize;
    /**
     * Minimum idle Redis connection amount.
     */
    private int connectionMinimumIdleSize;
}

2. Configure and execute the available RedissonClient (current stand-alone mode):

@Configuration
@EnableConfigurationProperties(RedissonProperties.class)
public class RedissonConfiguration {

    @Bean
    @ConditionalOnMissingBean(RedissonProperties.class)
    @ConditionalOnProperty(prefix = "spring.redisson", value = {"address", "password"})
    RedissonClient redissonSingleClient(RedissonProperties redissonProperties) {
        Config config = new Config();
        config.useSingleServer()
            .setAddress(redissonProperties.getAddress())
            .setTimeout(redissonProperties.getTimeout())
            .setConnectionPoolSize(redissonProperties.getConnectionPoolSize())
            .setConnectionMinimumIdleSize(redissonProperties.getConnectionMinimumIdleSize())
            .setPassword(redissonProperties.getPassword());
        return Redisson.create(config);
    }


}

3. Target method:

@Slf4j
public class LockMethodHelper implements MethodInterceptor {


    public static final String BEFORE_KEY = "LOCK:";

    @Autowired(required = false)
    private RedissonClient redissonClient;

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        Method method = methodInvocation.getMethod();
        Object[] args = methodInvocation.getArguments();
        //锁key
        String key = builderKey(method, args);
        // key不存在的情况下,不加锁处理,直接执行目标方法
        if (StringUtils.isEmpty(key)) {
            return methodInvocation.proceed();
        }
        RLock lock = redissonClient.getLock(key);
        try {
            if (lock.tryLock()) {
                // 通过反射机制调用目标方法
                return methodInvocation.proceed();
            } else {
                log.warn("资源竞争太大了,获取锁失败了,key为:{}", key);
                throw new LockException("人太多了,系统小哥已经处理不过来了...");
            }
        } catch (Exception e) {
            log.error("执行目标方法抛出异常,异常信息为:{}", e);
            throw e;
        } finally {
            lock.unlock();
        }
    }

    private String builderKey(Method method, Object[] args) throws NoSuchFieldException, IllegalAccessException {
        NeedLock needLock = method.getAnnotation(NeedLock.class);
        if (null == needLock) {
            return null;
        }
        // 锁的前半部分key
        String key = BEFORE_KEY + needLock.fieldKey();
        // 迭代全部参数的注解,根据使用KeyParam的注解的参数所在的下标,来获取args中对应下标的参数值拼接到前半部分key上
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        for (int i = 0; i < parameterAnnotations.length; i++) {
            for (Annotation annotation : parameterAnnotations[i]) {
                // 当前参数的注解不包含keyparam
                if (!annotation.annotationType().getClass().isInstance(KeyParam.class)) {
                    continue;
                }
                // 当前参数的注解包含keyparam,获取注解配置的值
                String[] columns = ((KeyParam) annotation).columns();
                if (columns.length == 0) {
                    // 普通数据类型直接拼接
                    if (null == args[i]) {
                        log.error("动态参数不能为null!");
                        throw new RuntimeException("动态参数不能为null!");
                    }
                    key += args[i];
                } else {
                    // keyparam的columns值不为null,所以当前参数应该是对象类型
                    for (int j = 0; j < columns.length; j++) {
                        Class<? extends Object> clasz = args[i].getClass();
                        Field declaredField = clasz.getDeclaredField(columns[j]);
                        declaredField.setAccessible(true);
                        Object value = declaredField.get(args[i]);
                        key += value;
                    }
                }
            }
        }
        return key;
    }
}

Four, use case demo:

1. Interception range configuration, the user configures the interception package range

@EnableRedissonLock
@Configuration
public class DisturiteLockConfig {

    public static final String traceExecution = "execution(* com.workstation.biz.service..*.*(..))";

    @Autowired
    LockMethodHelper lockMethodHelper;


    @Bean
    public DefaultPointcutAdvisor defaultPointcutAdvisor() {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(traceExecution);
        // 配置增强类advisor
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
        advisor.setPointcut(pointcut);
        advisor.setAdvice(lockMethodHelper);
        return advisor;
    }
}

2. Method:

   // 基于对象的使用
    @NeedLock(fieldKey = "user:uuid")
    public void userChange(@KeyParam(columns = {"uuid"}) UserDTO user) {

    }
// 基于字段的使用
   @NeedLock(fieldKey = "user:uuid")
    public void userChange(@KeyParam String uuid) {

    }

Insert picture description here

Published 41 original articles · Liked 14 · Visitors 10,000+

Guess you like

Origin blog.csdn.net/Yunwei_Zheng/article/details/104986463