Paquete de inicio de bloqueo distribuido por resorte (Redisson)

Debido a los servicios de derechos comerciales, la misma información puede ser operada por varias personas, por lo que existe un problema de competencia para las operaciones de recursos compartidos. Si se controla por separado para el negocio, el costo es relativamente alto. El control específico debe estar asociado al negocio. Es fácil causar un problema de punto muerto en la competencia, así que comparta un inicio de bloqueo distribuido basado en AOP. Bienvenidos a discutir juntos y bienvenidos sugerencias para mejoras.
Aquí, el MethodInterceptor de aop se utiliza para la intercepción a nivel de método, y todo el proceso se completa con el uso de anotaciones.

1. Notas principales

1. Inicie la anotación de configuración de Redission Habilite la anotación:

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

}

2. Si el método inicia el bloqueo

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

    String fieldKey();
}

3.Notas clave de costura:

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

}

Segundo, la configuración de Redisssion

1. Configuración básica de encapsulación de parámetros

@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 y ejecute el RedissonClient disponible (modo autónomo actual):

@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. Método de destino:

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

Cuatro, demostración de caso de uso:

1. Configuración del rango de intercepción, el usuario configura el rango del paquete de intercepción

@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. Método:

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

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

    }

Inserte la descripción de la imagen aquí

41 artículos originales publicados · Me gustaron 14 · Visitantes más de 10,000

Supongo que te gusta

Origin blog.csdn.net/Yunwei_Zheng/article/details/104986463
Recomendado
Clasificación