分布式锁的设计共分为3步
- 定义注解
- 对注解进行扫描
- 使用注解
加锁核心逻辑为
RLock rLock = redissonClient.getLock(key);
//是否加锁成功
boolean isLock = rLock.tryLock(timeOut, expireTime, timeUnit);
1.定义注解 LockAction
package com.jwds.app.compont.cache.annotation;
import com.jwds.app.compont.cache.service.lock.AppLockService;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
* 同步锁注解
*
* @author by liang.zhang
* @Description
* @Date 2021/9/7
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface LockAction {
/**
* 锁的资源,key。支持spring El表达式
* 例如:#user.id
*/
String value() default "'default'";
/**
* 等待时间 在等待时间内将会一直等待
* 默认-1 不进行等待,获取失败直接返回结果
* @return
*/
long timeOut() default AppLockService.TIME_OUT;
/**
* 获取锁的等待时间单位 超过这个时间就获取失败
*
* @return
*/
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
/**
* 过期时间 默认30分钟,过期后自动删除key -1永远不过期
*
* @return
*/
long expireTime() default AppLockService.EXPIRE_TIME;
/**
* 获取锁失败时的重试次数 默认为0
* 当获取锁失败时,会进行锁的重新获取
*/
int retryTimes() default AppLockService.RETRY_TIMES;
/**
* 获取锁失败时的重试的间隔时间 单位毫秒
*/
long sleepMills() default AppLockService.RETRY_SLEEP_MILLIS;
/**
* 加锁失败的回调方法 参数为方法的入参
* 回调进入该方法,
* @return
*/
String fallbackMethod() default "";
/**
* 未拿到锁 提示消息
*
* @return
*/
String errorMsg() default "操作过于频繁,请稍后再试!";
}
2. 对注解进行切面处理
//主要注意加锁失败后的回调方法
@Around("lockPoint()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
LockAction lockAction = method.getAnnotation(LockAction.class);
String key = lockAction.value();
Object[] args = pjp.getArgs();
//系统编码+key 组成唯一key
//NameSpace命名空间
key = "RedisLock" + AppConstant.COLON + parse(key, method, args);
//重试次数
int retryTimes = lockAction.retryTimes();
boolean lock = lockService
.lock(key, lockAction.timeOut(), lockAction.expireTime(), lockAction.timeUnit(), retryTimes,
lockAction.sleepMills());
//加锁失败抛出异常 如果存在回调方法则不抛出异常,由业务自己去处理
if (!lock) {
//回调方法存在的时候,调用
if (null != lockAction.fallbackMethod() && !lockAction.fallbackMethod().trim().equals("")) {
log.error("get lock failed : {}进入失败回调方法", key);
//获取方法所在的类(controller)
Class<?> beanType = signature.getDeclaringType();
Object object = beanType.newInstance();
//参数转变
Class<?>[] params = new Class[args.length];
for (int i = 0; i < args.length; i++) {
Object o = args[i];
params[i] = o.getClass();
}
try {
//回调失败方法
Method infoMethod = beanType.getMethod(lockAction.fallbackMethod(), params);
if (infoMethod==null){
throw new RuntimeException("回调方法:"+lockAction.fallbackMethod()+"不存在或为private的");
}
infoMethod.invoke(object, args);
} catch (Exception e) {
log.error("调用加锁失败回调方法{}异常", lockAction.fallbackMethod(), e);
throw new LockException(lockAction.errorMsg());
}
} else {
log.error("get lock failed : {}没有失败回调方法", key);
throw new LockException(lockAction.errorMsg());
}
}
//具体的锁代码
@Override
public boolean lock(String key, long timeOut, long expireTime, TimeUnit timeUnit, int retryTimes,
long retrySleepMillis) {
//增加系统编码
key = appCacheProperties.getSysCode() + AppConstant.COLON + key;
boolean result = setRedis(key, timeOut, expireTime, timeUnit);
// 如果获取锁失败,按照传入的重试次数进行重试
while ((!result) && retryTimes-- > 0) {
try {
log.debug("lock failed, retrying..." + retryTimes);
Thread.sleep(retrySleepMillis);
} catch (InterruptedException e) {
return false;
}
result = setRedis(key, timeOut, expireTime, timeUnit);
}
return result;
}
//加锁的核心代码
/**
* @param key
* @param timeOut 超时时间
* @param expireTime 过期时间
* @param timeUnit 单位
* @return
*/
private boolean setRedis(String key, long timeOut, long expireTime, TimeUnit timeUnit) {
RLock rLock = redissonClient.getLock(key);
log.debug("原始传入上锁时间:" + timeOut);
//是否加锁成功
boolean isLock = false;
try {
//尝试加锁
isLock = rLock.tryLock(timeOut, expireTime, timeUnit);
} catch (Exception e) {
isLock = false;
log.error("===>{}操作Redis失败:{}", key, e.getMessage());
throw new RuntimeException(e.getMessage());
}
log.debug("取得加锁状态:" + isLock + ",最终超时间:" + timeOut);
return isLock;
}
//解锁核心代码 必须是当前线程才能进行解锁
@Override
public boolean releaseLock(String key) {
//增加系统编码
key = appCacheProperties.getSysCode() + AppConstant.COLON + key;
try {
log.debug("开始执行解锁:" + key);
RLock rLock = redissonClient.getLock(key);
//判断是否锁定
if (rLock.isLocked()) {
//判断是否是当前线程锁定
if (rLock.isHeldByCurrentThread()) {
//解锁
//rLock.unlockAsync();
rLock.unlock();
}
}
return true;
} catch (Exception e) {
log.error("解锁失败:" + e);
return false;
}
}
3.注解使用
其中value 支持EL表达式
@LockAction(value = "#reqDto.queryCondition.id", fallbackMethod = "fallbackMethod")
也可以手动调用代码 进行加锁和解锁操作
AppLockService.lock();