问题:定时任务在只部署一台服务器时没有问题,当需要集群时,就会重复执行多次。
解决方案:1. 利用数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。
这里我使用的是redis分布锁的方式实现,自己封装了一个注解,如有问题请联系我一下,谢谢!
加锁 :同一个定时任务同时多次给redis加锁(key),如果存在key,则加锁失败,如果不存在,则尝试去加锁,返回加锁结果。
解锁: 设置一下过期时间为20秒(可根据任务执行长短调整),过期后自动释放掉,当定时任务执行完后redis还没有过期是就手动解锁。
封装aop注解:
package com.ruoyi.config;
import java.lang.annotation.*;
/**
* ************************************************
* 功能描述: TODO
*
* @author shuangping.yang
* @version 1.0
* @ClassName RedisTryLock
* @date 2020.07.15 下午 03:53 创建文件
* @see ************************************************
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisTryLock {
/**
* 锁的有效时间长,单位:秒
*
* @return
*/
int expireTime() default 10;
/**
* 自定义锁的keyName(不用包含namespace,内部已实现)
*/
String keyName() default "";
}
核心代码实现:
package com.ruoyi.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.concurrent.TimeUnit;
/**
* @author shuangping.yang
*/
@Slf4j
@Aspect
@Component
public class RedisTryLockAspect {
@Autowired
private RedisTemplate redisTemplate;
InetAddress addr = null;
@Around("execution(* *.*(..)) && @annotation(com.ruoyi.config.RedisTryLock)")
public void redisTryLockPoint(ProceedingJoinPoint pjp) throws Exception {
String defKey = "redis:lock:";
RedisTryLock annotation = null;
Method method = null;
//获得所在切点的该类的class对象
Class<?> aClass = pjp.getTarget().getClass();
//获取该切点所在方法的名称,每一个使用切面的方法
String name = pjp.getSignature().getName();
try {
//通过反射获得该方法
method = aClass.getMethod(name);
//获得该注解
annotation = method.getAnnotation(RedisTryLock.class);
//获取注解对应的值keyName,expireTime
String keyName = annotation.keyName();
Integer expireTime = annotation.expireTime();
Assert.isTrue(0 != expireTime, "redis lock's expireTime is null");
//获取本机ip
try {
addr = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
log.error("获取IP失败--》", e);
}
String ip = addr.getHostAddress();
// 设置redis key值
defKey = StringUtils.isBlank(keyName) ? defKey + aClass.getDeclaringClass() + method.getName() : defKey + keyName;
//根据redis 锁的原理判断是否执行成功,设值成功说明其他服务器没有执行定时任务,反则正在执行
if (redisTemplate.opsForValue().setIfAbsent(defKey, ip)) {
redisTemplate.expire(defKey, expireTime, TimeUnit.SECONDS);
log.info("获得分布式锁成功! key:{}", defKey);
pjp.proceed();
redisTemplate.delete(defKey);
log.info("定时任务执行完,释放分布式锁成功,key:{}", defKey);
return;
}
Object redisVal = redisTemplate.opsForValue().get(defKey);
log.info("{}已在{}机器上占用分布式锁,聚类任务正在执行", defKey, redisVal);
} catch (NoSuchMethodException e) {
e.printStackTrace();
log.error("Facet aop failed error {}", e.getLocalizedMessage());
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
使用示例:
@Scheduled(cron = "0 0 0/1 * * ?")
@RedisTryLock(keyName = "repeat_flow_direction_file_task", expireTime = 180)
public void redisTask() {
//业务代码实现
}