分布式锁 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 测试
pom依赖:
<dependency> <groupId>com.javacoo</groupId> <artifactId>lock-spring-boot-starter</artifactId> <version>1.0.0</version></dependency>
配置参数,如果使用默认实现,则无需配置,如要扩展则需要,配置如下:
#lock是否可用,默认可用 lock.enabled = true #lock实现,默认内部实现 lock.impl = default
方法加注解,如:
注解说明
字段 | 类型 | 说明 |
---|---|---|
fieldName | String[] | 指定需要加入锁的字段,默认为空 |
timeInSecond | int | 锁的有效时间,单位为秒,默认值为60 |
waitTime | int | 等待时间,单位为秒,默认值为0 |
paramIndex | int | 指定参数在参数列表中的索引,默认值为0 |
coolingTime | int | 方法冷却时间,多久能调用一次该方法,单位为秒,默认值为0,不限制 |
示例
//参数级锁@MethodLock(fieldName = "applNo")@Overridepublic BaseResponse gjjloanConfirm(LoanConfirmRequest request) { ...} //参数级锁,带冷却时间@MethodLock(fieldName = "username",coolingTime = 10)public ResponseEntity login(@RequestBody UserLoginRequest loginRequest, HttpServletRequest request) { ...} //方法级锁@MethodLockpublic void divisionCase() { ...}
基于xkernel 提供的SPI机制,扩展非常方便,大致步骤如下:
实现锁接口:如 com.xxxx.xxxx.MyLockImpl
配置锁接口:
在项目resource目录新建包->META-INF->services
创建com.javacoo.lock.client.api.Lock文件,文件内容:实现类的全局限定名,如:
myLock=com.xxxx.xxxx.MyLockImpl
修改配置文件,添加如下内容:
#lock实现 lock.impl = myLock
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、实现锁接口,如默认实现:
/** * 锁接口实现类 * <li>基于Redssion</li> * * @author: duanyong * @since: 2020/6/22 10:39 */@Slf4jpublic 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、锁注解
/** * 服务锁注解 * <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; /** * 指定参数在参数列表中的索引 * <li></li> * @author duanyong * @date 2020/10/14 9:22 * @return: int */ int paramIndex() default 0; /** * 方法冷却时间 * <li>多久能调用一次该方法,单位为秒,默认值为0,不限制</li> * @author duanyong * @date 2023/1/9 15:52 * @return: int */ int coolingTime() default 0;}
4、锁切面
/** * 业务拦截器 * <li></li> * @author duanyong * @date 2021/3/1 15:56 */@Slf4j@Aspect@Componentpublic 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("[加锁交易]执行方法发生异常,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; } /** * 方法是否可用 * <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("[交易系统拦截器]方法冻结成功,KEY:{}",coolTimeKey); } return isDisabled; } return true; } /** * 生成锁的key key=类名-方法名-参数集合 * <li></li> * @author duanyong * @date 2021/4/29 13:08 * @param args: 参数 * @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{ //根据参数列表索引获取入参 默认为第一个参数 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<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("初始化分布式锁,实现类名称:{}",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(); }
2023-12-13T11:46:19
2023-12-13T11:48:22
2024-01-02T09:07:42
2024-01-02T09:07:20
2024-01-02T09:06:50
2024-01-02T09:06:26
2024-01-02T09:06:01
2024-01-02T09:05:20
2024-01-02T09:04:49
2024-01-02T09:04:17