0.背景
我们项目一个服务部署了三台服务器,所以为避免在执行定时任务的时候重复执行,
需要引入分布式锁,本打算引入Quartz框架,后发现引入框架需要添加很多数据库,
为做到最小影响,故使用Redisson做分布式锁就行了
1.引入pom坐标
如果是springboot2.0.X使用如下pom坐标
<!--Redisson分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<exclusions>
<!--因为是springboot2.0.8排除21依赖-->
<exclusion>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-21</artifactId>
</exclusion>
</exclusions>
<version>3.10.6</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-20</artifactId>
<version>3.10.6</version>
</dependency>
如果是2.1.X , 直接引入就行了
<!--Redisson分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.10.6</version>
</dependency>
当然redis也需要引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.创建redisson注解
import com.sangfor.enums.LockTypeEnum;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* Redisson锁(Redis分布式锁)注解
*
* @author :zhouJia
* @date :2020-11-09 17:52
**/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
//@Inherited
public @interface LockAction {
/** 锁的资源,key。支持spring El表达式*/
@AliasFor("key")
String value() default "'default'";
@AliasFor("value")
String key() default "'default'";
/** 锁类型*/
LockTypeEnum lockType() default LockTypeEnum.REENTRANT_LOCK;
/** 获取锁等待时间,默认10秒*/
long waitTime() default 10000L;
/** 锁自动释放时间,默认10秒*/
long leaseTime() default 10000L;
/** 时间单位(获取锁等待时间和持锁时间都用此单位)*/
TimeUnit unit() default TimeUnit.MILLISECONDS;
}
3. aop切面类
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
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 org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
/**
* LockAction注解切面类
*
* @author :zhouJia
* @date :2020-11-10 19:52
**/
@Aspect
@Component
//@ConditionalOnBean(RedissonClient.class)
//@AutoConfigureAfter(RedissonAutoConfiguration.class)
@Slf4j
public class RedissonDistributedLockAspectConfiguration {
@Autowired
private RedissonClient redissonClient;
private ExpressionParser parser = new SpelExpressionParser();
private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
@Pointcut("@annotation(com.sangfor.aop.LockAction)")
public void lockPoint(){
}
@Around("lockPoint()")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
LockAction lockAction = method.getAnnotation(LockAction.class);
String key = lockAction.value();
Object[] args = pjp.getArgs();
//key = parse(key, method, args);
RLock lock = getLock(key, lockAction);
if(!lock.tryLock(lockAction.waitTime(), lockAction.leaseTime(), lockAction.unit())) {
log.debug("get lock failed [{}]", key);
log.info(String.format("get lock failed [%s]", LocalDateTime.now()));
return null;
}
//得到锁,执行方法,释放锁
log.debug("get lock success [{}]", key);
try {
log.info(String.format("获取锁%s执行时间为:%s", key, LocalDateTime.now()));
return pjp.proceed();
} catch (Exception e) {
log.error("execute locked method occured an exception", e);
} finally {
lock.unlock();
log.debug("release lock [{}]", key);
}
return null;
}
/**
* @description 解析spring EL表达式
* @author fuwei.deng
* @date 2018年1月9日 上午10:41:01
* @version 1.0.0
* @param key 表达式
* @param method 方法
* @param args 方法参数
* @return
*/
private String parse(String key, Method method, Object[] args) {
String[] params = discoverer.getParameterNames(method);
EvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < params.length; i ++) {
context.setVariable(params[i], args[i]);
}
return parser.parseExpression(key).getValue(context, String.class);
}
private RLock getLock(String key, LockAction lockAction) {
switch (lockAction.lockType()) {
case REENTRANT_LOCK:
return redissonClient.getLock(key);
case FAIR_LOCK:
return redissonClient.getFairLock(key);
case READ_LOCK:
return redissonClient.getReadWriteLock(key).readLock();
case WRITE_LOCK:
return redissonClient.getReadWriteLock(key).writeLock();
default:
throw new RuntimeException("do not support lock type:" + lockAction.lockType().name());
}
}
}
3.实际使用
/**
* 背景: temp_dd
* 定时方法作用: 自动从临时表表同步物料到物料表
*/
@Scheduled(cron = "0 10/30 0/1 * * ?")
@LockAction(value = "LOCK:SCHEDULE:SYNC_TEMP", leaseTime= 15L, unit= TimeUnit.MINUTES)
public void autoSyncTempToProductUnit() {
String key = System.currentTimeMillis() + "";
try {
productUnitService.autoSyncTempToProductUnit(key);
} catch (Exception e) {
log.error("定时同步表失败", e);
moaMessageSender.sendMsg(LocalDateTime.now()+", [同步数据异常]" + e, Arrays.asList(moaRemind.split(SymbolConstant.COMMA)));
}
}