引言:
非单点利用,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 接口定义:
@FunctionalInterface
public 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)
@Component
public 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")
@Override
public 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…