一、目标:
1、简化手动开关锁的重复代码(专注业务本身)
2、集成不同分布式锁解决方案(锁不同使用方式不同)
3、规范锁的命名和异常信息内容(内容不规范,不易于理解和维护)
4、避免事务大于锁的优先级,造成幻读(事务和锁使用不熟)
二、基本需求:
锁的命名、异常信息、锁等待时间、快速失败、异常是否抛出
三、设计思想:
通过springAOP和order注解在方法上切入解决事务和锁的优先级,提供统一开关锁接口loackManager集成不同分布式锁作为锁管理器,使用springEL表达式规范锁的命名和异常信息内容
四、常规分布式锁实现方案
1、内存容器(单机),如concurrentHashMap
2、spring提供的(支持集群)
jdbc-lock-registry、redis-lock-registry、zk-lock-registry
五、代码实现:(目前暂实现concurrentHashMap和redis,如有需要自己拓展)
/*** @description: DistributionLock <br>* 建议:'领域对象:对象行为:'+#业务参数;层级前加:(redis结构清晰),参数前#(spel语法)* @SyncLock(key = "'branchCompany:cece:' + #dto.diposeOrgId, lockManager =* "redis")* @date: 2023/01/15 15:26 <br>* @author: arthur<br>* @version: 1.0 <br>*/
@Target({METHOD})
@Retention(RUNTIME)
public @interface SyncLock {/*** 锁管理器** @return 锁管理器*/String lockManager() default "jvm";/*** SpEL表达式支持** @return SpEL表达式*/String key();/*** SpEL表达式支持** @return SpEL表达式*/String errorMsg() default "";/*** 快速失败** @return 快速失败*/boolean fastFail() default false;/*** 锁等待时间(单位:秒,默认10S)** @return 锁等待时间*/long waitTime() default 10;/*** 异常是否抛出** @return 默认true*/boolean throwError() default true;
}
/*** @description: LockManager <br>* @date: 2023/01/15 15:26 <br>* @author: arthur<br>* @version: 1.0 <br>*/
public interface LockManager {Lock getLock(String lockName);
}/*** @description: JvmLockManager <br>* @date: 2023/01/15 15:26 <br>* @author: arthur<br>* @version: 1.0 <br>*/
public class JvmLockManager implements LockManager {private final Map<String, Lock> cache = new ConcurrentHashMap<>();@Overridepublic Lock getLock(String lockName) {return cache.computeIfAbsent(lockName, k -> new ReentrantLock());}
}/*** @description: RedissionLockManager <br>* @date: 2023/01/15 15:26 <br>* @author: arthur<br>* @version: 1.0 <br>*/
@Component
@RequiredArgsConstructor
public class RedisLockManager implements LockManager {private final RedissonClient redissonClient;@Overridepublic Lock getLock(String lockName) {return redissonClient.getLock(lockName);}
}
/*** @description: DistributionLockAspect <br>* @date: 2023/01/15 15:26 <br>* @author: arthur<br>* @version: 1.0 <br>*/
@Slf4j
@Aspect
@RequiredArgsConstructor
@Order(1)
public class SyncLockAspect implements InitializingBean, ApplicationContextAware {private final ExpressionParser parser = new SpelExpressionParser();private final LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();private ApplicationContext context;private final Map<String, LockManager> lockManagerMap = new HashMap<>();@Pointcut(value = "@annotation(com.runlion.hshb.common.lock.annotation.SyncLock)")public void SyncLock() {}@Around("SyncLock() && @annotation(syncLock)")public Object inject(ProceedingJoinPoint pjp, SyncLock syncLock) throws Throwable {Signature s = pjp.getSignature();MethodSignature ms = (MethodSignature) s;Method method = ms.getMethod();String lockKey = parseSpel(method, pjp.getArgs(), syncLock.key());LockManager lockManager = lockManagerMap.get(syncLock.lockManager());if (null == lockManager) {log.warn("未获取到同步锁管理器:{}", syncLock.lockManager());return pjp.proceed();}Lock lock = lockManager.getLock(lockKey);boolean getLock;//是否快速失败if (syncLock.fastFail()) {getLock = lock.tryLock();} else {getLock = lock.tryLock(syncLock.waitTime(), TimeUnit.SECONDS);}//判断是否获取到锁if (getLock) {try {return pjp.proceed();} finally {lock.unlock();}} else if (syncLock.throwError()) {if (StringUtils.isNotBlank(syncLock.errorMsg())) {throw new IllegalArgumentException(parseSpel(method, pjp.getArgs(), syncLock.errorMsg()));} else {throw new IllegalArgumentException("无法获取分布式锁[" + lockKey + "]");}} else {log.info("无法获取lockKey:: {}", lockKey);return null;}}private String parseSpel(Method method, Object[] arguments, String spelExpression) {String[] params = discoverer.getParameterNames(method);assert params != null;EvaluationContext context = new StandardEvaluationContext();for (int len = 0; len < params.length; len++) {context.setVariable(params[len], arguments[len]);}try {Expression expression = parser.parseExpression(spelExpression);return expression.getValue(context, String.class);} catch (Exception e) {return spelExpression;}}@Overridepublic void afterPropertiesSet() {Map<String, LockManager> managerMap = this.context.getBeansOfType(LockManager.class);for (Map.Entry<String, LockManager> entry : managerMap.entrySet()) {this.lockManagerMap.put(StringUtils.removeEnd(entry.getKey(), "LockManager"), entry.getValue());}log.info("已安装同步锁管理器:{}", this.lockManagerMap.keySet());}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.context = applicationContext;}
}
/*** @description: DistributionLockAutoConfiguration <br>* @date: 2023/01/15 15:26 <br>* @author: arthur<br>* @version: 1.0 <br>*/
@Configuration
public class SyncLockAutoConfiguration {@Beanpublic SyncLockAspect syncLockAspect() {return new SyncLockAspect();}@Beanpublic JvmLockManager jvmLockManager() {return new JvmLockManager();}@Configuration@ConditionalOnProperty(prefix = "lock", value = "distributed", havingValue = "true")@Import({CacheConfiguration.class})@AutoConfigureAfter({CacheConfiguration.class})public static class DistributionLockConfiguration {@Beanpublic RedisLockManager redisLockManager(RedissonClient redissonClient) {return new RedisLockManager(redissonClient);}}
}