开源工具(二)分布式代理锁

6 篇文章 1 订阅

一、引言

        在项目中使用博主自己封装的分布式代理锁已经很久了,慢慢发展已经比较完善通用,现在开源共享。

github地址:

GitHub - SongTing0711/distributed-proxy-lock

gitee地址:distributed-proxy-lock: 分布式代理锁,动态的锁后缀采用ThreadLocal或者参数名获取,锁粒度自定义选择

         相比较博主之前在Redis分布式代理锁的两种实现_tingmailang的博客-CSDN博客封装的工具,开源的工具除了支持redisson之外还支持spring redis加锁,通过参数获取锁后缀的方式也更加灵活,可以看出工具演变的过程。

二、代码解析

1、注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DistributedProxyLock {
    String key();
    long waitOut() default 60;
    long executeOut() default 60;
    TimeUnit timeUnit() default TimeUnit.SECONDS;
    boolean atuoRemove() default true;
    String suffixKeyTypeEnum() default DistributedProxyLockCommonUtil.NO_SUFFIX;
    String objectName() default "";
    String paramName() default "";
    String lockConnectionEnum() default DistributedProxyLockCommonUtil.REDISSON;
}

2、连接工具枚举

@Getter
public enum LockConnectionEnum {
    REDISSON("redisson", "使用redisson"),
    SPRING_REDIS("spring_redis", "使用springredis"),
    ;


    private final String code;
    private final String desc;

    LockConnectionEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    @JsonCreator
    public static LockConnectionEnum of(String key) {
        Optional<LockConnectionEnum> assetStatusEnum = Arrays.stream(LockConnectionEnum.values())
                .filter(c -> c.getCode().equals(key)).findFirst();
        return assetStatusEnum.orElse(null);
    }


}

3、锁后缀获取方式枚举

        三种方式获取锁后缀

@Getter
public enum DistributedProxyLockSuffixKeyTypeEnum {
    NO_SUFFIX("no_suffix", "没有后缀"),
    THREAD_LOCAL("thread_local", "通过ThreadLocal获取后缀"),
    PARAM("param", "通过参数获取后缀"),
    ;


    private final String code;
    private final String desc;

    DistributedProxyLockSuffixKeyTypeEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    @JsonCreator
    public static DistributedProxyLockSuffixKeyTypeEnum of(String key) {
        Optional<DistributedProxyLockSuffixKeyTypeEnum> assetStatusEnum = Arrays.stream(DistributedProxyLockSuffixKeyTypeEnum.values())
                .filter(c -> c.getCode().equals(key)).findFirst();
        return assetStatusEnum.orElse(null);
    }


}

4、ThreadLocal工具类

public class DistributedProxyLockUtil {


    static ThreadLocal<String> LOCK_KEY = new ThreadLocal<String>();

    public static void set(String key) {
        LOCK_KEY.set(key);
    }
    public static String get() {
        return LOCK_KEY.get();
    }
    public static void remove() {
        LOCK_KEY.remove();
    }
}

5、共用类

public class DistributedProxyLockCommonUtil {

    public static final String REDISSON = "redisson";

    public static final String SPRING_REDIS = "spring_redis";

    public static final String NO_SUFFIX = "no_suffix";

    public static final String THREAD_LOCAL = "thread_local";

    public static final String PARAM = "param";

    public static final String GET = "get";

    public static Object getFieldValueByName(String fieldName, Object o) {
        try {
            String firstLetter = fieldName.substring(0, 1).toUpperCase();
            String getter = GET + firstLetter + fieldName.substring(1);
            Method method = o.getClass().getMethod(getter, new Class[]{});
            Object value = method.invoke(o, new Object[]{});
            return value;
        } catch (Exception e) {
            throw new DistributedProxyLockException("获取属性值失败" + e);
        }
    }
}

6、异常类

public class DistributedProxyLockException extends RuntimeException {
  public DistributedProxyLockException() {
    super();
  }

  public DistributedProxyLockException(String message) {
    super("DistributedProxyLockException:" + message);
  }

  public DistributedProxyLockException(String message, Throwable cause) {
    super("DistributedProxyLockException:" + message, cause);
  }

  public DistributedProxyLockException(Throwable cause) {
    super(cause);
  }

}

7、切面

        根据使用者在注解设置的后缀获取方式进行锁拼接,默认无后缀

        根据使用者设置的redis链接工具进行加锁,默认redisson

@Aspect
@Component
public class DistributedProxyLockAspect {

    @Resource
    private DistributedProxyLockService distributedProxyLockService;

    @Pointcut("@annotation(com.annotation.DistributedProxyLock)")
    public void lockPointCut() {
    }

    @Around("lockPointCut() && @annotation(distributedProxyLock)")
    public Object around(ProceedingJoinPoint joinPoint, DistributedProxyLock distributedProxyLock) throws Throwable {
        String lockKey;
        try {
            DistributedProxyLockSuffixKeyTypeEnum suffixKeyTypeEnum = DistributedProxyLockSuffixKeyTypeEnum.of(distributedProxyLock.suffixKeyTypeEnum());
            switch (suffixKeyTypeEnum) {
                case PARAM:
                    lockKey = distributedProxyLockService.getKeyWithParam(joinPoint, distributedProxyLock);
                    break;
                case NO_SUFFIX:
                    lockKey = distributedProxyLock.key();
                    break;
                case THREAD_LOCAL:
                    lockKey = distributedProxyLockService.getKeyWithThreadLocal(joinPoint, distributedProxyLock);
                    break;
                default:
                    throw new DistributedProxyLockException("未知后缀获取类型" + distributedProxyLock.suffixKeyTypeEnum());
            }
        } catch (Exception e) {
            throw new DistributedProxyLockException("获取后缀key失败" + e);
        }

        LockConnectionEnum lockConnectionEnum = LockConnectionEnum.of(distributedProxyLock.lockConnectionEnum());
        switch (lockConnectionEnum) {
            case REDISSON:
                return distributedProxyLockService.lockByRedisson(lockKey, joinPoint, distributedProxyLock);
            case SPRING_REDIS:
                return distributedProxyLockService.lockBySpringRedis(lockKey, joinPoint, distributedProxyLock);
            default:
                throw new DistributedProxyLockException("未知redis工具" + distributedProxyLock.lockConnectionEnum());
        }
    }

}

8、代理锁业务处理

public interface DistributedProxyLockService {

    String getKeyWithThreadLocal(ProceedingJoinPoint joinPoint, DistributedProxyLock distributedProxyLock);


    String getKeyWithParam(ProceedingJoinPoint joinPoint, DistributedProxyLock distributedProxyLock);

    Object lockByRedisson(String lockKey, ProceedingJoinPoint joinPoint, DistributedProxyLock distributedProxyLock) throws Throwable;

    Object lockBySpringRedis(String lockKey, ProceedingJoinPoint joinPoint, DistributedProxyLock distributedProxyLock) throws Throwable;

}

@Service
@Slf4j
public class DistributedProxyLockKeyServiceImpl implements DistributedProxyLockService {

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private RedisTemplate redisTemplate;

    @Override
    public String getKeyWithThreadLocal(ProceedingJoinPoint joinPoint, DistributedProxyLock distributedProxyLock) {
        String lockKey = distributedProxyLock.key();
        if (StringUtil.isBlank(DistributedProxyLockUtil.get())) {
            throw new DistributedProxyLockException("DistributedProxyLockUtil为空");
        }
        return lockKey;
    }


    @Override
    public String getKeyWithParam(ProceedingJoinPoint joinPoint, DistributedProxyLock distributedProxyLock) {
        String objectName = distributedProxyLock.objectName();
        if (StringUtil.isBlank(objectName)) {
            throw new DistributedProxyLockException("objectName为空");
        }
        String paramName = distributedProxyLock.paramName();
        Object[] args = joinPoint.getArgs();
        String[] objectNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
        Map<String, Object> objectHashMap = new HashMap<>();
        for (int i = 0; i < objectNames.length; i++) {
            objectHashMap.put(objectNames[i], args[i]);
        }
        if (!objectHashMap.containsKey(objectName)) {
            throw new DistributedProxyLockException("入参不包含该对象" + objectName);
        }
        Object o = objectHashMap.get(objectName);
        if (StringUtil.isBlank(paramName)) {
            return distributedProxyLock.key() + o.toString();
        }
        String lockKey = distributedProxyLock.key() + DistributedProxyLockCommonUtil.getFieldValueByName(paramName, o);
        return lockKey;
    }


    public Object lockBySpringRedis(String lockKey, ProceedingJoinPoint joinPoint, DistributedProxyLock distributedProxyLock) throws Throwable {
        try {
            if (redisTemplate.opsForValue().setIfAbsent(lockKey, lockKey)) {
                redisTemplate.expire(lockKey, distributedProxyLock.executeOut(), distributedProxyLock.timeUnit());
                log.debug("代理加锁成功:{}", lockKey);
                return joinPoint.proceed();
            } else {
                log.debug("代理加锁失败:{}", lockKey);
            }
        } catch (InterruptedException e) {
            log.error("获取代理锁异常:{}", e);
            throw e;
        } finally {
            redisTemplate.delete(lockKey);
        }
        return null;

    }


    public Object lockByRedisson(String lockKey, ProceedingJoinPoint joinPoint, DistributedProxyLock distributedProxyLock) throws Throwable {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            if (lock.tryLock(distributedProxyLock.waitOut(), distributedProxyLock.executeOut(), distributedProxyLock.timeUnit())) {
                log.debug("代理加锁成功:{}", lockKey);
                return joinPoint.proceed();
            } else {
                log.debug("代理加锁失败:{}", lockKey);
            }
        } catch (InterruptedException e) {
            log.error("获取代理锁异常:{}", e);
            throw e;
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
                log.debug("代理解锁:{}", lockKey);
            }
            //如果方法注解中开启自动清除,就去除
            if (distributedProxyLock.atuoRemove()) {
                DistributedProxyLockUtil.remove();
                log.debug("自动清除DistributedProxyLockUtil:{}", lockKey);
            }
        }
        return null;

    }
}

三、依赖pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.7</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ting</groupId>
    <artifactId>redis-lock</artifactId>
    <version>1.0.0</version>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--        包含自动配置的代码-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>

        <!--        配置文件点击可以跳转实体-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.10</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>2.15.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

四、使用


1.  无后缀(redisson加锁)

    @DistributedProxyLock(key = "SHOP_LOCK_KEY")   
    public void test(ShopChainDTO shopChainDTO) {   
        for (int i = 0; i < 6; i++) {   
            log.info("测试加锁:{}", DistributedProxyLockUtil.get() + i);    
        }   
    }   
    
2.  参数中获取(redisson加锁)

    ①从某个入参对象的某个参数获取
    
        @DistributedProxyLock(key = "SHOP_LOCK_KEY", 
            suffixKeyTypeEnum = DistributedProxyLockCommonUtil.PARAM,
            objectName = "shopChainDTO",
            paramName = "shopId")   
        public void test(ShopChainDTO shopChainDTO) {   
            for (int i = 0; i < 6; i++) {   
                log.info("测试加锁:{}", DistributedProxyLockUtil.get() + i);    
            }   
        }   
        
    ②从某个入参对象获取
    
        @DistributedProxyLock(key = "SHOP_LOCK_KEY", 
            suffixKeyTypeEnum = DistributedProxyLockCommonUtil.PARAM,
            objectName = "shopId")  
        public void test(LocalDateTime onlineTime, String shopId) { 
            for (int i = 0; i < 6; i++) {   
                log.info("测试加锁:{}", DistributedProxyLockUtil.get() + i);    
            }   
        }   
        
3.  使用ThreadLocal获取(redisson加锁)

    @Slf4j  
    @Service    
    public class ShopServiceImpl implements ShopService {   
    
        @DistributedProxyLock(key = "SHOP_LOCK_KEY", 
            suffixKeyTypeEnum = DistributedProxyLockCommonUtil.THREAD_LOCAL) 
        public void test(ShopChainDTO shopChainDTO) {   
            for (int i = 0; i < 6; i++) {   
                log.info("测试加锁:{}", DistributedProxyLockUtil.get() + i);    
            }   
        }   
        
    }
    
    @RestController 
    @RequestMapping("shop") 
    public class ShopController {   
        
        @Resource   
        private ShopService shopService;    

        @PostMapping("/online") 
        @MethodLogger   
        public void run(@RequestBody @Validated ShopChainDTO dto) { 
            DistributedProxyLockUtil.set(dto.getShopId());  
            shopService.test(dto);  
        }
    }
    
4.  无后缀(Spring redis加锁)

    @DistributedProxyLock(key = "SHOP_LOCK_KEY",
        lockConnectionEnum = DistributedProxyLockCommonUtil.SPRING_REDIS)   
    public void test(ShopChainDTO shopChainDTO) {   
        for (int i = 0; i < 6; i++) {   
            log.info("测试加锁:{}", DistributedProxyLockUtil.get() + i);    
        }   
    }   
    
5.  参数中获取(Spring redis加锁)

    ①从某个入参对象的某个参数获取
    
        @DistributedProxyLock(key = "SHOP_LOCK_KEY", 
            suffixKeyTypeEnum = DistributedProxyLockCommonUtil.PARAM,
            objectName = "shopChainDTO",
            paramName = "shopId",
            lockConnectionEnum = DistributedProxyLockCommonUtil.SPRING_REDIS)   
        public void test(ShopChainDTO shopChainDTO) {   
            for (int i = 0; i < 6; i++) {   
                log.info("测试加锁:{}", DistributedProxyLockUtil.get() + i);    
            }   
        }   
        
    ②从某个入参对象获取
    
        @DistributedProxyLock(key = "SHOP_LOCK_KEY", 
            suffixKeyTypeEnum = DistributedProxyLockCommonUtil.PARAM,
            objectName = "shopId",
            lockConnectionEnum = DistributedProxyLockCommonUtil.SPRING_REDIS)  
        public void test(LocalDateTime onlineTime, String shopId) { 
            for (int i = 0; i < 6; i++) {   
                log.info("测试加锁:{}", DistributedProxyLockUtil.get() + i);    
            }   
        }   
        
6.  使用ThreadLocal获取(Spring redis加锁)

    @Slf4j  
    @Service    
    public class ShopServiceImpl implements ShopService {   
    
        @DistributedProxyLock(key = "SHOP_LOCK_KEY", 
            suffixKeyTypeEnum = DistributedProxyLockCommonUtil.THREAD_LOCAL,
             lockConnectionEnum = DistributedProxyLockCommonUtil.SPRING_REDIS) 
        public void test(ShopChainDTO shopChainDTO) {   
            for (int i = 0; i < 6; i++) {   
                log.info("测试加锁:{}", DistributedProxyLockUtil.get() + i);    
            }   
        }   
        
    }
    
    @RestController 
    @RequestMapping("shop") 
    public class ShopController {   
        
        @Resource   
        private ShopService shopService;    

        @PostMapping("/online") 
        @MethodLogger   
        public void run(@RequestBody @Validated ShopChainDTO dto) { 
            DistributedProxyLockUtil.set(dto.getShopId());  
            shopService.test(dto);  
        }
    }

五、总结

        使用工具之后基本上加锁只需要一个注解就可以。

        考虑到通用性redislock支持了多种redis链接工具,如果有其他常用的链接工具,同学们不妨提个issue,博主再去封装。

        同时分布式锁除了通过redis实现,还有zk等其他工具可以实现,但是博主这里暂时没有使用,有兴趣的同学可以加入这个工具的完善。

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胖当当技术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值