[java] Spring Task Scheduler 가이드

1. intro

  • Spring task scheduling 메커니즘을 살짝 알아보자
  • Spring의 스케줄링에 대해 조금더 알고 싶으면 아래를 더 보자
    • https://www.baeldung.com/spring-async
    • https://www.baeldung.com/spring-scheduled-tasks

2. ThreadPoolTaskScheduler

  • ThreadPoolTaskScheduler 는 작업을 ScheduledExecutorService에 위임 하고 TaskExecutor 인터페이스를 구현하므로 내부 스레드 관리에 적합하다고하는데..
  • 아래의 예시를 보자 (ThreadPoolTaskSchedulerConfig 에서 ThreadPoolTaskScheduler 빈을 정의 한다.)
@Configuration
@ComponentScan(
  basePackages="com.baeldung.taskscheduler",
  basePackageClasses={ThreadPoolTaskSchedulerExamples.class})
public class ThreadPoolTaskSchedulerConfig {

    @Bean
    public ThreadPoolTaskScheduler threadPoolTaskScheduler(){
        ThreadPoolTaskScheduler threadPoolTaskScheduler
          = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.setPoolSize(5);
        threadPoolTaskScheduler.setThreadNamePrefix(
          "ThreadPoolTaskScheduler");
        return threadPoolTaskScheduler;
    }
}
  • pool size 5를 기반으로 구성된 bean threadPoolTaskScheduler는 비동기 작업을 실행할수있다
  • 모든 ThreadPoolTaskScheduler 관련 스레드 이름에는 ThreadPoolTaskScheduler 접두사가 붙도록 했다.
  • 이제, 예약할수 있는 간단한 작업을 구현해 보자
class RunnableTask implements Runnable{
    private String message;
    
    public RunnableTask(String message){
        this.message = message;
    }
    
    @Override
    public void run() {
        System.out.println(new Date()+" Runnable Task with "+message
          +" on thread "+Thread.currentThread().getName());
    }
}
  • 이제 이 작업이 스케줄러에 의해 실행되도록 아래처럼 간단하게 예약할 수 있다.
taskScheduler.schedule(
  new Runnabletask("Specific time, 3 Seconds from now"),
  new Date(System.currentTimeMillis + 3000)
);
  • taskScheduler는 정확히 현재 시간으로부터 3초후에 작업을 시작하도록 스케줄이 돌도록 하였다.

이제 ThreadPoolTaskScheduler 스케줄링 메커니즘 에 대해 좀 더 자세히 알아보자

3. Schedule Runnable Task With Fixed Delay

  • 고정 지연 일정은 두 가지 간단한 메커니즘으로 수행할 수 있다.

3.1. Scheduling After a Fixed Delay of the Last Scheduled Execution

  • 1000밀리초의 고정 지연 후에 실행되도록 작업을 구성해 보자
taskScheduler.scheduleWithFixedDelay(
  new RunnableTask("Fixed 1 second Delay"), 1000);
  • RunnableTask는 이전 작업을 완료하고 1000 milliseconds 이후에 실행된다.

3.2. Scheduling After a Fixed Delay of a Specific Date

  • 지정된 시작 시간의 고정 지연 후에 실행되도록 작업을 구성해 보겠습니다.
taskScheduler.scheduleWithFixedDelay(
  new RunnableTask("Current Date Fixed 1 second Delay"),
  new Date(),
  1000);
  • @PostConstruct 가 시작하고 이어서 1000 밀리 초 지연 후 RunnableTask가 실행된다.

4. Scheduling at a Fixed Rate

  • 고정된 주기로 실행 가능한 작업을 예약하기 위한 두 가지 간단한 메커니즘이 있습니다

4.1. Scheduling the RunnableTask at a Fixed Rate

  • 고정된 밀리초 속도 로 실행되도록 작업을 예약해 보겠습니다 .
taskScheduler.scheduleAtFixedRate(
  new RunnableTask("Fixed Rate of 2 seconds") , 2000);
  • 이전 RunnableTask 가 실행중이던지 말던지 관계없이 항상 2000밀리초 후에 RunnableTask가 실행됩니다.

4.2. Scheduling the RunnableTask at a Fixed Rate From a Given Date

taskScheduler.scheduleAtFixedRate(new RunnableTask(
  "Fixed Rate of 2 seconds"), new Date(), 3000);
  • RunnableTask는 현재 시간 이후에 3000 밀리 초 뒤에 를 실행 작업을 실행한다

5. Scheduling with CronTrigger

  • CronTrigger 는 cron 표현식을 기반으로 작업을 예약하는 데 사용된다. 아래 예제를 보자.
CronTrigger cronTrigger 
  = new CronTrigger("10 * * * * ?");
  • 제공된 트리거를 사용하여 지정된 특정 주기 또는 일정에 따라 작업을 실행할 수 있습니다.
taskScheduler.schedule(new RunnableTask("Cron Trigger"), cronTrigger);
  • 이 경우 RunnableTask 는 매분 10초에 실행됩니다.

6. Scheduling with PeriodicTrigger

  • 2000밀리초의 고정 지연 으로 작업을 예약하기 위해 PeriodicTrigger 를 사용하겠습니다 .
PeriodicTrigger periodicTrigger 
  = new PeriodicTrigger(2000, TimeUnit.MICROSECONDS);
  • 구성된 PeriodicTrigger 빈은 2000밀리초의 고정 지연 후에 작업을 실행하는 데 사용됩니다.
  • 이제 PeriodicTrigger를 사용 하여 RunnableTask 를 예약해 보겠습니다 .
taskScheduler.schedule(
  new RunnableTask("Periodic Trigger"), periodicTrigger);
  • 또한 PeriodicTrigger 가 고정 지연이 아닌 고정 속도로 초기화되도록 구성 할 수 있으며 첫 번째 예약된 작업에 대해 주어진 밀리초 단위로 초기 지연을 설정할 수도 있습니다.

  • 우리가 해야 할 일은 periodTrigger 빈 에서 return 문 앞에 두 줄의 코드를 추가하는 것입니다 .

periodicTrigger.setFixedRate(true);
periodicTrigger.setInitialDelay(1000);
  • setFixedRate 메서드를 사용하여 고정 지연이 아닌 고정 속도로 작업을 예약한 다음 setInitialDelay 메서드를 사용하여 실행할 첫 번째 실행 가능한 작업에 대해서만 초기 지연을 설정합니다.

7. 실전 예제

7.1

@Data
@Component
@ConfigurationProperties(value="scheduler-pool")
public class SchedulerPoolProperties {
	
	private int poolSize = 5;
	
	private boolean waitForTasksToCompleteOnShutdown = false;
	
	private int awaitTerminationSeconds = 0;
}
@RequiredArgsConstructor
@Configuration
@EnableScheduling
public class SchedulerPoolConfiguration implements SchedulingConfigurer {

	private final SchedulerPoolProperties schedulerPoolProperties;
	
	@Bean
	public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
		ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
		threadPoolTaskScheduler.setPoolSize( schedulerPoolProperties.getPoolSize() );
		threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown( true );
		threadPoolTaskScheduler.setAwaitTerminationSeconds( schedulerPoolProperties.getAwaitTerminationSeconds() );
		threadPoolTaskScheduler.setDaemon(true);
		threadPoolTaskScheduler.setThreadNamePrefix( "test-scheduler-" );
		
		return threadPoolTaskScheduler;
	}
	
	@Override
	public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
		taskRegistrar.setTaskScheduler(threadPoolTaskScheduler());
	}
}

7.2

@Data
@Component
@ConfigurationProperties(value="async-pool")
public class AsyncPoolProperties {
	
	private int corePoolSize = 16;
	private int maxPoolSize = 200;
	private int keepAliveSeconds = 60;
	private int queueCapacity = 1000;
	private int awaitTerminationSeconds = 10;
}


// http://www.baeldung.com/spring-async
@RequiredArgsConstructor
@Configuration
@EnableAsync
public class AsyncPoolConfiguration implements AsyncConfigurer {

	private final AsyncPoolProperties asyncPoolProperties;

	@Override
    public Executor getAsyncExecutor() {
	    
        return asyncThreadPoolTaskExecutor();
    }

    /**
     * core 개수가 차면, queue 에 넣고
     * queue 가 다 차면 maxQueue 사이즈만큼 늘린다.
     *
     * @return
     */
	@Bean
    public ThreadPoolTaskExecutor asyncThreadPoolTaskExecutor() {
	    ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize( asyncPoolProperties.getCorePoolSize() ); // 실행할 최소 Thread 수.
        taskExecutor.setMaxPoolSize( asyncPoolProperties.getMaxPoolSize() ); // 최대 Thread 지원수
        taskExecutor.setQueueCapacity( asyncPoolProperties.getQueueCapacity() );
        taskExecutor.setKeepAliveSeconds( asyncPoolProperties.getKeepAliveSeconds() ); // 현재 풀에 corePoolSize 의 수보다 많은 thread가 있는 경우, 초과한 만큼의 thread는, IDLE 상태가 되어 있는 기간이 keepAliveTime 를 넘으면(자) 종료
        taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        taskExecutor.setAwaitTerminationSeconds( asyncPoolProperties.getAwaitTerminationSeconds() );
        taskExecutor.setDaemon(true);
        taskExecutor.setThreadNamePrefix( "test-async-" );
        taskExecutor.initialize();
        
        return taskExecutor;
    }
    
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    	
        return new SimpleAsyncUncaughtExceptionHandler();
    }
}

참조

  • https://www.baeldung.com/spring-task-scheduler
  • https://www.baeldung.com/spring-async
  • https://www.baeldung.com/spring-scheduled-tasks