分布式鎖 starter

lock-spring-boot-starter

分布式鎖 starter

介紹

說明

工程接口(擴展點):

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

基于xkernel 提供的SPI機制,結(jié)合SpringBoot注解 ConditionalOnBean,ConditionalOnProperty實現(xiàn)。

類關(guān)系圖

Lock.png

項目結(jié)構(gòu)

lock-spring-boot-starter
 └── src
    ├── main  
    │   ├── java  
    │   │   └── com.javacoo 
    │   │   ├────── lock
    │   │   │         ├──────client
    │   │   │         │         ├── api
    │   │   │         │         │     ├── annotation
    │   │   │         │         │     │      └── MethodLock 鎖注解
    │   │   │         │         │     ├── client
    │   │   │         │         │     │      └── Lock 鎖接口
    │   │   │         │         │     ├── aspect
    │   │   │         │         │     │      └── LockAspect 鎖切面
    │   │   │         │         │     ├── config
    │   │   │         │         │     │      └── LockConfig 鎖配置
    │   │   │         │         │     ├── exception
    │   │   │         │         │            └── LockException 鎖異常
    │   │   │         │         └── internal 接口內(nèi)部實現(xiàn)
    │   │   │         │               ├── redis
    │   │   │         │                      ├── RedissionConfig Redission配置類
    │   │   │         │                      └── RedssionLock 鎖接口實現(xiàn)類
    │   │   │         └──────starter
    │   │   │                   ├── LockAutoConfiguration 自動配置類
    │   │   │                   └── LockHolder 鎖接口對象持有者
    │   └── resource  
    │       ├── META-INF
    │             └── ext
    │                  └── internal
    │                          └── com.javacoo.lock.client.api.Lock
    └── test  測試

如何使用

  1. pom依賴:

    <dependency>
       <groupId>com.javacoo</groupId>
       <artifactId>lock-spring-boot-starter</artifactId>
       <version>1.0.0</version>
    </dependency>
    
  2. 配置參數(shù),如果使用默認實現(xiàn),則無需配置,如要擴展則需要,配置如下:

    #lock是否可用,默認可用
    lock.enabled = true
    #lock實現(xiàn),默認內(nèi)部實現(xiàn)
    lock.impl = default
    
  3. 方法加注解,如:

注解說明

字段 類型 說明
fieldName String[] 指定需要加入鎖的字段,默認為空
timeInSecond int 鎖的有效時間,單位為秒,默認值為60
waitTime int 等待時間,單位為秒,默認值為0
paramIndex int 指定參數(shù)在參數(shù)列表中的索引,默認值為0
coolingTime int 方法冷卻時間,多久能調(diào)用一次該方法,單位為秒,默認值為0,不限制

示例

//參數(shù)級鎖
@MethodLock(fieldName = "applNo")
@Override
public BaseResponse gjjloanConfirm(LoanConfirmRequest request) {
 ...
}     
//參數(shù)級鎖,帶冷卻時間
@MethodLock(fieldName = "username",coolingTime = 10)
public ResponseEntity login(@RequestBody UserLoginRequest loginRequest, HttpServletRequest request)  {
 ...
}       
//方法級鎖
@MethodLock
public void divisionCase() {
 ...
}   

SPI擴展

基于xkernel 提供的SPI機制,擴展非常方便,大致步驟如下:

  1. 實現(xiàn)鎖接口:如 com.xxxx.xxxx.MyLockImpl

  2. 配置鎖接口:

    • 在項目resource目錄新建包->META-INF->services

    • 創(chuàng)建com.javacoo.lock.client.api.Lock文件,文件內(nèi)容:實現(xiàn)類的全局限定名,如:

      myLock=com.xxxx.xxxx.MyLockImpl
      
    • 修改配置文件,添加如下內(nèi)容:

    #lock實現(xiàn)
    lock.impl = myLock
    

默認實現(xiàn)

1、鎖接口:

/**
 * 鎖接口
 * <li></li>
 *
 * @author: duanyong
 * @since: 2020/6/22 10:19
 */
@Spi(LockConfig.DEFAULT_IMPL)
public interface Lock<T> {
    /**超時時間*/
    int TIMEOUT_SECOND = 60;
    /**
     * 對lockKey加鎖
     * <li></li>
     * @author duanyong
     * @date 2020/6/22 10:30
     * @param lockKey:lockKey
     * @return: T 鎖對象
     */
    T lock(String lockKey);
    /**
     * 對lockKey加鎖,timeout后過期
     * <li></li>
     * @author duanyong
     * @date 2020/6/22 10:31
     * @param lockKey: lockKey
     * @param timeout: 鎖超時時間,單位:秒
     * @return: T 鎖對象
     */
    T lock(String lockKey, int timeout);
    /**
     * 對lockKey加鎖,指定時間單位,timeout后過期
     * <li></li>
     * @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);
    /**
     * 嘗試獲取鎖
     * <li></li>
     * @author duanyong
     * @date 2020/6/22 10:35
     * @param lockKey: lockKey
     * @return: boolean 是否成功,成功返回:true
     */
    boolean tryLock(String lockKey);
    /**
     * 嘗試獲取鎖
     * <li></li>
     * @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);
    /**
     * 嘗試獲取鎖
     * <li></li>
     * @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);
    /**
     * 釋放鎖
     * <li></li>
     * @author duanyong
     * @date 2020/6/22 10:37
     * @param lockKey: lockKey
     * @return: void
     */
    void unlock(String lockKey);
    /**
     * 釋放鎖
     * <li></li>
     * @author duanyong
     * @date 2020/6/22 10:37
     * @param lock:鎖對象
     * @return: void
     */
    void unlock(T lock);
}

2、實現(xiàn)鎖接口,如默認實現(xiàn):

/**
 * 鎖接口實現(xiàn)類
 * <li>基于Redssion</li>
 *
 * @author: duanyong
 * @since: 2020/6/22 10:39
 */
@Slf4j
public class RedssionLock implements Lock<RLock> {
    /**約定緩存前綴:appId:模塊:key*/
    public static final String CACHE_PREFIX_KEY ="javacoo:service:lock:";
    @Autowired
    private RedissonClient redissonClient;
    /**
     * 對lockKey加鎖
     * <li>阻塞獲取鎖</li>
     *
     * @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后過期
     * <li></li>
     *
     * @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后過期
     * <li></li>
     *
     * @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;
    }

    /**
     * 嘗試獲取鎖
     * <li>獲取鎖失敗立即返回,上鎖后60秒自動釋放鎖</li>
     *
     * @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);
    }

    /**
     * 嘗試獲取鎖
     * <li></li>
     *
     * @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);
    }

    /**
     * 嘗試獲取鎖
     * <li></li>
     *
     * @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;
    }

    /**
     * 釋放鎖
     * <li></li>
     *
     * @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);
        }
    }

    /**
     * 釋放鎖
     * <li></li>
     *
     * @param lock :鎖對象
     * @author duanyong
     * @date 2020/6/22 10:37
     * @return: void
     */
    @Override
    public void unlock(RLock lock) {
        lock.unlock();
    }

3、鎖注解

/**
 * 服務(wù)鎖注解
 * <li></li>
 * @author duanyong
 * @date 2020/10/14 9:20
 */
@Documented
@Inherited
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodLock {

  /**
     * 指定需要加入鎖的字段
     * <li></li>
     * @author duanyong
     * @date 2020/10/14 9:21
     * @return: java.lang.String[]
     */
    String[] fieldName() default {};
    /**
     * 鎖的有效時間,單位為秒,默認值為60
     * <li></li>
     * @author duanyong
     * @date 2020/10/14 9:21
     * @return: int
     */
    int timeInSecond() default 60;
    /**
     * 等待時間,單位為秒,默認值為0
     * <li></li>
     * @author duanyong
     * @date 2022/10/8 9:34
     * @param
     * @return: int
     */
    int waitTime() default 0;
    /**
     * 指定參數(shù)在參數(shù)列表中的索引
     * <li></li>
     * @author duanyong
     * @date 2020/10/14 9:22
     * @return: int
     */
    int paramIndex() default 0;
    /**
     * 方法冷卻時間
     * <li>多久能調(diào)用一次該方法,單位為秒,默認值為0,不限制</li>
     * @author duanyong
     * @date 2023/1/9 15:52

     * @return: int
     */
    int coolingTime() default 0;
}

4、鎖切面

/**
 * 業(yè)務(wù)攔截器
 * <li></li>
 * @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<String> 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("[加鎖交易]執(zhí)行方法發(fā)生異常,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注解的方法進行重復(fù)訪問限制
     * @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("[交易系統(tǒng)攔截器]加鎖成功,KEY:{}",cacheKey);
        }
        return isLocked;
    }
    /**
     * 方法是否可用
     * <li></li>
     * @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("[交易系統(tǒng)攔截器]方法凍結(jié)成功,KEY:{}",coolTimeKey);
            }
            return isDisabled;
        }
        return true;
    }
    /**
     * 生成鎖的key key=類名-方法名-參數(shù)集合
     * <li></li>
     * @author duanyong
     * @date 2021/4/29 13:08
     * @param args: 參數(shù)
     * @param className: 類名
     * @param methodName: 方法名
     * @param methodLock: 鎖接口
     * @return: java.lang.String 鎖的key
     */
    private Optional<String> generateKey(Object[] args, String className, String methodName, MethodLock methodLock) throws Exception {
        try{
            //根據(jù)參數(shù)列表索引獲取入?yún)?默認為第一個參數(shù)
            StringBuilder keyBuilder = new StringBuilder();
            //參數(shù)為空,則為方法鎖
            if(args == null || args.length <= 0){
                keyBuilder.append(className)
                        .append("-")
                        .append(methodName);
                return Optional.of(keyBuilder.toString());
            }
            Object param = args[methodLock.paramIndex()];
            List<String> 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、鎖接口對象持有者

/**
 * 鎖接口對象持有者
 * <li></li>
 *
 * @author: duanyong
 * @since: 2021/3/16 8:56
 */
public class LockHolder {
    /** 鎖對象*/
    static Lock lock;

    public static Optional<Lock> getLock() {
        return Optional.ofNullable(lock);
    }
}

6、自動配置。

/**
 * 自動配置類
 * <li></li>
 * @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("初始化分布式鎖,實現(xiàn)類名稱:{}",lockConfig.getImpl());
        LockHolder.lock = ExtensionLoader.getExtensionLoader(Lock.class).getExtension(lockConfig.getImpl());
        log.info("初始化分布式鎖成功,實現(xiàn)類:{}",LockHolder.lock);
        return LockHolder.lock;
    }

    @Bean
    public LockAspect createLockAspect() {
        return new LockAspect();
    }

}

一些信息

路漫漫其修遠兮,吾將上下而求索
碼云:https://gitee.com/javacoo
QQ群:164863067
作者/微信:javacoo
郵箱:xihuady@126.com
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容