Spring Boot integra bloqueos distribuidos de Redisson (versión anotada)

        Redisson es una biblioteca de servicios y objetos distribuidos basada en clústeres residentes de Java basados ​​en Redis, que puede proporcionarnos una implementación rica de bloqueos distribuidos y colecciones seguras para subprocesos. El uso de Redisson en las aplicaciones Spring Boot puede implementar fácilmente algunos aspectos de las aplicaciones distribuidas, como bloqueos distribuidos, colecciones distribuidas, publicación y suscripción de eventos distribuidos, etc. Este artículo es un ejemplo detallado del uso de Redisson para implementar bloqueos distribuidos. En este ejemplo, definimos la anotación DistributedLock, que se puede marcar en el método, y cooperamos con el aspecto DistributedLockAspect y la interfaz encapsulada por el bloqueo distribuido IDistributedLock para implementar el Bloqueo distribuido de Redisson. Llamadas API.

Spring Boot integra Redisson

 1. Agregue dependencias relacionadas con Redisson en el archivo pom.xml

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.16.1</version>
</dependency>

2. Configure la información de conexión y los parámetros relacionados del modo independiente de Redisson en application.yml

spring:
  redis:
    host: localhost
    port: 6379
    password: null
redisson:
  codec: org.redisson.codec.JsonJacksonCodec
  threads: 4
  netty:
    threads: 4
  single-server-config:
    address: "redis://localhost:6379"
    password: null
    database: 0

3. Redission también admite configuraciones maestro-esclavo, clúster y centinela.

//主从模式
spring:
  redis:
    sentinel:
      master: my-master
      nodes: localhost:26379,localhost:26389
    password: your_password
redisson:
  master-slave-config:
    master-address: "redis://localhost:6379"
    slave-addresses: "redis://localhost:6380,redis://localhost:6381"
    password: ${spring.redis.password}

// 集群模式
spring:
  redis:
    cluster:
      nodes: localhost:6379,localhost:6380,localhost:6381,localhost:6382,localhost:6383,localhost:6384
    password: your_password
redisson:
  cluster-config:
    node-addresses: "redis://localhost:6379,redis://localhost:6380,redis://localhost:6381,redis://localhost:6382,redis://localhost:6383,redis://localhost:6384"
    password: ${spring.redis.password}

// 哨兵模式
spring:
  redis:
    sentinel:
      master: my-master
      nodes: localhost:26379,localhost:26389
    password: your_password
redisson:
  sentinel-config:
    master-name: my-master
    sentinel-addresses: "redis://localhost:26379,redis://localhost:26380,redis://localhost:26381"
    password: ${spring.redis.password}

 Encapsulación local de cerraduras distribuidas de Redisson

1. Defina la interfaz de bloqueo distribuido IDistributedLock

public interface IDistributedLock {
    /**
     * 获取锁,默认30秒失效,失败一直等待直到获取锁
     *
     * @param key 锁的key
     * @return 锁对象
     */
    ILock lock(String key);

    /**
     * 获取锁,失败一直等待直到获取锁
     *
     * @param key      锁的key
     * @param lockTime 加锁的时间,超过这个时间后锁便自动解锁; 如果lockTime为-1,则保持锁定直到显式解锁
     * @param unit     {@code lockTime} 参数的时间单位
     * @param fair     是否公平锁
     * @return 锁对象
     */
    ILock lock(String key, long lockTime, TimeUnit unit, boolean fair);

    /**
     * 尝试获取锁,30秒获取不到超时异常,锁默认30秒失效
     *
     * @param key     锁的key
     * @param tryTime 获取锁的最大尝试时间
     * @return
     * @throws Exception
     */
    ILock tryLock(String key, long tryTime) throws Exception;

    /**
     * 尝试获取锁,获取不到超时异常
     *
     * @param key      锁的key
     * @param tryTime  获取锁的最大尝试时间
     * @param lockTime 加锁的时间
     * @param unit     {@code tryTime @code lockTime} 参数的时间单位
     * @param fair     是否公平锁
     * @return
     * @throws Exception
     */
    ILock tryLock(String key, long tryTime, long lockTime, TimeUnit unit, boolean fair) throws Exception;

    /**
     * 解锁
     *
     * @param lock
     * @throws Exception
     */
    void unLock(Object lock);


    /**
     * 释放锁
     *
     * @param lock
     * @throws Exception
     */
    default void unLock(ILock lock) {
        if (lock != null) {
            unLock(lock.getLock());
        }
    }


}

2. Clase de implementación IDistributedLock

@Slf4j
@Component
public class RedissonDistributedLock implements IDistributedLock {

    @Resource
    private RedissonClient redissonClient;
    /**
     * 统一前缀
     */
    @Value("${redisson.lock.prefix:bi:distributed:lock}")
    private String prefix;

    @Override
    public ILock lock(String key) {
        return this.lock(key, 0L, TimeUnit.SECONDS, false);
    }

    @Override
    public ILock lock(String key, long lockTime, TimeUnit unit, boolean fair) {
        RLock lock = getLock(key, fair);
        // 获取锁,失败一直等待,直到获取锁,不支持自动续期
        if (lockTime > 0L) {
            lock.lock(lockTime, unit);
        } else {
            // 具有Watch Dog 自动延期机制 默认续30s 每隔30/3=10 秒续到30s
            lock.lock();
        }
        return new ILock(lock, this);
    }

    @Override
    public ILock tryLock(String key, long tryTime) throws Exception {
        return this.tryLock(key, tryTime, 0L, TimeUnit.SECONDS, false);
    }

    @Override
    public ILock tryLock(String key, long tryTime, long lockTime, TimeUnit unit, boolean fair)
            throws Exception {
        RLock lock = getLock(key, fair);
        boolean lockAcquired;
        // 尝试获取锁,获取不到超时异常,不支持自动续期
        if (lockTime > 0L) {
            lockAcquired = lock.tryLock(tryTime, lockTime, unit);
        } else {
            // 具有Watch Dog 自动延期机制 默认续30s 每隔30/3=10 秒续到30s
            lockAcquired = lock.tryLock(tryTime, unit);
        }
        if (lockAcquired) {
            return new ILock(lock, this);
        }
        return null;
    }

    /**
     * 获取锁
     *
     * @param key
     * @param fair
     * @return
     */
    private RLock getLock(String key, boolean fair) {
        RLock lock;
        String lockKey = prefix + ":" + key;
        if (fair) {
            // 获取公平锁
            lock = redissonClient.getFairLock(lockKey);
        } else {
            // 获取普通锁
            lock = redissonClient.getLock(lockKey);
        }
        return lock;
    }

    @Override
    public void unLock(Object lock) {
        if (!(lock instanceof RLock)) {
            throw new IllegalArgumentException("Invalid lock object");
        }
        RLock rLock = (RLock) lock;
        if (rLock.isLocked()) {
            try {
                rLock.unlock();
            } catch (IllegalMonitorStateException e) {
                log.error("释放分布式锁异常", e);
            }
        }
    }
}

 3. Defina el objeto de bloqueo ILock

import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.Objects;

/**
 * <p>
 * RedissonLock 包装的锁对象 实现AutoCloseable接口,在java7的try(with resource)语法,不用显示调用close方法
 * </p>

 * @since 2023-06-08 16:57
 */
@AllArgsConstructor
public class ILock implements AutoCloseable {
    /**
     * 持有的锁对象
     */
    @Getter
    private Object lock;
    /**
     * 分布式锁接口
     */
    @Getter
    private IDistributedLock distributedLock;

    @Override
    public void close() throws Exception {
        if(Objects.nonNull(lock)){
            distributedLock.unLock(lock);
        }
    }
}

4. Ejemplo de inyección de la interfaz IDistributedLock

// 定义接口
public interface IProductSkuSupplierMeasureService {
    /**
     * 保存SKU供应商供货信息
     *
     * @param dto
     * @return
     *
    Boolean saveSupplierInfo(ProductSkuSupplierInfoDTO dto);

    /**
     * 编辑SKU供应商供货信息
     *
     * @param dto
     * @return
     */
    Boolean editSupplierInfo(ProductSkuSupplierInfoDTO dto);
}    


Ejemplo de liberación manual de un candado 

// 实现类
@Service
public class ProductSkuSupplierMeasureServiceImpl 
        implements IProductSkuSupplierMeasureService {

    @Resource
    private IDistributedLock distributedLock;    
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean saveSupplierInfo(ProductSkuSupplierInfoDTO dto) {
        // 手动释放锁
        String sku = dto.getSku();
        ILock lock = null;
        try {
            lock = distributedLock.lock(dto.getSku(),10L, TimeUnit.SECONDS, false);
            // 业务代码
        } catch (Exception e) {
            log.error("保存异常", e);
            throw new BaseException(e.getMessage());
        } finally {
            if (Objects.nonNull(lock)) {
                distributedLock.unLock(lock);
            }
        }
        return Boolean.TRUE;
    }

}

 Utilice azúcar sintáctico de prueba con recursos para liberar bloqueos automáticamente

// 实现类
@Service
public class ProductSkuSupplierMeasureServiceImpl 
        implements IProductSkuSupplierMeasureService {

    @Resource
    private IDistributedLock distributedLock;    
   

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean editSupplierInfo(ProductSkuSupplierInfoDTO dto) {
       
        String sku = dto.getSku();
        // try-with-resources 语法糖自动释放锁
        try(ILock lock = distributedLock.lock(dto.getSku(),10L, TimeUnit.SECONDS, false)) {
            if(Objects.isNull(lock)){
                throw new IdempotencyException("Duplicate request for method still in process");
            }
            
            // 业务代码
        } catch (Exception e) {
            log.error("异常", e);
           throw new BaseException(e.getMessage());
        }
        return Boolean.TRUE;
   }
}

5. Utilice el aspecto AOP para realizar la vinculación de bloqueos distribuidos.

  Definir la anotación DistributedLock

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

    /**
     * 保证业务接口的key的唯一性,否则失去了分布式锁的意义 锁key
     * 支持使用spEl表达式
     */
    String key();

    /**
     * 保证业务接口的key的唯一性,否则失去了分布式锁的意义 锁key 前缀
     */
    String keyPrefix() default "";

    /**
     * 是否在等待时间内获取锁,如果在等待时间内无法获取到锁,则返回失败
     */
    boolean tryLok() default false;

    /**
     * 获取锁的最大尝试时间 ,会尝试tryTime时间获取锁,在该时间内获取成功则返回,否则抛出获取锁超时异常,tryLok=true时,该值必须大于0。
     *
     */
    long tryTime() default 0;

    /**
     * 加锁的时间,超过这个时间后锁便自动解锁
     */
    long lockTime() default 30;

    /**
     * tryTime 和 lockTime的时间单位
     */
    TimeUnit unit() default TimeUnit.SECONDS;

    /**
     * 是否公平锁,false:非公平锁,true:公平锁
     */
    boolean fair() default false;
}

 Definir el aspecto de bloqueo DistributedLockAspect

@Aspect
@Slf4j
public class DistributedLockAspect {

    @Resource
    private IDistributedLock distributedLock;

    /**
     * SpEL表达式解析
     */
    private SpelExpressionParser spelExpressionParser = new SpelExpressionParser();

    /**
     * 用于获取方法参数名字
     */
    private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

    @Pointcut("@annotation(com.yt.bi.common.redis.distributedlok.annotation.DistributedLock)")
    public void distributorLock() {
    }

    @Around("distributorLock()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        // 获取DistributedLock
        DistributedLock distributedLock = this.getDistributedLock(pjp);
        // 获取 lockKey
        String lockKey = this.getLockKey(pjp, distributedLock);
        ILock lockObj = null;
        try {
            // 加锁,tryLok = true,并且tryTime > 0时,尝试获取锁,获取不到超时异常
            if (distributedLock.tryLok()) {
                if(distributedLock.tryTime() <= 0){
                    throw new IdempotencyException("tryTime must be greater than 0");
                }
                lockObj = this.distributedLock.tryLock(lockKey, distributedLock.tryTime(), distributedLock.lockTime(), distributedLock.unit(), distributedLock.fair());
            } else {
                lockObj = this.distributedLock.lock(lockKey, distributedLock.lockTime(), distributedLock.unit(), distributedLock.fair());
            }

            if (Objects.isNull(lockObj)) {
                throw new IdempotencyException("Duplicate request for method still in process");
            }

            return pjp.proceed();
        } catch (Exception e) {
            throw e;
        } finally {
            // 解锁
            this.unLock(lockObj);
        }
    }

    /**
     * @param pjp
     * @return
     * @throws NoSuchMethodException
     */
    private DistributedLock getDistributedLock(ProceedingJoinPoint pjp) throws NoSuchMethodException {
        String methodName = pjp.getSignature().getName();
        Class clazz = pjp.getTarget().getClass();
        Class<?>[] par = ((MethodSignature) pjp.getSignature()).getParameterTypes();
        Method lockMethod = clazz.getMethod(methodName, par);
        DistributedLock distributedLock = lockMethod.getAnnotation(DistributedLock.class);
        return distributedLock;
    }

    /**
     * 解锁
     *
     * @param lockObj
     */
    private void unLock(ILock lockObj) {
        if (Objects.isNull(lockObj)) {
            return;
        }

        try {
            this.distributedLock.unLock(lockObj);
        } catch (Exception e) {
            log.error("分布式锁解锁异常", e);
        }
    }

    /**
     * 获取 lockKey
     *
     * @param pjp
     * @param distributedLock
     * @return
     */
    private String getLockKey(ProceedingJoinPoint pjp, DistributedLock distributedLock) {
        String lockKey = distributedLock.key();
        String keyPrefix = distributedLock.keyPrefix();
        if (StringUtils.isBlank(lockKey)) {
            throw new IdempotencyException("Lok key cannot be empty");
        }
        if (lockKey.contains("#")) {
            this.checkSpEL(lockKey);
            MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
            // 获取方法参数值
            Object[] args = pjp.getArgs();
            lockKey = getValBySpEL(lockKey, methodSignature, args);
        }
        lockKey = StringUtils.isBlank(keyPrefix) ? lockKey : keyPrefix + lockKey;
        return lockKey;
    }

    /**
     * 解析spEL表达式
     *
     * @param spEL
     * @param methodSignature
     * @param args
     * @return
     */
    private String getValBySpEL(String spEL, MethodSignature methodSignature, Object[] args) {
        // 获取方法形参名数组
        String[] paramNames = nameDiscoverer.getParameterNames(methodSignature.getMethod());
        if (paramNames == null || paramNames.length < 1) {
            throw new IdempotencyException("Lok key cannot be empty");
        }
        Expression expression = spelExpressionParser.parseExpression(spEL);
        // spring的表达式上下文对象
        EvaluationContext context = new StandardEvaluationContext();
        // 给上下文赋值
        for (int i = 0; i < args.length; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        return expression.getValue(context).toString();
    }

    /**
     * SpEL 表达式校验
     *
     * @param spEL
     * @return
     */
    private void checkSpEL(String spEL) {
        try {
            ExpressionParser parser = new SpelExpressionParser();
            parser.parseExpression(spEL, new TemplateParserContext());
        } catch (Exception e) {
            log.error("spEL表达式解析异常", e);
            throw new IdempotencyException("Invalid SpEL expression [" + spEL + "]");
        }
    }
}

Defina la versión de anotación de bloqueo distribuido para iniciar la metaanotación

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({DistributedLockAspect.class})
public @interface EnableDistributedLock {
}

6. Un ejemplo del uso del aspecto AOP para realizar la vinculación de bloqueos distribuidos.

   La clase de inicio agrega @EnableDistributedLock para habilitar la compatibilidad con anotaciones

@SpringBootApplication
@EnableDistributedLock
public class BiCenterGoodsApplication {

    public static void main(String[] args) {
        
        SpringApplication.run(BiCenterGoodsApplication.class, args);
        
    }
}

@DistributedLock marca los métodos que necesitan usar bloqueos distribuidos 

    @ApiOperation("编辑SKU供应商供货信息")
    
    @PostMapping("/editSupplierInfo")
    //@DistributedLock(key = "#dto.sku + '-' + #dto.skuId", lockTime = 10L, keyPrefix = "sku-")
    @DistributedLock(key = "#dto.sku", lockTime = 10L, keyPrefix = "sku-")
    public R<Boolean> editSupplierInfo(@RequestBody @Validated ProductSkuSupplierInfoDTO dto) {
        return R.ok(productSkuSupplierMeasureService.editSupplierInfo(dto));
    }
#dto.sku es una expresión SpEL. Admite todo lo admitido en Spring. Por ejemplo, llamar a métodos estáticos y expresiones ternarias. SpEL puede utilizar cualquier parámetro del método. Referencia de expresión SpEL

Del principio a la práctica, analice varios esquemas de implementación de bloqueos distribuidos de Redis (1)




 

Supongo que te gusta

Origin blog.csdn.net/Ascend1977/article/details/131126047
Recomendado
Clasificación