代码改变世界

Spring Boot 集成 Redisson分布式锁(注解版)

  l_v_y_forever  阅读(601)  评论(0编辑  收藏  举报

转载自:https://blog.csdn.net/Ascend1977/article/details/131126047

        Redisson 是一种基于 Redis 的 Java 驻留集群的分布式对象和服务库,可以为我们提供丰富的分布式锁和线程安全集合的实现。在 Spring Boot 应用程序中使用 Redisson 可以方便地实现分布式应用程序的某些方面,例如分布式锁、分布式集合、分布式事件发布和订阅等。本篇是一个使用 Redisson 实现分布式锁的详细示例,在这个示例中,我们定义了DistributedLock注解,它可以标注在方法上,配合DistributedLockAspect切面以及IDistributedLock分布式锁封装的接口,来实现redisson 分布式锁的 API 调用。

Spring Boot 集成 Redisson

 1、在 pom.xml 文件中添加 Redisson 的相关依赖

  1.  
    <dependency>
  2.  
    <groupId>org.redisson</groupId>
  3.  
    <artifactId>redisson-spring-boot-starter</artifactId>
  4.  
    <version>3.16.1</version>
  5.  
    </dependency>

2、在 application.yml 中配置 Redisson单机模式 的连接信息和相关参数

  1.  
    spring:
  2.  
    redis:
  3.  
    host: localhost
  4.  
    port: 6379
  5.  
    password: null
  6.  
    redisson:
  7.  
    codec: org.redisson.codec.JsonJacksonCodec
  8.  
    threads: 4
  9.  
    netty:
  10.  
    threads: 4
  11.  
    single-server-config:
  12.  
    address: "redis://localhost:6379"
  13.  
    password: null
  14.  
    database: 0

3、Redission还支持主从、集群、哨兵配置

  1.  
    //主从模式
  2.  
    spring:
  3.  
    redis:
  4.  
    sentinel:
  5.  
    master: my-master
  6.  
    nodes: localhost:26379,localhost:26389
  7.  
    password: your_password
  8.  
    redisson:
  9.  
    master-slave-config:
  10.  
    master-address: "redis://localhost:6379"
  11.  
    slave-addresses: "redis://localhost:6380,redis://localhost:6381"
  12.  
    password: ${spring.redis.password}
  13.  
     
  14.  
    // 集群模式
  15.  
    spring:
  16.  
    redis:
  17.  
    cluster:
  18.  
    nodes: localhost:6379,localhost:6380,localhost:6381,localhost:6382,localhost:6383,localhost:6384
  19.  
    password: your_password
  20.  
    redisson:
  21.  
    cluster-config:
  22.  
    node-addresses: "redis://localhost:6379,redis://localhost:6380,redis://localhost:6381,redis://localhost:6382,redis://localhost:6383,redis://localhost:6384"
  23.  
    password: ${spring.redis.password}
  24.  
     
  25.  
    // 哨兵模式
  26.  
    spring:
  27.  
    redis:
  28.  
    sentinel:
  29.  
    master: my-master
  30.  
    nodes: localhost:26379,localhost:26389
  31.  
    password: your_password
  32.  
    redisson:
  33.  
    sentinel-config:
  34.  
    master-name: my-master
  35.  
    sentinel-addresses: "redis://localhost:26379,redis://localhost:26380,redis://localhost:26381"
  36.  
    password: ${spring.redis.password}
  37.  
     

 本地封装Redisson 分布式锁

1、定义IDistributedLock分布式锁接口

  1.  
    public interface IDistributedLock {
  2.  
    /**
  3.  
    * 获取锁,默认30秒失效,失败一直等待直到获取锁
  4.  
    *
  5.  
    * @param key 锁的key
  6.  
    * @return 锁对象
  7.  
    */
  8.  
    ILock lock(String key);
  9.  
     
  10.  
    /**
  11.  
    * 获取锁,失败一直等待直到获取锁
  12.  
    *
  13.  
    * @param key 锁的key
  14.  
    * @param lockTime 加锁的时间,超过这个时间后锁便自动解锁; 如果lockTime为-1,则保持锁定直到显式解锁
  15.  
    * @param unit {@code lockTime} 参数的时间单位
  16.  
    * @param fair 是否公平锁
  17.  
    * @return 锁对象
  18.  
    */
  19.  
    ILock lock(String key, long lockTime, TimeUnit unit, boolean fair);
  20.  
     
  21.  
    /**
  22.  
    * 尝试获取锁,30秒获取不到超时异常,锁默认30秒失效
  23.  
    *
  24.  
    * @param key 锁的key
  25.  
    * @param tryTime 获取锁的最大尝试时间
  26.  
    * @return
  27.  
    * @throws Exception
  28.  
    */
  29.  
    ILock tryLock(String key, long tryTime) throws Exception;
  30.  
     
  31.  
    /**
  32.  
    * 尝试获取锁,获取不到超时异常
  33.  
    *
  34.  
    * @param key 锁的key
  35.  
    * @param tryTime 获取锁的最大尝试时间
  36.  
    * @param lockTime 加锁的时间
  37.  
    * @param unit {@code tryTime @code lockTime} 参数的时间单位
  38.  
    * @param fair 是否公平锁
  39.  
    * @return
  40.  
    * @throws Exception
  41.  
    */
  42.  
    ILock tryLock(String key, long tryTime, long lockTime, TimeUnit unit, boolean fair) throws Exception;
  43.  
     
  44.  
    /**
  45.  
    * 解锁
  46.  
    *
  47.  
    * @param lock
  48.  
    * @throws Exception
  49.  
    */
  50.  
    void unLock(Object lock);
  51.  
     
  52.  
     
  53.  
    /**
  54.  
    * 释放锁
  55.  
    *
  56.  
    * @param lock
  57.  
    * @throws Exception
  58.  
    */
  59.  
    default void unLock(ILock lock) {
  60.  
    if (lock != null) {
  61.  
    unLock(lock.getLock());
  62.  
    }
  63.  
    }
  64.  
     
  65.  
     
  66.  
    }

2、IDistributedLock实现类

  1.  
    @Slf4j
  2.  
    @Component
  3.  
    public class RedissonDistributedLock implements IDistributedLock {
  4.  
     
  5.  
    @Resource
  6.  
    private RedissonClient redissonClient;
  7.  
    /**
  8.  
    * 统一前缀
  9.  
    */
  10.  
    @Value("${redisson.lock.prefix:bi:distributed:lock}")
  11.  
    private String prefix;
  12.  
     
  13.  
    @Override
  14.  
    public ILock lock(String key) {
  15.  
    return this.lock(key, 0L, TimeUnit.SECONDS, false);
  16.  
    }
  17.  
     
  18.  
    @Override
  19.  
    public ILock lock(String key, long lockTime, TimeUnit unit, boolean fair) {
  20.  
    RLock lock = getLock(key, fair);
  21.  
    // 获取锁,失败一直等待,直到获取锁,不支持自动续期
  22.  
    if (lockTime > 0L) {
  23.  
    lock.lock(lockTime, unit);
  24.  
    } else {
  25.  
    // 具有Watch Dog 自动延期机制 默认续30s 每隔30/3=10 秒续到30s
  26.  
    lock.lock();
  27.  
    }
  28.  
    return new ILock(lock, this);
  29.  
    }
  30.  
     
  31.  
    @Override
  32.  
    public ILock tryLock(String key, long tryTime) throws Exception {
  33.  
    return this.tryLock(key, tryTime, 0L, TimeUnit.SECONDS, false);
  34.  
    }
  35.  
     
  36.  
    @Override
  37.  
    public ILock tryLock(String key, long tryTime, long lockTime, TimeUnit unit, boolean fair)
  38.  
    throws Exception {
  39.  
    RLock lock = getLock(key, fair);
  40.  
    boolean lockAcquired;
  41.  
    // 尝试获取锁,获取不到超时异常,不支持自动续期
  42.  
    if (lockTime > 0L) {
  43.  
    lockAcquired = lock.tryLock(tryTime, lockTime, unit);
  44.  
    } else {
  45.  
    // 具有Watch Dog 自动延期机制 默认续30s 每隔30/3=10 秒续到30s
  46.  
    lockAcquired = lock.tryLock(tryTime, unit);
  47.  
    }
  48.  
    if (lockAcquired) {
  49.  
    return new ILock(lock, this);
  50.  
    }
  51.  
    return null;
  52.  
    }
  53.  
     
  54.  
    /**
  55.  
    * 获取锁
  56.  
    *
  57.  
    * @param key
  58.  
    * @param fair
  59.  
    * @return
  60.  
    */
  61.  
    private RLock getLock(String key, boolean fair) {
  62.  
    RLock lock;
  63.  
    String lockKey = prefix + ":" + key;
  64.  
    if (fair) {
  65.  
    // 获取公平锁
  66.  
    lock = redissonClient.getFairLock(lockKey);
  67.  
    } else {
  68.  
    // 获取普通锁
  69.  
    lock = redissonClient.getLock(lockKey);
  70.  
    }
  71.  
    return lock;
  72.  
    }
  73.  
     
  74.  
    @Override
  75.  
    public void unLock(Object lock) {
  76.  
    if (!(lock instanceof RLock)) {
  77.  
    throw new IllegalArgumentException("Invalid lock object");
  78.  
    }
  79.  
    RLock rLock = (RLock) lock;
  80.  
    if (rLock.isLocked()) {
  81.  
    try {
  82.  
    rLock.unlock();
  83.  
    } catch (IllegalMonitorStateException e) {
  84.  
    log.error("释放分布式锁异常", e);
  85.  
    }
  86.  
    }
  87.  
    }
  88.  
    }

 3、定义ILock锁对象

  1.  
    import lombok.AllArgsConstructor;
  2.  
    import lombok.Getter;
  3.  
     
  4.  
    import java.util.Objects;
  5.  
     
  6.  
    /**
  7.  
    * <p>
  8.  
    * RedissonLock 包装的锁对象 实现AutoCloseable接口,在java7的try(with resource)语法,不用显示调用close方法
  9.  
    * </p>
  10.  
     
  11.  
    * @since 2023-06-08 16:57
  12.  
    */
  13.  
    @AllArgsConstructor
  14.  
    public class ILock implements AutoCloseable {
  15.  
    /**
  16.  
    * 持有的锁对象
  17.  
    */
  18.  
    @Getter
  19.  
    private Object lock;
  20.  
    /**
  21.  
    * 分布式锁接口
  22.  
    */
  23.  
    @Getter
  24.  
    private IDistributedLock distributedLock;
  25.  
     
  26.  
    @Override
  27.  
    public void close() throws Exception {
  28.  
    if(Objects.nonNull(lock)){
  29.  
    distributedLock.unLock(lock);
  30.  
    }
  31.  
    }
  32.  
    }

4、注入IDistributedLock接口使用示例

  1.  
    // 定义接口
  2.  
    public interface IProductSkuSupplierMeasureService {
  3.  
    /**
  4.  
    * 保存SKU供应商供货信息
  5.  
    *
  6.  
    * @param dto
  7.  
    * @return
  8.  
    *
  9.  
    Boolean saveSupplierInfo(ProductSkuSupplierInfoDTO dto);
  10.  
     
  11.  
    /**
  12.  
    * 编辑SKU供应商供货信息
  13.  
    *
  14.  
    * @param dto
  15.  
    * @return
  16.  
    */
  17.  
    Boolean editSupplierInfo(ProductSkuSupplierInfoDTO dto);
  18.  
    }
  19.  
     
  20.  
     

手动释放锁示例 

  1.  
    // 实现类
  2.  
    @Service
  3.  
    public class ProductSkuSupplierMeasureServiceImpl
  4.  
    implements IProductSkuSupplierMeasureService {
  5.  
     
  6.  
    @Resource
  7.  
    private IDistributedLock distributedLock;
  8.  
     
  9.  
    @Override
  10.  
    @Transactional(rollbackFor = Exception.class)
  11.  
    public Boolean saveSupplierInfo(ProductSkuSupplierInfoDTO dto) {
  12.  
    // 手动释放锁
  13.  
    String sku = dto.getSku();
  14.  
    ILock lock = null;
  15.  
    try {
  16.  
    lock = distributedLock.lock(dto.getSku(),10L, TimeUnit.SECONDS, false);
  17.  
    if (Objects.isNull(lock)) {
  18.  
    throw new BusinessException("Duplicate request for method still in process");
  19.  
    }
  20.  
    // 业务代码
  21.  
    }catch (BusinessException e) {
  22.  
    throw new BusinessException(e.getMessage());
  23.  
    } catch (Exception e) {
  24.  
    log.error("保存异常", e);
  25.  
    throw new BusinessException (e.getMessage());
  26.  
    } finally {
  27.  
    if (Objects.nonNull(lock)) {
  28.  
    distributedLock.unLock(lock);
  29.  
    }
  30.  
    }
  31.  
    return Boolean.TRUE;
  32.  
    }
  33.  
     
  34.  
    }

 使用try-with-resources 语法糖自动释放锁

  1.  
    // 实现类
  2.  
    @Service
  3.  
    public class ProductSkuSupplierMeasureServiceImpl
  4.  
    implements IProductSkuSupplierMeasureService {
  5.  
     
  6.  
    @Resource
  7.  
    private IDistributedLock distributedLock;
  8.  
     
  9.  
     
  10.  
    @Override
  11.  
    @Transactional(rollbackFor = Exception.class)
  12.  
    public Boolean editSupplierInfo(ProductSkuSupplierInfoDTO dto) {
  13.  
     
  14.  
    String sku = dto.getSku();
  15.  
    // try-with-resources 语法糖自动释放锁
  16.  
    try(ILock lock = distributedLock.lock(dto.getSku(),10L, TimeUnit.SECONDS, false)) {
  17.  
    if(Objects.isNull(lock)){
  18.  
    throw new BusinessException ("Duplicate request for method still in process");
  19.  
    }
  20.  
     
  21.  
    // 业务代码
  22.  
    }catch (BusinessException e) {
  23.  
    throw new BusinessException (e.getMessage());
  24.  
    } catch (Exception e) {
  25.  
    log.error("修改异常", e);
  26.  
    throw new BusinessException ("修改异常");
  27.  
    }
  28.  
    return Boolean.TRUE;
  29.  
    }
  30.  
    }

5、使用AOP切面实现分布式锁的绑定

  定义DistributedLock注解

  1.  
    @Target({ElementType.METHOD})
  2.  
    @Retention(RetentionPolicy.RUNTIME)
  3.  
    @Documented
  4.  
    public @interface DistributedLock {
  5.  
     
  6.  
    /**
  7.  
    * 保证业务接口的key的唯一性,否则失去了分布式锁的意义 锁key
  8.  
    * 支持使用spEl表达式
  9.  
    */
  10.  
    String key();
  11.  
     
  12.  
    /**
  13.  
    * 保证业务接口的key的唯一性,否则失去了分布式锁的意义 锁key 前缀
  14.  
    */
  15.  
    String keyPrefix() default "";
  16.  
     
  17.  
    /**
  18.  
    * 是否在等待时间内获取锁,如果在等待时间内无法获取到锁,则返回失败
  19.  
    */
  20.  
    boolean tryLok() default false;
  21.  
     
  22.  
    /**
  23.  
    * 获取锁的最大尝试时间 ,会尝试tryTime时间获取锁,在该时间内获取成功则返回,否则抛出获取锁超时异常,tryLok=true时,该值必须大于0。
  24.  
    *
  25.  
    */
  26.  
    long tryTime() default 0;
  27.  
     
  28.  
    /**
  29.  
    * 加锁的时间,超过这个时间后锁便自动解锁
  30.  
    */
  31.  
    long lockTime() default 30;
  32.  
     
  33.  
    /**
  34.  
    * tryTime 和 lockTime的时间单位
  35.  
    */
  36.  
    TimeUnit unit() default TimeUnit.SECONDS;
  37.  
     
  38.  
    /**
  39.  
    * 是否公平锁,false:非公平锁,true:公平锁
  40.  
    */
  41.  
    boolean fair() default false;
  42.  
    }

 定义DistributedLockAspect Lock切面

  1.  
    @Aspect
  2.  
    @Slf4j
  3.  
    public class DistributedLockAspect {
  4.  
     
  5.  
    @Resource
  6.  
    private IDistributedLock distributedLock;
  7.  
     
  8.  
    /**
  9.  
    * SpEL表达式解析
  10.  
    */
  11.  
    private SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
  12.  
     
  13.  
    /**
  14.  
    * 用于获取方法参数名字
  15.  
    */
  16.  
    private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
  17.  
     
  18.  
    @Pointcut("@annotation(com.yt.bi.common.redis.distributedlok.annotation.DistributedLock)")
  19.  
    public void distributorLock() {
  20.  
    }
  21.  
     
  22.  
    @Around("distributorLock()")
  23.  
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
  24.  
    // 获取DistributedLock
  25.  
    DistributedLock distributedLock = this.getDistributedLock(pjp);
  26.  
    // 获取 lockKey
  27.  
    String lockKey = this.getLockKey(pjp, distributedLock);
  28.  
    ILock lockObj = null;
  29.  
    try {
  30.  
    // 加锁,tryLok = true,并且tryTime > 0时,尝试获取锁,获取不到超时异常
  31.  
    if (distributedLock.tryLok()) {
  32.  
    if(distributedLock.tryTime() <= 0){
  33.  
    throw new IdempotencyException("tryTime must be greater than 0");
  34.  
    }
  35.  
    lockObj = this.distributedLock.tryLock(lockKey, distributedLock.tryTime(), distributedLock.lockTime(), distributedLock.unit(), distributedLock.fair());
  36.  
    } else {
  37.  
    lockObj = this.distributedLock.lock(lockKey, distributedLock.lockTime(), distributedLock.unit(), distributedLock.fair());
  38.  
    }
  39.  
     
  40.  
    if (Objects.isNull(lockObj)) {
  41.  
    throw new IdempotencyException("Duplicate request for method still in process");
  42.  
    }
  43.  
     
  44.  
    return pjp.proceed();
  45.  
    } catch (Exception e) {
  46.  
    throw e;
  47.  
    } finally {
  48.  
    // 解锁
  49.  
    this.unLock(lockObj);
  50.  
    }
  51.  
    }
  52.  
     
  53.  
    /**
  54.  
    * @param pjp
  55.  
    * @return
  56.  
    * @throws NoSuchMethodException
  57.  
    */
  58.  
    private DistributedLock getDistributedLock(ProceedingJoinPoint pjp) throws NoSuchMethodException {
  59.  
    String methodName = pjp.getSignature().getName();
  60.  
    Class clazz = pjp.getTarget().getClass();
  61.  
    Class<?>[] par = ((MethodSignature) pjp.getSignature()).getParameterTypes();
  62.  
    Method lockMethod = clazz.getMethod(methodName, par);
  63.  
    DistributedLock distributedLock = lockMethod.getAnnotation(DistributedLock.class);
  64.  
    return distributedLock;
  65.  
    }
  66.  
     
  67.  
    /**
  68.  
    * 解锁
  69.  
    *
  70.  
    * @param lockObj
  71.  
    */
  72.  
    private void unLock(ILock lockObj) {
  73.  
    if (Objects.isNull(lockObj)) {
  74.  
    return;
  75.  
    }
  76.  
     
  77.  
    try {
  78.  
    this.distributedLock.unLock(lockObj);
  79.  
    } catch (Exception e) {
  80.  
    log.error("分布式锁解锁异常", e);
  81.  
    }
  82.  
    }
  83.  
     
  84.  
    /**
  85.  
    * 获取 lockKey
  86.  
    *
  87.  
    * @param pjp
  88.  
    * @param distributedLock
  89.  
    * @return
  90.  
    */
  91.  
    private String getLockKey(ProceedingJoinPoint pjp, DistributedLock distributedLock) {
  92.  
    String lockKey = distributedLock.key();
  93.  
    String keyPrefix = distributedLock.keyPrefix();
  94.  
    if (StringUtils.isBlank(lockKey)) {
  95.  
    throw new IdempotencyException("Lok key cannot be empty");
  96.  
    }
  97.  
    if (lockKey.contains("#")) {
  98.  
    this.checkSpEL(lockKey);
  99.  
    MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
  100.  
    // 获取方法参数值
  101.  
    Object[] args = pjp.getArgs();
  102.  
    lockKey = getValBySpEL(lockKey, methodSignature, args);
  103.  
    }
  104.  
    lockKey = StringUtils.isBlank(keyPrefix) ? lockKey : keyPrefix + lockKey;
  105.  
    return lockKey;
  106.  
    }
  107.  
     
  108.  
    /**
  109.  
    * 解析spEL表达式
  110.  
    *
  111.  
    * @param spEL
  112.  
    * @param methodSignature
  113.  
    * @param args
  114.  
    * @return
  115.  
    */
  116.  
    private String getValBySpEL(String spEL, MethodSignature methodSignature, Object[] args) {
  117.  
    // 获取方法形参名数组
  118.  
    String[] paramNames = nameDiscoverer.getParameterNames(methodSignature.getMethod());
  119.  
    if (paramNames == null || paramNames.length < 1) {
  120.  
    throw new IdempotencyException("Lok key cannot be empty");
  121.  
    }
  122.  
    Expression expression = spelExpressionParser.parseExpression(spEL);
  123.  
    // spring的表达式上下文对象
  124.  
    EvaluationContext context = new StandardEvaluationContext();
  125.  
    // 给上下文赋值
  126.  
    for (int i = 0; i < args.length; i++) {
  127.  
    context.setVariable(paramNames[i], args[i]);
  128.  
    }
  129.  
    Object value = expression.getValue(context);
  130.  
    if (value == null) {
  131.  
    throw new IdempotencyException("The parameter value cannot be null");
  132.  
    }
  133.  
    return value.toString();
  134.  
    }
  135.  
     
  136.  
    /**
  137.  
    * SpEL 表达式校验
  138.  
    *
  139.  
    * @param spEL
  140.  
    * @return
  141.  
    */
  142.  
    private void checkSpEL(String spEL) {
  143.  
    try {
  144.  
    ExpressionParser parser = new SpelExpressionParser();
  145.  
    parser.parseExpression(spEL, new TemplateParserContext());
  146.  
    } catch (Exception e) {
  147.  
    log.error("spEL表达式解析异常", e);
  148.  
    throw new IdempotencyException("Invalid SpEL expression [" + spEL + "]");
  149.  
    }
  150.  
    }
  151.  
    }

定义分布式锁注解版启动元注解

  1.  
    @Target(ElementType.TYPE)
  2.  
    @Retention(RetentionPolicy.RUNTIME)
  3.  
    @Documented
  4.  
    @Import({DistributedLockAspect.class})
  5.  
    public @interface EnableDistributedLock {
  6.  
    }

6、AOP切面实现分布式锁的绑定使用示例

   启动类添加@EnableDistributedLock启用注解支持

  1.  
    @SpringBootApplication
  2.  
    @EnableDistributedLock
  3.  
    public class BiCenterGoodsApplication {
  4.  
     
  5.  
    public static void main(String[] args) {
  6.  
     
  7.  
    SpringApplication.run(BiCenterGoodsApplication.class, args);
  8.  
     
  9.  
    }
  10.  
    }

@DistributedLock标注需要使用分布式锁的方法 

  1.  
    @ApiOperation("编辑SKU供应商供货信息")
  2.  
     
  3.  
    @PostMapping("/editSupplierInfo")
  4.  
    //@DistributedLock(key = "#dto.sku + '-' + #dto.skuId", lockTime = 10L, keyPrefix = "sku-")
  5.  
    @DistributedLock(key = "#dto.sku", lockTime = 10L, keyPrefix = "sku-")
  6.  
    public R<Boolean> editSupplierInfo(@RequestBody @Validated ProductSkuSupplierInfoDTO dto) {
  7.  
    return R.ok(productSkuSupplierMeasureService.editSupplierInfo(dto));
  8.  
    }
#dto.sku是 SpEL表达式。Spring中支持的它都支持的。比如调用静态方法,三目表达式。SpEL 可以使用方法中的任何参数。SpEL表达式参考

从原理到实践,分析 Redis 分布式锁的多种实现方案(一)_Ascend JF的博客-CSDN博客


相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示