引言:
非单点利用,JDK自带的管程锁(即:监视器锁、Monitor锁,通过synchronized关键字来实现加锁)、或可重入锁(ReentrantLock)已无奈做到对临界资源的加锁,达到同步拜访的目标。
简介:
分布式锁基于Redis + SpringAOP来实现,通过申明式注解的形式为具体临界资源加锁,达到同步拜访的目标。
实现:
RedissionLock注解类定义:
@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface RedissonLock { /** * 锁生效工夫 * 默认-1秒,永恒持有,直到以后锁被开释 */ int expireTime() default -1; /** * 抢锁等待时间,默认30秒,-1示意不期待,胜利或失败都立刻返回 */ int waitTime() default 30; /** * 锁过期工夫单位,默认秒 */ TimeUnit expireTimeUnit() default TimeUnit.SECONDS; /** * 抢锁等待时间单位,默认秒 */ TimeUnit waitTimeUnit() default TimeUnit.SECONDS; //-------------------------------------------------------------------- // redisKey 对 values、expressions、spEL、keyGenerator 进行拼接 //-------------------------------------------------------------------- /** * redisKey */ String[] values() default {}; /** * redisKey * 反对OGNL表达式 */ String[] expressions() default {}; /** * redisKey * Spring Expression Language (SpEL) expression for computing the key dynamically. */ String[] spEL() default {}; /** * The bean name of the custom {@link RedisKeyGenerator} to use. */ String keyGenerator() default ""; /** * 未拿锁时的错误码 * MSG_1000007(1000007, "零碎忙碌,请稍后再试(key=%s)") */ ErrCode errorCode() default ErrCode.MSG_1000007; /** * 等锁超时,是否抛出异样 */ boolean throwExceptionWhenTryLockTimeOut() default true;}
RedisKeyGenerator接口定义:
@FunctionalInterfacepublic interface RedisKeyGenerator { /** * Generate a key for the given method and its parameters. * @param target the target instance * @param method the method being called * @param params the method parameters (with any var-args expanded) * @return a generated key */ String generate(Object target, Method method, Object... params);}
切面定义:
/** * 确保此切面执行程序在事务切面之前。事务切面执行程序是 {@link Integer#MAX_VALUE} */@Slf4j@Aspect@Order(0)@Componentpublic class LockAop implements ApplicationContextAware { @Resource private RedissonClient redissonClient; @Pointcut("@annotation(RedissonLock)") private void pointCut() { } private static final String prefix = "c:e:lock:"; @Around(value = "pointCut() && @annotation(redissonLock)") public Object around(ProceedingJoinPoint joinPoint, RedissonLock redissonLock) throws Throwable { String redisKey; // 字符串 StringJoiner joiner = new StringJoiner(":", prefix, StringUtils.EMPTY); String[] values = redissonLock.values(); if (ArrayUtils.isNotEmpty(values)) { for (String value : values) { joiner.add(value); } } // OGNL 表达式 String[] expressions = redissonLock.expressions(); if (ArrayUtils.isNotEmpty(expressions)) { // 根对象 Map<String, Object> rootObject = getVariables(joinPoint); for (String expression : expressions) { joiner.add(String.valueOf(OgnlCache.getValue(expression, rootObject))); } } // Spring EL 表达式 String[] spEL = redissonLock.spEL(); if (ArrayUtils.isNotEmpty(spEL)) { StandardEvaluationContext context = new StandardEvaluationContext(); // 上下文对象中的变量 Map<String, Object> rootObject = getVariables(joinPoint); context.setVariables(rootObject); SpelExpressionParser spelExpressionParser = new SpelExpressionParser(); for (String el : spEL) { Expression expression = spelExpressionParser.parseExpression(el); String value = expression.getValue(context, String.class); joiner.add(value); } } // key 生成器 String keyGenerator = redissonLock.keyGenerator(); if (StringUtils.isNotEmpty(keyGenerator)) { if (applicationContext.containsBean(keyGenerator)) { RedisKeyGenerator bean = applicationContext.getBean(RedisKeyGenerator.class, keyGenerator); MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); String value = bean.generate(joinPoint.getTarget(), methodSignature.getMethod(), joinPoint.getArgs()); joiner.add(value); } } redisKey = joiner.toString(); if (StringUtils.isEmpty(redisKey)) { // MSG_1000008(1000008, "锁key不能为空") throw new ServiceException(ErrCode.MSG_1000008); } // 尝试获取锁 RLock rLock = redissonClient.getLock(redisKey); // 对立过期工夫和等待时间单位为纳秒 long expireTime = redissonLock.expireTime(); long waitTime = redissonLock.waitTime(); if (expireTime != -1) { expireTime = redissonLock.expireTimeUnit().toNanos(expireTime); } if (waitTime != -1) { waitTime = redissonLock.waitTimeUnit().toNanos(waitTime); } boolean tryLock = rLock.tryLock(waitTime, expireTime, TimeUnit.NANOSECONDS); if (!tryLock) { log.info("key=[{}] get the lock failure", redisKey); throw ServiceException.getInstance(redissonLock.errorCode(), redisKey); } log.info("key=[{}] get the lock success", redisKey); Object proceed; try { proceed = joinPoint.proceed(); } finally { if (rLock.isHeldByCurrentThread()) { rLock.unlock(); log.info("key=[{}] release the lock success", redisKey); } } return proceed; } private static Map<String, Object> getVariables(ProceedingJoinPoint joinPoint) { // 切面办法参数 Object[] args = joinPoint.getArgs(); // 拿到办法签名 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Map<String, Object> map = new HashMap<>(); // 把参数名和参数对象仍入汇合 for (int i = 0; i < methodSignature.getParameterNames().length; i++) { String parameterName = methodSignature.getParameterNames()[i]; Object arg = args[i]; map.put(parameterName, arg); } return map; } private ApplicationContext applicationContext; @Override public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }}
OGNL表达式应用参考:
所有申请参数会用参数名和参数值存入一个Map中,并设置为根对象。
@RedissonLock(expressions = { "req.activityId", "addReq.dayBound.countBound", "list1[0].activityStartTime", "list2[0]", "name", "map.v555", "arr[0].activityId"})public void lockTest(ActivityTerminatedRequest req, ActivityAddAndUpdateRequest addReq, List<ActivityAddAndUpdateRequest> list1, List<Integer> list2, String name, Map<String, String> map, ActivityAddAndUpdateRequest[] arr) {}
SPEL表达式应用参考:
所有申请参数会被设置成上下文变量。
@RedissonLock( spEL = {"#req.activityId", "#addReq.dayBound.countBound", "#list1[0].activityStartTime", "#list2[0]", "#name", "#map['v555']", "#arr[0].activityId"})public void lockTest(ActivityTerminatedRequest req, ActivityAddAndUpdateRequest addReq, List<ActivityAddAndUpdateRequest> list1, List<Integer> list2, String name, Map<String, String> map, ActivityAddAndUpdateRequest[] arr) {}
残缺应用参考:
@RedissonLock(waitTime = 10, values = {PromoDomain.PROMO_DOMAIN_NAME, PromoDomain.ACTIVITY_DOMAIN_NAME}, expressions = {"req.activityId", "addReq.dayBound.countBound", "list1[0].activityStartTime", "list2[0]", "name", "map.v555", "arr[0].activityId"}, spEL = {"#req.activityId", "#addReq.dayBound.countBound", "#list1[0].activityStartTime", "#list2[0]", "#name", "#map['v555']", "#arr[0].activityId"}, keyGenerator = "activityKeyGenerator")@Overridepublic void lockTest(ActivityTerminatedRequest req, ActivityAddAndUpdateRequest addReq, List<ActivityAddAndUpdateRequest> list1, List<Integer> list2, String name, Map<String, String> map, ActivityAddAndUpdateRequest[] arr) {}@Service(value = "activityKeyGenerator")public class ActivityKeyGenerator implements RedisKeyGenerator { @Override public String generate(Object target, Method method, Object... params) { String name = (String)params[4]; return name + "#Generate"; }}
此锁只合适局部场景,红锁,点锁不提供反对。锁重入时,请留神key的"一致性"。
Note:key的定义必须以畛域名称和聚合名称打头,避免key反复。
援用:
https://commons.apache.org/proper/commons-ognl/language-guide...
https://docs.spring.io/spring-framework/docs/3.2.x/spring-fra...