1、背景
在日常开发中,为了防止高并发,在不依赖过多的中间件的情况下,最常使用的分布式锁之一是 Redis锁。使用Redis锁就不得不面临一个问题,就是在业务代码中要控制Redis加锁、释放锁等等,对代码的侵入性较强。本文采用注解的方式为方法体增加分布式锁,唯一标识从方法参数中动态获取。
2、优点
- 无侵入。通过注解实现加锁和释放锁,代码中只需关注业务实现,无须关心“锁”问题,避免代码侵入。
- 无死锁。即使某一线程中断没能释放锁,在到达指定的时间后,程序会自动释放锁。
- 锁唯一独有。加锁和释放锁必须由同一线程执行,不会出现A线程加锁后,B线程将锁释放。
- 支持多种方式传参做key。通过注解指定参数名,通过反射,动态获得key。
- 线程间锁互斥。在同一时间内,仅有一个线程持有锁,避免多个线程同时执行逻辑,出现并发情况。
3、实现方式
- 通过AOP切面对方法执行前和方法执行后加锁和释放锁
- 注解参数传入key保证唯一,通过key加锁保证key不会重复
- 默认释放锁时间30分钟,可以通过注解参数设置不同方法的默认释放锁时间防止死锁
4、具体实现
分布式锁注解,适用于方法 通过key定义请求唯一值,防止业务重复提交, 业务重复提交判断需要在方法内判断,注解能拦截正在提交中不能再次请求唯一值。 key中禁止使用 ” p # + . “字符 这几个字符作为解析key的关键字
4.1、定义一个注解,接收参数key
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface RedisLockTarget {
/**请求唯一标识*/
String key() default "";
}
4.2、定义aop切入注解,获取有注解的方法
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Date;
@Aspect
@Component
@Slf4j
public class RedisLockAop {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private RedisLock redisLock;
@Around("@annotation(RedisLockTarget)")
public void doAround(ProceedingJoinPoint joinPoint){
Boolean redisLockFlag = false;
String redisKey = null;
// 获得当前访问的class
Class<?> clazz = joinPoint.getTarget().getClass();
// 获得访问的方法名
String methodName = joinPoint.getSignature().getName();
// 得到方法的参数的类型
Class<?>[] argClass = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
try {
// 得到访问的方法对象
Method method = clazz.getMethod(methodName, argClass);
method.setAccessible(true);
if (method.isAnnotationPresent(RedisLockTarget.class)) {
RedisLockTarget annotation = method.getAnnotation(RedisLockTarget.class);
redisKey = getRedisKey(annotation);
//加锁
RedisLock.initRedisLock(redisTemplate,redisKey);
}
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
log.error(throwable.getMessage());
}finally {
//释放锁
if (redisLockFlag) {
if (log.isDebugEnabled()) {
log.debug(redisKey, "释放时间:", System.currentTimeMillis());
}
redisLock.unLock();
}
}
}
/**
* 解析key对应的参数
*
*
* @param annotation
* @return
* @throws Exception
*/
private String getRedisKey(RedisLockTarget annotation) throws Exception {
String redisKey = annotation.key();
String format = DateFormatUtils.format(new Date(), "yyyyMMddHHmmss");
log.info("分布式锁注解获取到的key值:{}",redisKey+":"+format);
return redisKey+":"+format;
}
}
5、总结
利用注解加切面来动态的加锁,无侵入无死锁锁唯一独有,可以方便开发。