栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

开源工具distributed-proxy-lock(分布式代理锁)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

开源工具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 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 assetStatusEnum = Arrays.stream(DistributedProxyLockSuffixKeyTypeEnum.values())
                .filter(c -> c.getCode().equals(key)).findFirst();
        return assetStatusEnum.orElse(null);
    }


}
4、ThreadLocal工具类
public class DistributedProxyLockUtil {


    static ThreadLocal LOCK_KEY = new ThreadLocal();

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


    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.6.7
         
    
    com.ting
    redis-lock
    1.0.0
    
        1.8
    
    
        
            org.springframework.boot
            spring-boot-starter-web
        
        
        
            org.springframework.boot
            spring-boot-autoconfigure
        

        
        
            org.springframework.boot
            spring-boot-configuration-processor
            true
        
        
            org.aspectj
            aspectjweaver
            1.8.10
        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        

        
            org.redisson
            redisson
            2.15.1
        
        
            org.springframework.boot
            spring-boot-configuration-processor
            true
        
        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    


四、使用
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等其他工具可以实现,但是博主这里暂时没有使用,有兴趣的同学可以加入这个工具的完善。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/926395.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号