引言:
非单点应用,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 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 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 getVariables(ProceedingJoinPoint joinPoint) {
// 切面方法参数
Object[] args = joinPoint.getArgs();
// 拿到方法签名
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Map 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 list1,
List list2, String name,
Map 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 list1,
List list2, String name,
Map 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 list1,
List list2,
String name,
Map 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...