- spring boot scheduler basic2023년 10월 09일
- 알쓸개잡
- 작성자
- 2023.10.09.오후10:05
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 다음글이전글이전 글이 없습니다.댓글