分布式集群环境下使用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");
	}
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐