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 표현식의 순서는 아래와 같다.
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
'스프링부트' 카테고리의 다른 글
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 |
댓글