分布式集群环境下使用SpringBoot定时任务保证只有一个定时任务在执行
原理是使用redis进行实现,当定时任务进行的时候,使用redisLock对类和方法名进行lock,这样的话其他的定时任务进不来。
注解SchedulerClusterLock
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Description: 分布式定时任务方法锁
* @author mazhao
* @date 2022年6月20日
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SchedulerClusterLock {
}
切面
import java.lang.reflect.Method;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.original.basic.common.annotion.SchedulerClusterLock;
import com.original.basic.core.constant.RedisConst;
/**
* @Description: 集群环境中使用定时任务的问题。通过在redis中加锁来控制同一时间段内仅允许一个定时任务执行。
* @author mazhao
* @date 2022年6月20日
*/
@Aspect
public class SchedulerClusterLockAspect {
/** The Constant logger. */
private static final Logger LOG = LoggerFactory.getLogger(SchedulerClusterLockAspect.class);
private RedissonClient redissonClient;
@Around("execution(public void com.original.basic.rest.modular.*.task.*.*())")
public Object lock(ProceedingJoinPoint pjp) throws Throwable {
// 获得所拦截的对象
Object target = pjp.getTarget();
// 获得所拦截的方法名
String methodName = pjp.getSignature().getName();
// 通过反射,获取无参的public方法对象
Method method = target.getClass().getMethod(methodName);
// 判断该方法签名上是否有@ClusterLock
if (!method.isAnnotationPresent(SchedulerClusterLock.class)) {
return pjp.proceed();
}
// build a lock key
String lockKey = RedisConst.LOCK_PREFIX + target.getClass().getName() + "." + methodName + "()";
// timer task lock for a cluster environment
RLock lock = redissonClient.getLock(lockKey);
//试用redis原子性SETNX来判断是否有锁 使用失效时间, 避免redis长时间内保留锁,造成定时任务无法执行
try {
if (lock.tryLock()) {
// LOG.info("execute method = [{}] start", method);
Object obj = pjp.proceed();
// LOG.info("execute method = [{}] end", method);
return obj;
} else {
LOG.warn("other thread is running, lockKey[{}].", lockKey);
return null;
}
} catch (Exception e) {
throw e;
} finally {
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
public void setRedissonClient(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
}
加入spring容器
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.original.basic.core.aop.DuplicateSubmitClusterLockAspect;
import com.original.basic.core.aop.SchedulerClusterLockAspect;
@Configuration
public class ClusterLockConfig {
@Autowired
private RedissonClient redissonClient;
@Bean
public SchedulerClusterLockAspect schedulerClusterLockAspect() {
SchedulerClusterLockAspect clusterLockAspect = new SchedulerClusterLockAspect();
clusterLockAspect.setRedissonClient(redissonClient);
return clusterLockAspect;
}
@Bean
public DuplicateSubmitClusterLockAspect duplicateSubmitClusterLockAspect() {
DuplicateSubmitClusterLockAspect clusterLockAspect = new DuplicateSubmitClusterLockAspect();
clusterLockAspect.setRedissonClient(redissonClient);
return clusterLockAspect;
}
}
使用注解@SchedulerClusterLock
@Transactional
@SchedulerClusterLock
@Scheduled(initialDelayString="${schedules.order.beanHandleInitialDelay}", fixedDelayString="${schedules.order.beanHandleFixedDelay}")
public void beanHandle() {
System.out.println("doing business");
}