分布式锁 starter

lock-spring-boot-starter

分布式锁 starter

介绍

说明

工程接口(扩展点):

         接口->com.javacoo.lock.client.api.Lock

基于xkernel 提供的SPI机制,结合SpringBoot注解 ConditionalOnBean,ConditionalOnProperty实现。

类关系图

Lock.png

项目结构

lock-spring-boot-starter
 └── src
    ├── main  
    │   ├── java  
    │   │   └── com.javacoo 
    │   │   ├────── lock
    │   │   │         ├──────client
    │   │   │         │         ├── api
    │   │   │         │         │     ├── annotation
    │   │   │         │         │     │      └── MethodLock 锁注解
    │   │   │         │         │     ├── client
    │   │   │         │         │     │      └── Lock 锁接口
    │   │   │         │         │     ├── aspect
    │   │   │         │         │     │      └── LockAspect 锁切面
    │   │   │         │         │     ├── config
    │   │   │         │         │     │      └── LockConfig 锁配置
    │   │   │         │         │     ├── exception
    │   │   │         │         │            └── LockException 锁异常
    │   │   │         │         └── internal 接口内部实现
    │   │   │         │               ├── redis
    │   │   │         │                      ├── RedissionConfig Redission配置类
    │   │   │         │                      └── RedssionLock 锁接口实现类
    │   │   │         └──────starter
    │   │   │                   ├── LockAutoConfiguration 自动配置类
    │   │   │                   └── LockHolder 锁接口对象持有者
    │   └── resource  
    │       ├── META-INF
    │             └── ext
    │                  └── internal
    │                          └── com.javacoo.lock.client.api.Lock
    └── test  测试

如何使用

  1. pom依赖:

    
       com.javacoo
       lock-spring-boot-starter
       1.0.0
    
    
  2. 配置参数,如果使用默认实现,则无需配置,如要扩展则需要,配置如下:

    #lock是否可用,默认可用
    lock.enabled = true
    #lock实现,默认内部实现
    lock.impl = default
    
  3. 方法加注解,如:

注解说明

字段 类型 说明
fieldName String[] 指定需要加入锁的字段,默认为空
timeInSecond int 锁的有效时间,单位为秒,默认值为60
waitTime int 等待时间,单位为秒,默认值为0
paramIndex int 指定参数在参数列表中的索引,默认值为0
coolingTime int 方法冷却时间,多久能调用一次该方法,单位为秒,默认值为0,不限制

示例

//参数级锁
@MethodLock(fieldName = "applNo")
@Override
public BaseResponse gjjloanConfirm(LoanConfirmRequest request) {
 ...
}     
//参数级锁,带冷却时间
@MethodLock(fieldName = "username",coolingTime = 10)
public ResponseEntity login(@RequestBody UserLoginRequest loginRequest, HttpServletRequest request)  {
 ...
}       
//方法级锁
@MethodLock
public void divisionCase() {
 ...
}   

SPI扩展

基于xkernel 提供的SPI机制,扩展非常方便,大致步骤如下:

  1. 实现锁接口:如 com.xxxx.xxxx.MyLockImpl

  2. 配置锁接口:

    • 在项目resource目录新建包->META-INF->services

    • 创建com.javacoo.lock.client.api.Lock文件,文件内容:实现类的全局限定名,如:

      myLock=com.xxxx.xxxx.MyLockImpl
      
    • 修改配置文件,添加如下内容:

    #lock实现
    lock.impl = myLock
    

默认实现

1、锁接口:

/**
 * 锁接口
 * 
  • * * @author: duanyong * @since: 2020/6/22 10:19 */ @Spi(LockConfig.DEFAULT_IMPL) public interface Lock { /**超时时间*/ int TIMEOUT_SECOND = 60; /** * 对lockKey加锁 *
  • * @author duanyong * @date 2020/6/22 10:30 * @param lockKey:lockKey * @return: T 锁对象 */ T lock(String lockKey); /** * 对lockKey加锁,timeout后过期 *
  • * @author duanyong * @date 2020/6/22 10:31 * @param lockKey: lockKey * @param timeout: 锁超时时间,单位:秒 * @return: T 锁对象 */ T lock(String lockKey, int timeout); /** * 对lockKey加锁,指定时间单位,timeout后过期 *
  • * @author duanyong * @date 2020/6/22 10:33 * @param lockKey: lockKey * @param unit: 时间单位 * @param timeout: 锁超时时间 * @return: T 锁对象 */ T lock(String lockKey, TimeUnit unit , int timeout); /** * 尝试获取锁 *
  • * @author duanyong * @date 2020/6/22 10:35 * @param lockKey: lockKey * @return: boolean 是否成功,成功返回:true */ boolean tryLock(String lockKey); /** * 尝试获取锁 *
  • * @author duanyong * @date 2020/6/22 10:35 * @param lockKey: lockKey * @param waitTime:最多等待时间 * @param timeout:上锁后自动释放锁时间 * @return: boolean 是否成功,成功返回:true */ boolean tryLock(String lockKey, int waitTime, int timeout); /** * 尝试获取锁 *
  • * @author duanyong * @date 2020/6/22 10:36 * @param lockKey: lockKey * @param unit:时间单位 * @param waitTime:最多等待时间 * @param timeout:上锁后自动释放锁时间 * @return: boolean 是否成功,成功返回:true */ boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int timeout); /** * 释放锁 *
  • * @author duanyong * @date 2020/6/22 10:37 * @param lockKey: lockKey * @return: void */ void unlock(String lockKey); /** * 释放锁 *
  • * @author duanyong * @date 2020/6/22 10:37 * @param lock:锁对象 * @return: void */ void unlock(T lock); }

    2、实现锁接口,如默认实现:

    /**
     * 锁接口实现类
     * 
  • 基于Redssion
  • * * @author: duanyong * @since: 2020/6/22 10:39 */ @Slf4j public class RedssionLock implements Lock { /**约定缓存前缀:appId:模块:key*/ public static final String CACHE_PREFIX_KEY ="javacoo:service:lock:"; @Autowired private RedissonClient redissonClient; /** * 对lockKey加锁 *
  • 阻塞获取锁
  • * * @param lockKey :lockKey * @author duanyong * @date 2020/6/22 10:30 * @return: T 锁对象 */ @Override public RLock lock(String lockKey) { log.info(">>>>> lock key[{}]",lockKey); Assert.hasText(lockKey,"lockKey 不能为空"); RLock lock = redissonClient.getLock(CACHE_PREFIX_KEY +lockKey); lock.lock(); return lock; } /** * 对lockKey加锁,timeout后过期 *
  • * * @param lockKey : lockKey * @param timeout : 锁超时时间,单位:秒 * @author duanyong * @date 2020/6/22 10:31 * @return: T 锁对象 */ @Override public RLock lock(String lockKey, int timeout) { return lock(lockKey, TimeUnit.SECONDS,timeout); } /** * 对lockKey加锁,指定时间单位,timeout后过期 *
  • * * @param lockKey : lockKey * @param unit : 时间单位 * @param timeout : 锁超时时间 * @author duanyong * @date 2020/6/22 10:33 * @return: T 锁对象 */ @Override public RLock lock(String lockKey, TimeUnit unit, int timeout) { log.info(">>>>> lockKey:{},TimeUnit:{},timeout:{}",lockKey,unit,timeout); Assert.hasText(lockKey,"lockKey 不能为空"); RLock lock = redissonClient.getLock(CACHE_PREFIX_KEY +lockKey); lock.lock(timeout, unit); return lock; } /** * 尝试获取锁 *
  • 获取锁失败立即返回,上锁后60秒自动释放锁
  • * * @param lockKey : lockKey * @author duanyong * @date 2020/6/22 10:35 * @return: boolean 是否成功,成功返回:true */ @Override public boolean tryLock(String lockKey) { return tryLock(lockKey,0,60); } /** * 尝试获取锁 *
  • * * @param lockKey : lockKey * @param waitTime :最多等待时间 * @param timeout :上锁后自动释放锁时间 * @author duanyong * @date 2020/6/22 10:35 * @return: boolean 是否成功,成功返回:true */ @Override public boolean tryLock(String lockKey, int waitTime, int timeout) { return tryLock(lockKey, TimeUnit.SECONDS,waitTime,timeout); } /** * 尝试获取锁 *
  • * * @param lockKey : lockKey * @param unit :时间单位 * @param waitTime :最多等待时间 * @param timeout :上锁后自动释放锁时间 * @author duanyong * @date 2020/6/22 10:36 * @return: boolean 是否成功,成功返回:true */ @Override public boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int timeout) { Assert.hasText(lockKey,"lockKey 不能为空"); lockKey = CACHE_PREFIX_KEY +lockKey; log.info(">>>>> tryLock lockKey:{},TimeUnit:{},waitTime:{},timeout:{}",lockKey,unit,waitTime,timeout); RLock lock = redissonClient.getLock(lockKey); try { return lock.tryLock(waitTime, timeout, unit); } catch (Exception e) { e.printStackTrace(); log.error("尝试获取锁:{},TimeUnit:{},waitTime:{},timeout:{},失败",lockKey,unit,waitTime,timeout,e); } return false; } /** * 释放锁 *
  • * * @param lockKey : lockKey * @author duanyong * @date 2020/6/22 10:37 * @return: void */ @Override public void unlock(String lockKey) { lockKey = CACHE_PREFIX_KEY +lockKey; try { RLock lock = redissonClient.getLock(lockKey); if(!lock.isLocked()){ log.error(">>>>> unlock lockKey fail:{},isLocked:{}",lockKey,lock.isLocked()); return; } if(!lock.isHeldByCurrentThread()){ log.error(">>>>> unlock lockKey fail:{},isHeldByCurrentThread:{}",lockKey,lock.isHeldByCurrentThread()); return; } lock.unlock(); log.info(">>>>> unlock lockKey success:{}",lockKey); } catch (Exception e) { e.printStackTrace(); log.error("释放锁失败:{}",lockKey,e); } } /** * 释放锁 *
  • * * @param lock :锁对象 * @author duanyong * @date 2020/6/22 10:37 * @return: void */ @Override public void unlock(RLock lock) { lock.unlock(); }

    3、锁注解

    /**
     * 服务锁注解
     * 
  • * @author duanyong * @date 2020/10/14 9:20 */ @Documented @Inherited @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MethodLock { /** * 指定需要加入锁的字段 *
  • * @author duanyong * @date 2020/10/14 9:21 * @return: java.lang.String[] */ String[] fieldName() default {}; /** * 锁的有效时间,单位为秒,默认值为60 *
  • * @author duanyong * @date 2020/10/14 9:21 * @return: int */ int timeInSecond() default 60; /** * 等待时间,单位为秒,默认值为0 *
  • * @author duanyong * @date 2022/10/8 9:34 * @param * @return: int */ int waitTime() default 0; /** * 指定参数在参数列表中的索引 *
  • * @author duanyong * @date 2020/10/14 9:22 * @return: int */ int paramIndex() default 0; /** * 方法冷却时间 *
  • 多久能调用一次该方法,单位为秒,默认值为0,不限制
  • * @author duanyong * @date 2023/1/9 15:52 * @return: int */ int coolingTime() default 0; }

    4、锁切面

    /**
     * 业务拦截器
     * 
  • * @author duanyong * @date 2021/3/1 15:56 */ @Slf4j @Aspect @Component public class LockAspect { /**锁对象*/ @Autowired private Lock lock; @Around("@annotation(methodLock)") public Object around(ProceedingJoinPoint joinPoint, MethodLock methodLock) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); // 获取被拦截的方法 Method method = signature.getMethod(); // 获取被拦截的类名 String className = signature.getDeclaringType().getSimpleName(); // 获取被拦截的方法名 String methodName = method.getName(); log.info("[加锁交易]请求开始,方法口名:{}", className+"."+methodName); //生成锁KEY Optional lockKeyOptional = generateKey(joinPoint.getArgs(),className,methodName,methodLock); if(lockKeyOptional.isPresent()){ String lockKey = lockKeyOptional.get(); if(tryLock(lockKey,methodLock) && enabled(lockKey,methodLock)){ try { log.info("[加锁交易]加锁成功,MethodName:{},key:{}",methodName,lockKey); return joinPoint.proceed(); } catch (Exception e) { log.error("[加锁交易]执行方法发生异常,MethodName:{},lockKey:{},Exception:{}",methodName,lockKey,e); throw e; }finally { lock.unlock(lockKey); log.info("[加锁交易]方法解锁,MethodName:{},key:{}",methodName,lockKey); } }else{ log.error("[加锁交易]过滤频繁操作,加锁失败,Key:{}",lockKey); return null; } }else{ return joinPoint.proceed(); } } /** * 对添加RedisLock注解的方法进行重复访问限制 * @param cacheKey * @param methodLock */ private boolean tryLock(String cacheKey,MethodLock methodLock) { boolean isLocked = lock.tryLock(cacheKey, TimeUnit.SECONDS,methodLock.waitTime(),methodLock.timeInSecond()); if(isLocked){ log.info("[交易系统拦截器]加锁成功,KEY:{}",cacheKey); } return isLocked; } /** * 方法是否可用 *
  • * @author duanyong * @date 2023/1/9 16:09 * @param cacheKey 缓存KEY * @param methodLock 锁接口 * @return: boolean */ private boolean enabled(String cacheKey,MethodLock methodLock) { if(methodLock.coolingTime() > 0){ String coolTimeKey = cacheKey+"_disabled"; boolean isDisabled = lock.tryLock(coolTimeKey, TimeUnit.SECONDS,methodLock.waitTime(),methodLock.coolingTime()); if(isDisabled){ log.info("[交易系统拦截器]方法冻结成功,KEY:{}",coolTimeKey); } return isDisabled; } return true; } /** * 生成锁的key key=类名-方法名-参数集合 *
  • * @author duanyong * @date 2021/4/29 13:08 * @param args: 参数 * @param className: 类名 * @param methodName: 方法名 * @param methodLock: 锁接口 * @return: java.lang.String 锁的key */ private Optional generateKey(Object[] args, String className, String methodName, MethodLock methodLock) throws Exception { try{ //根据参数列表索引获取入参 默认为第一个参数 StringBuilder keyBuilder = new StringBuilder(); //参数为空,则为方法锁 if(args == null || args.length <= 0){ keyBuilder.append(className) .append("-") .append(methodName); return Optional.of(keyBuilder.toString()); } Object param = args[methodLock.paramIndex()]; List fieldValueList = new ArrayList<>(); String[] fieldNames = methodLock.fieldName(); if(param instanceof String){ fieldValueList.add(String.valueOf(param)); }else if(param instanceof Long){ fieldValueList.add(String.valueOf(param)); }else{ String jsonString = JSON.toJSONString(param); JSONObject jsonObject = JSON.parseObject(jsonString); for (String filedName:fieldNames){ if(jsonObject.containsKey(filedName)){ fieldValueList.add(jsonObject.getString(filedName)); } } } keyBuilder.append(className) .append("-") .append(methodName) .append("-") .append(fieldValueList); return Optional.of(keyBuilder.toString()); }catch (Exception e){ log.error("生成锁的key失败",e); } return Optional.empty(); } }

    5、锁接口对象持有者

    /**
     * 锁接口对象持有者
     * 
  • * * @author: duanyong * @since: 2021/3/16 8:56 */ public class LockHolder { /** 锁对象*/ static Lock lock; public static Optional getLock() { return Optional.ofNullable(lock); } }

    6、自动配置。

    /**
     * 自动配置类
     * 
  • * @author duanyong * @date 2021/3/5 9:50 */ @Slf4j @Configuration @EnableConfigurationProperties(value = LockConfig.class) @ConditionalOnClass(Lock.class) @ConditionalOnProperty(prefix = LockConfig.PREFIX, value = LockConfig.ENABLED, matchIfMissing = true) public class LockAutoConfiguration { @Autowired private LockConfig lockConfig; @Bean @ConditionalOnMissingBean(Lock.class) public Lock createLock() { log.info("初始化分布式锁,实现类名称:{}",lockConfig.getImpl()); LockHolder.lock = ExtensionLoader.getExtensionLoader(Lock.class).getExtension(lockConfig.getImpl()); log.info("初始化分布式锁成功,实现类:{}",LockHolder.lock); return LockHolder.lock; } @Bean public LockAspect createLockAspect() { return new LockAspect(); } }

    一些信息

    路漫漫其修远兮,吾将上下而求索
    码云:https://gitee.com/javacoo
    QQ群:164863067
    作者/微信:javacoo
    邮箱:[email protected]
    

    你可能感兴趣的:(分布式锁 starter)