项目中经常用到定时任务,而且在生产环境中定时任务工程往往会部署在多个节点上,就会出现定时任务重复执行的问题。
既要避免定时任务单节点部署,又要同一时刻防止重复执行定时任务, 可以使用redis的分布式锁,为定时任务唯一指定的key加锁,并设置锁超时时间。当触发定时任务时,第一台服务获取到锁,并设置较长的过期时间,执行定时任务方法。第二台服务设置锁的时候发现该锁已存在返回false,不执行定时任务。
项目工程基于spring boot,redis分布式锁使用redisson,结合aop 实现用注解方式加锁。
pom文件添加
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
配置Redisson:
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
@Value("${spring.redis.password}")
private String password;
@Bean
public RedissonClient getRedisson() {
Config config = new Config();
config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
return Redisson.create(config);
}
}
注解定义:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.TimeUnit;
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
long tryTime() default 0;
long lockTime() default 0;
TimeUnit timeUnit() default TimeUnit.SECONDS;
String key();
}
redis分布式锁切面处理:
import org.apache.http.util.Asserts;
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.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
@Aspect
public class RedisLockAspect {
public final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private RedissonClient redissonClient;
@Pointcut("@annotation(com.xx.xx.task.lock.RedisLock)")
public void redisLockAspect() {
}
@Around("redisLockAspect()&&@annotation(redisLock)")
public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
String key = redisLock.key();
Asserts.notEmpty(key,"lockKey is empty");
long tryTime = redisLock.tryTime();
long lockTime = redisLock.lockTime();
TimeUnit timeUnit = redisLock.timeUnit();
RLock lock = redissonClient.getLock(key);
boolean flag;
if(lockTime!=0&&tryTime!=0) {
//尝试在tryTime时间内获取锁,锁在lockTime过后自动释放
flag= lock.tryLock(tryTime, lockTime,timeUnit);
}else if(tryTime!=0){
flag= lock.tryLock(tryTime,timeUnit);
}else{
flag= lock.tryLock();
}
if (!flag) {
return null;
}
try {
return joinPoint.proceed();
}catch (Exception e){
logger.error(joinPoint.getClass().getName()+": "+e.getMessage(),e);
} finally {
lock.unlock();
}
return null;
}
}
注解使用:
@RedisLock(tryTime = 1, lockTime = 120, key = "lock1")
public void test1() {}
@RedisLock(tryTime = 1, key = "lock2")
public void test2() {}