如何使用注解实现Redssion分布式锁
-
简介
在分布式系统的并发场景中,存在多个进程或线程对共享资源的并发访问,为了保证数据的一致性往往需要使用分布式锁来解决这个问题。 -
认识分布式锁Redssion
Redisson 是一个在 Redis 的基础上实现的 Java 驻内存数据网格客户端(In-Memory Data Grid)。它不仅提供了一系列的 redis 常用数据结构命令服务,还提供了许多分布式服务,例如分布式锁、分布式对象、分布式集合、分布式远程服务、分布式调度任务服务等等。
目前业界实现分布式锁的方式多种多样,如手动加锁解锁 、 分布式锁工具类 、 注解版分布式锁,注解版分布式锁时最三者中最上层的抽象,下面将介绍Reddsion注解版的实现逻辑。
- 引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.25.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
- 实现分布式锁逻辑
- Redssion配置
@Bean
public RedissonClient redissonClient(){
Config config = new Config();
config.setTransportMode(TransportMode.NIO);
SingleServerConfig singleServerConfig = config.useSingleServer();
//可以用"rediss://"来启用SSL连接
singleServerConfig.setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
return redisson;
}
- 定义分布式锁注解,用来锁对应的方法逻辑
@Documented
// 注解是否可以继承
@Inherited
// 指定注解在运行时保留
@Retention(RetentionPolicy.RUNTIME)
// 表示注解只能应用于方法上
@Target({ElementType.METHOD})
public @interface RedssionLockAspect {
/**
* 分布式锁key
* 支持SpEL 表达式
*/
String key() default "";
/**
* 最大等待时间 时间单位以 unit为准
* 默认值(-1) 不等待
*/
int waitTime() default -1;
/**
* 加锁多久后自动解锁 时间单位以 unit为准
* 默认值(-1):默认30s,会通过watchDog机制自动续期
* 其它值,值过期后会自动解锁,不会自动续期
*/
int leaseTime() default -1;
/**
* 时间单位,默认s
*/
TimeUnit unit() default TimeUnit.SECONDS;
}
- 编写切面(分布式锁key支持spel表达式)
@Component
@Aspect
@Order(0)
@Slf4j
public class RedssionLockHandler {
@Autowired
private RedissonClient redissonClient;
// AOP 处理分布式锁逻辑
@Around("@annotation(redssionLock)")
public Object around(ProceedingJoinPoint joinPoint, RedssionLockAspect redssionLock) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
String key = redssionLock.key();
String redssionKey = getRedssionKey(joinPoint, method, key);
Object obj = null;
int waitTime = redssionLock.waitTime();
int leaseTime = redssionLock.leaseTime();
TimeUnit timeUnit = redssionLock.unit();
RLock lock = redissonClient.getLock(redssionKey);
boolean lockFlag = leaseTime != -1 ? lock.tryLock(waitTime, leaseTime, timeUnit) :
waitTime != -1 ? lock.tryLock(waitTime, timeUnit) : lock.tryLock();
if (!lockFlag) {
log.info("redssionKey{},获取分布式锁失败", redssionKey);
throw new RuntimeException("分布式锁正在处理中");
}
try {
obj = joinPoint.proceed();
} catch (Exception e) {
throw new Exception(e.getMessage());
} finally {
/*
* isHeldByCurrentThread方法 和 isLocked方法的区别
* isHeldByCurrentThread: 用于检查当前线程是否持有锁
* isLocked: 用于检查锁是否被任何线程持有
*/
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
return obj;
}
/**
* 获取redis key
*
* @param joinPoint
* @param signature
* @param method
* @param key
* @return
*/
private String getRedssionKey(ProceedingJoinPoint joinPoint, Method method, String key) {
return StringUtils.isNotBlank(key) ? spelParseRedssionKey(joinPoint, method, key) : getMethodKey(method);
}
/**
* 类名加方法名
* @param method
* @return
*/
private String getMethodKey(Method method) {
return method.getDeclaringClass() + "-" + method.getName();
}
/**
* spel表达式解析key
*
* @param joinPoint
* @param method
* @param key
* @return
*/
private String spelParseRedssionKey(ProceedingJoinPoint joinPoint, Method method, String key) {
Object[] args = joinPoint.getArgs();
ExpressionParser spelExpressionParser = new SpelExpressionParser();
LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
String[] paramNames = discoverer.getParameterNames(method);
EvaluationContext context = new StandardEvaluationContext();
if (paramNames != null) {
for (int i = 0; i < paramNames.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
}
Expression expression = spelExpressionParser.parseExpression(key);
return expression.getValue(context, String.class);
}
}
- 测试
@RedssionLockAspect(key = "'testRedssion'+#user.name")
public void testDistributeLock(User user) {
System.out.println("id--->" + user.toString());
}
通过上面的步骤,一个支持Spel表达式注解版的分布式锁就实现啦!!!源码地址:https://gitee.com/git_xiaodong/springboot-demo,供参考。