SpringBoot定时调度Scheduled默认配置(单线程)导致的业务延迟

4 篇文章 0 订阅
4 篇文章 0 订阅

项目后台组件运用了Schedule每分钟启动一个job把数据发送到kafka(生产者),通过kafka的负载均衡分发到消费者中。在某个夜黑风高的夜晚,运维GG通过监控发现kafka写入出现每分钟不连续的现象,在没有数据写入的时段,消费线程一直处于等待状态。由于除了生产者任务job之外,还在存在其他定时job,随着业务的发展,其他job的数据量上升之后,加上Schedule调度使用了默认的配置,在一个分钟内处理不完所有的定时job,导致下一分钟的job任务得到不到执行,从而导致了业务延迟。

通过下面的例子来进行模拟,创建两个一分钟执行一次的定时任务,一个任务能够在一分钟内正常完成,另一个在一分钟内完成不了。

能够正常完成的job,开始执行输出RunJob is running,然后一个for循环,模拟业务跑一会,然后输出RunJob is end,结束。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class RunJob {

    private static final Logger logger = LoggerFactory.getLogger(RunJob.class);

    @Scheduled(cron = "0 0/1 * * * ?")
    public void run() {
        logger.info("RunJob is running");
        for(int i=0; i<100;i++) {

        }
        logger.info("RunJob is end");
    }
}

一分钟内不能执行结束的job,首先输出PendingJob is running,然后start=当前时间的毫秒数+两分钟的毫秒数,相当于让时间往前推两分钟,进入一个循环,当时间走到两分钟后,才能跳出循环,job才能够结束。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;


@Component
public class PendingJob {

    private static final Logger logger = LoggerFactory.getLogger(PendingJob.class);

    @Scheduled(cron = "0 0/1 * * * ?")
    public void run() {
        logger.info("PendingJob is running");
        long start = System.currentTimeMillis() + (2 * 60 * 1000);
        while (true) {
            long end = System.currentTimeMillis();
            if(end >= start) {
                break;
            }
        }
        logger.info("PendingJob is end");
    }
}

执行结果如下截图,可知:PendingJob先执行的话,只要等两分钟后PendingJob执行完了,RunJob才能够执行。在56分PendingJob开始执行,58分PendingJob才执行结束,这段时间内RunJob并没有得到执行,58分RunJob才得到执行,当下一次PendingJob执行的时候又出现了一样的现象。可知在Schedule默认设置下,是单线程阻塞执行地去执行任务,当一个执行时间较长的任务获得执行调度之后,在它没有被执行完成之前,其他job定时任务并不能得到执行。

为什么默认的Schedule是单线程执行的呢?我们在使用定时调度的时候会在入口类加入@EnableScheduling注解,如下

@SpringBootApplication
@EnableScheduling
public class ShowApplication {

	public static void main(String[] args) {
		SpringApplication.run(ShowApplication.class, args);
	}

}

进入@EnableScheduling注解如下

/*
 * @author Chris Beams
 * @author Juergen Hoeller
 * @since 3.1
 * @see Scheduled
 * @see SchedulingConfiguration
 * @see SchedulingConfigurer
 * @see ScheduledTaskRegistrar
 * @see Trigger
 * @see ScheduledAnnotationBeanPostProcessor
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {

}

然后进入ScheduledAnnotationBeanPostProcessor,维护着一个任务注册器ScheduledTaskRegistrar属性

public class ScheduledAnnotationBeanPostProcessor
		implements ScheduledTaskHolder, MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor,
		Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware,
		SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {

	private final ScheduledTaskRegistrar registrar;

	public ScheduledAnnotationBeanPostProcessor() {
		this.registrar = new ScheduledTaskRegistrar();
	}
}

当spring完成对该bean的加载之后,会调用registrar.afterPropertiesSet()来配置调度线程池的相关信息,如下:

public class ScheduledAnnotationBeanPostProcessor
		implements ScheduledTaskHolder, MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor,
		Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware,
		SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {

	private final ScheduledTaskRegistrar registrar;

	public ScheduledAnnotationBeanPostProcessor() {
		this.registrar = new ScheduledTaskRegistrar();
	}

    @Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		if (event.getApplicationContext() == this.applicationContext) {
			// Running in an ApplicationContext -> register tasks this late...
			// giving other ContextRefreshedEvent listeners a chance to perform
			// their work at the same time (e.g. Spring Batch's job registration).
			finishRegistration();
		}
	}

	private void finishRegistration() {
		// 省略

		this.registrar.afterPropertiesSet();
	}
}
    

在ScheduledTaskRegistrar中维护着ScheduledExecutorService线程池,默认的情况下是没有设置TaskScheduler的,所以该属性为null,那么就会执行this.localExecutor = Executors.newSingleThreadScheduledExecutor(); this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor); 

newSingleThreadScheduledExecutor()里创建了一个核心线程数大小为1的线程池。

public class ScheduledTaskRegistrar implements ScheduledTaskHolder, InitializingBean, DisposableBean {

	
	@Nullable
	private TaskScheduler taskScheduler;

	@Nullable
	private ScheduledExecutorService localExecutor;

	@Override
	public void afterPropertiesSet() {
		scheduleTasks();
	}

	/**
	 * Schedule all registered tasks against the underlying
	 * {@linkplain #setTaskScheduler(TaskScheduler) task scheduler}.
	 */
	@SuppressWarnings("deprecation")
	protected void scheduleTasks() {
		if (this.taskScheduler == null) {
			this.localExecutor = Executors.newSingleThreadScheduledExecutor();
			this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
		}
		if (this.triggerTasks != null) {
			for (TriggerTask task : this.triggerTasks) {
				addScheduledTask(scheduleTriggerTask(task));
			}
		}
		if (this.cronTasks != null) {
			for (CronTask task : this.cronTasks) {
				addScheduledTask(scheduleCronTask(task));
			}
		}
		if (this.fixedRateTasks != null) {
			for (IntervalTask task : this.fixedRateTasks) {
				addScheduledTask(scheduleFixedRateTask(task));
			}
		}
		if (this.fixedDelayTasks != null) {
			for (IntervalTask task : this.fixedDelayTasks) {
				addScheduledTask(scheduleFixedDelayTask(task));
			}
		}
	}
}

知道原因之后,我们可以通过代码手动配置ScheduledTaskRegistrar的TaskScheduler

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

import java.util.concurrent.Executors;

@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        scheduledTaskRegistrar.setScheduler(Executors.newScheduledThreadPool(2));
    }
}

设置之后,再次执行,结果如下,当PendingJob没有执行完成,RunJob也能得到调度执行。

 

 

 

 

 

 

  • 9
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值