스프링부트

spring boot scheduler basic

알쓸개잡 2023. 10. 9.

Spring은 주기적인 작업을 자동으로 수행할 수 있도록 하는 scheduler를 제공하여 이를 간소화할 수 있다.

Spring framework는 메서드 scheduling에 대한 지원을 제공한다. @Scheduled annotation을 사용하여 메서드를 예약된 메서드로 손쉽게 변환할 수 있다.

@Scheduled annotation은 메서드 수준 annotation이며 Spring Bean 클래스의 메서드에 추가해야 한다.

@Scheduled annotation 매개 변수를 제공하여 고정된 주기로 실행할지 또는 특정 시간 및 날짜에 실행할지 지정할 수 있다.

이번 포스팅에서는 spring boot의 scheduler를 사용하는 방법과 properties에 대해서 기록한다.

 

spring boot schedule properties

spring boot에서는 schedule 작업에 대한 auto configuration을 제공한다. spring-boot-autoconfigure artifact의 org.springframework.boot.autoconfigure.task 패키지에 TaskSchedulingAutoConfiguration, TaskSchedulingProperties 클래스를 제공한다.

TaskSchedulingProperties 클래스에는 ConfigurationProperties("spring.task.scheduling") 주석이 달려있고 이를 통해서 scheduling 관련 설정은 spring.task.scheduling.* 인 것을 알 수 있다.

spring.task.scheduling.thread-name-prefix task executor의 thread name prefix (default: scheduling-)
spring.task.scheduling.pool.size scheduled task를 수행할 때 사용할 thread pool size (default 1)
spring.task.scheduling.shutdown.await-termination 애플리케이션 종료시 수행중인 scheduled task 완료 대기 여부
spring.task.scheduling.shutdown.await-termination-period 애플리케이션 종료시 수행중인 scheduled task 완료 대기 시간(밀리)

spring에서 scheduled task는 ThreadPoolTaskScheduler를 통해서 수행되므로 scheduled task 역할을 수행하는 메서드가 여러 개이고 같은 시간대 혹은 각 메서드 실행 시간이 중첩되어 실행될 가능성이 있는 경우에는 spring.task.scheduling.pool.size를 2 이상으로 지정해 주는 것이 좋다. 그렇지 않으면 실행이 누락되는 scheduled task 메서드가 발생할 수 있다.

 

application.yml 설정

spring:
  task:
    scheduling:
      pool:
        size: 5
      shutdown:
        await-termination: true
        await-termination-period: 60000
    thread-name-prefix: test-scheduling-

TaskSchedulerCustomizer를 통한 설정

application.yml 이 아닌 TaskSchedulerCustomizer를 통해서 코드적으로 설정을 할 수 있다.

@Bean
public TaskSchedulerCustomizer taskSchedulerCustomizer() {
    return threadPoolTaskScheduler -> {
        threadPoolTaskScheduler.setPoolSize(5);
        threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
        threadPoolTaskScheduler.setAwaitTerminationSeconds(300);
        threadPoolTaskScheduler.setThreadNamePrefix("test-scheduling-");
    };
}

 

TaskSchedulingAutoConfiguration

spring boot scheduler는 내부적으로 ThreadPoolTaskScheduler를 사용한다.

TaskSchedulingAutoConfiguration 클래스는 spring-boot-autoconfigure artifact의 org.springframework.boot.autoconfigure.task에 정의되어 있다.

TaskSchedulingAutoConfiguration 클래스를 통해서 스케줄러에서 사용할 ThreadPoolTaskScheduler를 Bean으로 등록한다.

@ConditionalOnClass(ThreadPoolTaskScheduler.class)
@AutoConfiguration(after = TaskExecutionAutoConfiguration.class)
@EnableConfigurationProperties(TaskSchedulingProperties.class)
public class TaskSchedulingAutoConfiguration {

	@Bean
	@ConditionalOnBean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
	@ConditionalOnMissingBean({ SchedulingConfigurer.class, TaskScheduler.class, ScheduledExecutorService.class })
	public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) {
		return builder.build();
	}

	@Bean
	@ConditionalOnBean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
	public static LazyInitializationExcludeFilter scheduledBeanLazyInitializationExcludeFilter() {
		return new ScheduledBeanLazyInitializationExcludeFilter();
	}

	@Bean
	@ConditionalOnMissingBean
	public TaskSchedulerBuilder taskSchedulerBuilder(TaskSchedulingProperties properties,
			ObjectProvider<TaskSchedulerCustomizer> taskSchedulerCustomizers) {
		TaskSchedulerBuilder builder = new TaskSchedulerBuilder();
		builder = builder.poolSize(properties.getPool().getSize());
		Shutdown shutdown = properties.getShutdown();
		builder = builder.awaitTermination(shutdown.isAwaitTermination());
		builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
		builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
		builder = builder.customizers(taskSchedulerCustomizers);
		return builder;
	}

}

taskSchedulerBuilder() 메서드에서 TaskSchedulingProperties에 주입된 설정들을 TaskSchedulerBuilder에 셋팅한다.

또한 TaskSchedulerCustomizer Bean이 있다면 TaskSchedulerBuilder에 세팅한다.

taskScheduler() 메서드에서 TaskSchedulerBuilder 클래스의 build()를 호출한다.

TaskSchedulerBuilder 클래스의 build() 코드는 아래와 같다.

/**
 * Build a new {@link ThreadPoolTaskScheduler} instance and configure it using this
 * builder.
 * @return a configured {@link ThreadPoolTaskScheduler} instance.
 * @see #configure(ThreadPoolTaskScheduler)
 */
public ThreadPoolTaskScheduler build() {
    return configure(new ThreadPoolTaskScheduler());
}

/**
 * Configure the provided {@link ThreadPoolTaskScheduler} instance using this builder.
 * @param <T> the type of task scheduler
 * @param taskScheduler the {@link ThreadPoolTaskScheduler} to configure
 * @return the task scheduler instance
 * @see #build()
 */
public <T extends ThreadPoolTaskScheduler> T configure(T taskScheduler) {
    PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
    map.from(this.poolSize).to(taskScheduler::setPoolSize);
    map.from(this.awaitTermination).to(taskScheduler::setWaitForTasksToCompleteOnShutdown);
    map.from(this.awaitTerminationPeriod).asInt(Duration::getSeconds).to(taskScheduler::setAwaitTerminationSeconds);
    map.from(this.threadNamePrefix).to(taskScheduler::setThreadNamePrefix);
    if (!CollectionUtils.isEmpty(this.customizers)) {
        this.customizers.forEach((customizer) -> customizer.customize(taskScheduler));
    }
    return taskScheduler;
}

configure() 메서드를 보면 taskSchedulerBuilder() 에서 설정했던 설정 정보들을 ThreadPoolTaskScheduler에 적용한다.

마지막으로 TaskSchedulerCustomizer의 customize()를 ThreadPoolTaskScheduler에 적용한다.

 

@EnableScheduling

기본적으로 Scheduled 처리는 비활성화되어 있다.

Spring 애플리케이션에 Scheduled 작업을 활성화하려면 @EnableScheduling annotation을 추가해야 한다.

@Configuration
@EnableScheduling
public class SchedulerConfig {...}

 

@Scheduled

@Scheduled annotation은 메서드 수준 annotation이며, Spring Bean 클래스의 메서드에 추가할 수 있다. @Scheduled annotation이 붙은 메서드는 @Scheduled의 속성 값을 통해서 지정된 시간 혹은 지정된 주기에 실행된다.

지정된 주기의 기본 시간 단위는 밀리초이다. 시간 단위는 timeUnit annotation 속성으로 변경할 수 있다.

 

Fixed Delay vs Fixed Rate

fixedDelay 또는 fixedRate 속성을 사용하여 실행 주기를 설정할 수 있다.

fixedDelay를 지정한 경우 다음 실행은 이전 실행이 완료된 후 지정된 시간 후에 시작된다.

fixedRate를 지정한 경우 다음 실행은 이전 실행이 시작된 후 지정된 시간 후에 시작된다.

실행이 서로 종속되어 있고 항상 첫 번째 실행이 완료된 후 일정 시간 이후에 다음 실행이 시작되기를 원할 때 fixedDelay를 사용한다.

실행 속도가 더 중요하고 각 실행이 완료되는 데 걸리는 시간과 무관한 경우 fixedRate를 사용한다.

 

@Scheduled(fixedDelay = 2, timeUnit = TimeUnit.SECONDS)
public void fixedDelaySchedule() throws InterruptedException {
    Thread.sleep(2000);
    log.info("Task executed with fixedDelay, " + (LocalTime.now().getSecond()));
}
[st-scheduling-1] c.e.s.s.service.SchedulerService         : Task executed with fixedDelay, 18
[st-scheduling-1] c.e.s.s.service.SchedulerService         : Task executed with fixedDelay, 22
[st-scheduling-1] c.e.s.s.service.SchedulerService         : Task executed with fixedDelay, 26
[st-scheduling-1] c.e.s.s.service.SchedulerService         : Task executed with fixedDelay, 30

fixedDelaySchedule() 메서드는 2초 후에 실행이 완료된 이후 2초 후에 다시 실행되므로 4초 간격으로 로그가 생성된다.

@Scheduled(fixedRate = 2, timeUnit = TimeUnit.SECONDS)
public void fixedRateSchedule() throws InterruptedException {
    Thread.sleep(2000);
    log.info("Task executed with fixedRate, " + (LocalTime.now().getSecond()));
}
[st-scheduling-1] c.e.s.s.service.SchedulerService         : Task executed with fixedRate, 41
[st-scheduling-1] c.e.s.s.service.SchedulerService         : Task executed with fixedRate, 43
[st-scheduling-1] c.e.s.s.service.SchedulerService         : Task executed with fixedRate, 45
[st-scheduling-1] c.e.s.s.service.SchedulerService         : Task executed with fixedRate, 47

fixedRateSchedule() 메서드가 시작 후 2초 후에 완료되고 fixedRate가 2초 이므로 종료 후 바로 다음 scheduled task가 실행되어 2초 간격으로 로그가 생성된다.

fixedRate인 경우에도 실행 시간이 지정된 시간을 초과한 경우 다음 실행을 수행하지 않고 다음 실행은 이전 실행이 완료된 후에 실행된다.

 

initialDelay

@Scheduled annotation은 initialDelay 속성을 제공하여 초기 실행 지연 시간을 설정할 수 있다.

@Scheduled(fixedRate = 3000, initialDelay = 10000)
public void taskWithFixedRateAndInitialDelay(){
    logger.info("Task with Fixed Rate and Initial Delay, " + (LocalTime.now().getSecond()));
}

initialDelay 속성은 fixedRate와 fixedDelay 이 지정된 scheduled task와 함께 사용할 수 있다.

initialDelay를 설정하면 지정된 밀리초가 경과할 때까지 메서드의 처음 실행이 지연된다. 

initialDelay는 후속 실행에 영향을 미치지 않는다.

 

cron expression

@Scheduled annotation은 cron 표현식을 지정할 수 있다.

cron 표현식을 사용하여 배치로 수행되는 작업에 유연한 설정을 할 수 있다.

@Scheduled(cron = "0 45 1,2,3 * * ?", zone = "Asia/Seoul")
public void taskWithCronExpression(){
    logger.info("Task with Cron Expression, " + (LocalTime.now().getSecond()));
}

zone 속성을 지정하지 않으면 시스템의 timezone이 적용된다.

cron 표현식의 순서는 아래와 같다.

출처 https://docs.spring.io/spring-framework/reference/integration/scheduling.html

cron 표현식의 자세한 내용은 spring 문서의 Cron Expressions 섹션을 참고하면 도움이 된다.

아래 표는 cron 표현식의 예시이다.

0 0 * * * * top of every hour of every day
*/10 * * * * * every ten seconds
0 0 8-10 * * * 8, 9 and 10 o’clock of every day
0 0 6,19 * * * 6:00 AM and 7:00 PM every day
0 0/30 8-10 * * * 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day
0 0 9-17 * * MON-FRI on the hour nine-to-five weekdays
0 0 0 25 DEC ? every Christmas Day at midnight
0 0 0 L * * last day of the month at midnight
0 0 0 L-3 * * third-to-last day of the month at midnight
0 0 0 * * 5L last Friday of the month at midnight
0 0 0 * * THUL last Thursday of the month at midnight
0 0 0 1W * * first weekday of the month at midnight
0 0 0 LW * * last weekday of the month at midnight
0 0 0 ? * 5#2 the second Friday in the month at midnight
0 0 0 ? * MON#1 the first Monday in the month at midnight

 

String 타입 속성

@Scheduled annotation의 속성에서 String 타입의 속성들은 SpEL 표현식뿐만 아니라 Spring 스타일의 "${...}" placeholder를 지원한다.

아래와 같은 속성이 있을 때

schedule:
  fixedDelay: 2000
  fixedRate: 2000
  initialDelay: 10000
  cron: 0 0 1 * * ?
  timezone: Asia/Seoul

다음과 같이 사용할 수 있다.

@Scheduled(fixedRateString = "${schedule.fixedRate}", initialDelayString = "${schedule.initialDelay}")
@Async
public void fixedRateSchedule() throws InterruptedException {
    Thread.sleep(2000);
    log.info("Task executed with fixedRate, " + (LocalTime.now().getSecond()));
}

@Scheduled(cron = "${schedule.cron}", zone = "${schedule.timezone}")
public void cronSchedule() {
    log.info("Task executed with cron scheduler");
}

 

@EnableAsync and @Async annotation

fixedRate인 경우 실행 시간이 지정된 시간을 초과한 경우 다음 실행을 실행되도록 하려면 @EnableAsync 및 @Async annotation을 사용하면 된다.

 

@Service
@Slf4j
public class SchedulerService {
	@Scheduled(fixedRate = 1, timeUnit = TimeUnit.SECONDS)
	public void fixedRateSchedule() throws InterruptedException {
		log.info("Task executed with fixedRate, " + (LocalTime.now().getSecond()));
		Thread.sleep(2000);
	}
}

위 코드의 실행 결과는 다음과 같다.

[st-scheduling-1] c.e.s.s.service.SchedulerService         : Task executed with fixedRate, 41
[st-scheduling-2] c.e.s.s.service.SchedulerService         : Task executed with fixedRate, 43
[st-scheduling-1] c.e.s.s.service.SchedulerService         : Task executed with fixedRate, 45
[st-scheduling-3] c.e.s.s.service.SchedulerService         : Task executed with fixedRate, 47

fixedRateSchedule()의 실행은 2초 후에 완료되지만 fixedRate은 1초로 지정되었다. 실행 시간이 fixedRate 시간을 초과하였지만 실행이 완료 후 다음 실행이 동작하므로 2초 간격으로 로그가 생성된다.

 

@EnableAsync와 @Async가 적용된 결과는 다음과 같다.

spring:
  task:
    execution:
      pool:
        core-size: 5
        queue-capacity: 5
        max-size: 10
      shutdown:
        await-termination: true
        await-termination-period: 60000
@Service
@Slf4j
@EnableAsync
public class SchedulerService {
	@Scheduled(fixedRate = 1, timeUnit = TimeUnit.SECONDS)
	@Async
	public void fixedRateSchedule() throws InterruptedException {
		log.info("Task executed with fixedRate, " + (LocalTime.now().getSecond()));
		Thread.sleep(2000);
	}
}
[         task-1] c.e.s.s.service.SchedulerService         : Task executed with fixedRate, 20
[         task-2] c.e.s.s.service.SchedulerService         : Task executed with fixedRate, 21
[         task-3] c.e.s.s.service.SchedulerService         : Task executed with fixedRate, 22
[         task-4] c.e.s.s.service.SchedulerService         : Task executed with fixedRate, 23
[         task-5] c.e.s.s.service.SchedulerService         : Task executed with fixedRate, 24

@EnableAsync와 @Async가 적용되는 경우에는 task의 실행이 ThreadPoolTaskExecutor에 의해서 실행된다.

ThreadPoolTaskExecutor는 spring-boot-autoconfigure artifact의 org.springframework.boot.autoconfigure.task 패키지에 포함된 TaskExecutionAutoConfiguration 클래스에 의해서 Bean으로 등록된다.

ThreadPoolTaskExecutor Bean에 적용되는 properties는 TaskExecutionProperties 클래스에 주입된다.

TaskExecutionProperties는 spring.task.execution.* 의 property들을 주입받는다.

spring.task.execution.pool.queue-capacity queue capacity (default: INT_MAX)
spring.task.execution.pool.core-size thread core size (default: 8)
spring.task.execution.pool.max-size thread max size (default: INT_MAX)
spring.task.execution.pool.allow-core-thread-timeout core thread timeout 허용 여부 (default: true)
spring.task.execution.pool.keep-alive thread가 종료되기 전 유휴상태 시간 (default: 60sec)
spring.task.execution.shutdown.await-termination 애플리케이션 종료시 수행중인 scheduled task 완료 대기 여부
spring.task.execution.shutdown.await-termination-period 애플리케이션 종료시 수행중인 scheduled task 완료 대기 시간 (밀리)

 

직접 스케줄러 생성하기

@Scheduler 어노테이션 없이 직접 ThreadPoolTaskExecutor를 생성하고 설정할 수 있다.

다음은 ThreadPoolTaskScheduler를 직접 생성하고 설정하여 작업을 주기적으로 실행하는 예제이다.

@Slf4j
@Service
public class DirectSchedulerService {
    private final ThreadPoolTaskScheduler threadPoolTaskScheduler;

    public DirectSchedulerService() {
        threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.setPoolSize(10);
        threadPoolTaskScheduler.setThreadNamePrefix( "direct-scheduler-" );
        threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown( true );
        threadPoolTaskScheduler.initialize();

        //CronTrigger를 통해서 cron 형식으로 스케줄러를 실행할 수 있다. 
        //CronTrigger trigger = new CronTrigger( "*/10 * * * * *" );
        Duration duration = Duration.ofSeconds( 10 );
        PeriodicTrigger trigger = new PeriodicTrigger( duration );
        //10초 뒤에 scheduler가 실행된다.
        //지정하지 않으면 바로 scheduler가 실행되고 10초 주기로 동작한다.
        trigger.setInitialDelay( duration );
        trigger.setFixedRate( true );

        threadPoolTaskScheduler.schedule( () -> {
            log.info( "execute scheduler" );
        }, trigger );
    }

    @PreDestroy
    void destroy() {
        // 애플리케이션 종료시 shutdown()은 자동 수행되나 명시적으로 구현함.
        threadPoolTaskScheduler.shutdown();
    }
}

 


참고링크

https://docs.spring.io/spring-framework/reference/integration/scheduling.html

 

Task Execution and Scheduling :: Spring Framework

All Spring cron expressions have to conform to the same format, whether you are using them in @Scheduled annotations, task:scheduled-tasks elements, or someplace else. A well-formed cron expression, such as * * * * * *, consists of six space-separated time

docs.spring.io

 

'스프링부트' 카테고리의 다른 글

spring boot logging  (0) 2023.10.12
STOMP와 ACTIVEMQ를 이용한 메시지 broadcasting  (0) 2023.10.11
spring boot kafka 연동하기  (0) 2023.10.08
spring bean scope  (0) 2023.10.03
application events and listeners  (0) 2023.10.02

댓글

💲 추천 글