一个注解搞定redis分布式锁
首先自定义注解:@DistributeLock
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 方法分布式锁注解
*
* @author 敖癸
* @formatter:on
* @since 2024/3/4
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {
/** redis锁 key名称,支持字符串模板:如:KfptAppName:Lock:{}, {}表示占位符, 由args中的参数替换 */
String key();
/** 模板参数,支持SpEL表达式, ex: {"#serialNo", "#serialNo.length()", "T(java.util.Random).nextInt()", "isTure?'success':'false'", "normal"} */
String[] args() default {};
/** 锁过期时间(毫秒),默认3秒 */
long expire() default 3000;
/** 尝试获取锁超时时间 */
long timeout() default 0;
/** 尝试获取锁间隔时间(ms) */
int tryInterval() default 5;
/** 方法执行完是否立刻释放锁?默认true,表示方法执行完毕立即释放,false表示等到expire过期后释放 */
boolean isRelease() default true;
}
AOP切面拦截注解
import cn.hutool.core.lang.Opt;
import cn.hutool.core.thread.ThreadUtil;
import com.yc.kfpt.starter.common.util.AspectUtil;
import com.yc.kfpt.starter.redis.annotation.DistributedLock;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import java.util.concurrent.TimeUnit;
/**
* 分布式锁注解切面
*
* @author 敖癸
* @formatter:on
* @since 2023/11/29
*/
@Slf4j
@Aspect
@RequiredArgsConstructor
public class DistributedLockAspect {
private RedisTemplate<String,Object> redisTemplate;
private static final String REMOVE_IF_VAL = "if (redis.call('GET', KEYS[1]) == ARGV[1]) then return redis.call('DEL', KEYS[1]) else return 0 end";
/**
* 拦截被@DistributedLock注解的方法
*
* @param joinPoint
* @param distributedLock
* @return java.lang.Object
* @author 敖癸
* @since 2024/3/29 - 10:45
*/
@Around("@annotation(distributedLock)")
public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
// 根据注解参数生成redis key
String key = generateKey(joinPoint, distributedLock);
// 当前时间戳
long currentTimestamp = System.currentTimeMillis();
// 获取锁
if (acquireLock(distributedLock, key, currentTimestamp)) {
try {
return joinPoint.proceed();
} finally {
// 方法执行完成是否需要立即释放锁
if (distributedLock.isRelease()) {
// 释放锁, 释放时校验value值是否是当前线程的时间戳, 防止因为锁过期误删除其它线程的锁
RedisScript<Boolean> redisScript = RedisScript.of(REMOVE_IF_VAL, Boolean.class);
this.redisTemplate.execute(redisScript, Collections.singletonList(key), currentTimestamp);
}
}
} else {
// 获取锁失败
throw new RuntimeException("获取"+key+"锁失败");
}
}
/**
* 根据注解参数生成redis key
*
* @param joinPoint
* @param distributedLock
* @return java.lang.String
* @author 敖癸
* @since 2024/3/29 - 10:49
*/
private String generateKey(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) {
String key = AspectUtil.buildTemplate(joinPoint, distributedLock.key(), distributedLock.args());
return Opt.ofBlankAble(key).orElseThrow(()->new RuntimeException("redis key 不能为空"));
}
/**
* 获取锁
*
* @param distributedLock
* @param key
* @param currentTimestamp
* @return java.lang.Boolean
* @author 敖癸
* @since 2024/3/29 - 10:50
*/
private Boolean acquireLock(DistributedLock distributedLock, String key, long currentTimestamp) {
// 锁过期时间(ms)
long expire = distributedLock.expire();
// 尝试获取锁的等待时间(ms)
long timeout = distributedLock.timeout();
// 尝试获取锁
boolean tryLock = tryLock(key, currentTimestamp, expire);
// 如果获取锁失败,且超时等待时间大于0,则睡眠distributedLock.tryInterval() ms后重试
while (!tryLock && timeout > 0) {
// 如果超过timeout时间还没获取到锁,则获取锁失败
if (System.currentTimeMillis() - currentTimestamp > timeout) {
break;
}
ThreadUtil.sleep(distributedLock.tryInterval());
// 在次尝试获取锁
tryLock = tryLock(key, currentTimestamp, expire);
}
return tryLock;
}
/**
* 尝试获取锁
*
* @param key
* @param currentTimestamp
* @param expire
* @return boolean
* @author 敖癸
* @since 2024/3/29 - 10:53
*/
private boolean tryLock(String key, long currentTimestamp, long expire) {
return redisTemplate.opsForValue().setIfAbsent(key, currentTimestamp,expire, TimeUnit.MILLISECONDS);
}
}
依赖的工具类
import com.yc.kfpt.commons.util.StrUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.Arrays;
/**
* @author 敖癸
* @formatter:on
* @since 2024/3/4
*/
public class AspectUtil {
private static final ExpressionParser PARSER = new SpelExpressionParser();
/**
* 解析切点的上下文对象
*
* @param joinPoint aop切点
* @return org.springframework.expression.EvaluationContext
* @author 敖癸
* @since 2024/3/5 - 17:05
*/
public static EvaluationContext buildEvaluationContext(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切点的方法参数名称列表
String[] argNames = signature.getParameterNames();
EvaluationContext context = new StandardEvaluationContext();
// 获取切点的参数值列表
Object[] methodArgs = joinPoint.getArgs();
for (int i = 0; i < methodArgs.length; i++) {
// 将参数名称和参数值一一对应
context.setVariable(argNames[i], methodArgs[i]);
}
return context;
}
/**
* SpEL模板解析
*
* @param joinPoint aop切点
* @param template 字符串模板
* @param expressions SpEL参数
* @return java.lang.String
* @author 敖癸
* @since 2024/3/5 - 17:03
*/
public static String buildTemplate(ProceedingJoinPoint joinPoint, String template, String[] expressions) {
return buildTemplate(template, expressions, buildEvaluationContext(joinPoint));
}
/**
* SpEL模板解析
*
* @param context 切点的上下文对象
* @param template 字符串模板
* @param expressions SpEL参数
* @return java.lang.String
* @author 敖癸
* @since 2024/3/5 - 17:03
*/
public static String buildTemplate(String template, String[] expressions, EvaluationContext context) {
if (StrUtil.isNotBlank(template) && expressions != null) {
Object[] args = Arrays.stream(expressions).map(expression -> {
try {
return PARSER.parseExpression(expression).getValue(context);
} catch (Exception e) {
return expression;
}
}).toArray();
template = StrUtil.format(template, args);
}
return template;
}
}
食用方式:
// 锁5秒过期, 默认方法执行完成立即释放(并发锁)
@DistributedLock(key = "Lock:SqlExec:{}", args = "T(com.dmjy.util.SqlParserUtil).hash(#sql)", expire = 5000)
public void scanData(String sql) {
...
}
// 锁5秒过期, isRelease = false表示方法执行完成不立即释放(防重锁)
@DistributedLock(key = "Lock:SqlExec:{}", args = "T(com.dmjy.util.SqlParserUtil).hash(#sql)", expire = 5000, isRelease = false)
public void scanData(String sql) {
...
}
// 锁5秒过期, timeout = 3000表示获取锁超时等待3秒, 超过3秒还没获取到, 就抛出runtime异常
@DistributedLock(key = "Lock:SqlExec:{}", args = "T(com.dmjy.util.SqlParserUtil).hash(#sql)", expire = 5000, timeout = 3000)
public void scanData(String sql) {
...
}
浙公网安备 33010602011771号