티스토리 뷰

spring

Spring Scheduler 동작 방식

songjb 2024. 6. 19. 22:00

현재 서버에서 Scheduler를 사용하는 중이고 Scheduler가 어떻게 동작하는지 알아 볼려고합니다.


단일 스레드에서 스케줄링 작업들을 순차적으로 처리

@Configuration
public class ScheduledTasks {
    private final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);

    @Scheduled(fixedRate = 1000) // 1초마다 taskA를 실행합니다.
    public void taskA() throws InterruptedException {
        Thread.sleep(10000); // 10초 동안 일시 중단합니다.
        log.info("taskA - {} - {}", LocalDateTime.now(), Thread.currentThread().getName());
    }

    @Scheduled(fixedRate = 1000) // 1초마다 taskB를 실행합니다.
    public void taskB() {
        // taskB의 로직을 실행합니다.
        log.info("taskB - {} - {}", LocalDateTime.now(), Thread.currentThread().getName());
    }
}
[   scheduling-1] o.e.s.service.ScheduledTasks             : taskA - 2024-06-19T20:30:28.489853 - scheduling-1
[   scheduling-1] o.e.s.service.ScheduledTasks             : taskB - 2024-06-19T20:30:28.500651 - scheduling-1
[   scheduling-1] o.e.s.service.ScheduledTasks             : taskA - 2024-06-19T20:30:38.506476 - scheduling-1
[   scheduling-1] o.e.s.service.ScheduledTasks             : taskB - 2024-06-19T20:30:38.509343 - scheduling-1
[   scheduling-1] o.e.s.service.ScheduledTasks             : taskA - 2024-06-19T20:30:48.511443 - scheduling-1
[   scheduling-1] o.e.s.service.ScheduledTasks             : taskB - 2024-06-19T20:30:48.513789 - scheduling-1
[   scheduling-1] o.e.s.service.ScheduledTasks             : taskA - 2024-06-19T20:30:58.519031 - scheduling-1
[   scheduling-1] o.e.s.service.ScheduledTasks             : taskB - 2024-06-19T20:30:58.521203 - scheduling-1

 

@Scheduled 어노테이션의 기본 설정이 단일 스레드에서 작업을 수행되기 때문에 taskA와 taskB는 순차적으로 동작하게 됩니다.

 

public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport implements AsyncListenableTaskExecutor, SchedulingTaskExecutor, TaskScheduler {
    private volatile int poolSize = 1;
    
    ...
}

 

 

ThreadPoolTaskScheduler에서 기본으로 설정된 poolSize를 보면 1개로 설정되어있기때문에 스케줄링 작업이 단일 스레드에서 동작하게됩니다.

위와 같이 단일 스레드에서 동작하는 문제를 3가지 방법으로 해결할 수 있습니다.


SchedulingConfigurer 

@Configuration
public class SchedulerConfig implements SchedulingConfigurer {
    
    @Override
    public void configureTasks(final ScheduledTaskRegistrar taskRegistrar) {
        ThreadPoolTaskScheduler threadPool = new ThreadPoolTaskScheduler();

        // Thread 개수 설정
        int n = Runtime.getRuntime().availableProcessors();
        threadPool.setPoolSize(n);
        threadPool.initialize();

        taskRegistrar.setTaskScheduler(threadPool);
    }
}
[TaskScheduler-6] o.e.s.service.ScheduledTasks  : taskB - 2024-06-19T21:14:00.349843 - ThreadPoolTaskScheduler-6
[TaskScheduler-1] o.e.s.service.ScheduledTasks  : taskA - 2024-06-19T21:14:00.357643 - ThreadPoolTaskScheduler-1
[TaskScheduler-5] o.e.s.service.ScheduledTasks  : taskB - 2024-06-19T21:14:01.349502 - ThreadPoolTaskScheduler-5
[TaskScheduler-5] o.e.s.service.ScheduledTasks  : taskB - 2024-06-19T21:14:02.353956 - ThreadPoolTaskScheduler-5
[TaskScheduler-5] o.e.s.service.ScheduledTasks  : taskB - 2024-06-19T21:14:03.351233 - ThreadPoolTaskScheduler-5
[TaskScheduler-5] o.e.s.service.ScheduledTasks  : taskB - 2024-06-19T21:14:04.353376 - ThreadPoolTaskScheduler-5
[TaskScheduler-5] o.e.s.service.ScheduledTasks  : taskB - 2024-06-19T21:14:05.354604 - ThreadPoolTaskScheduler-5
[TaskScheduler-5] o.e.s.service.ScheduledTasks  : taskB - 2024-06-19T21:14:06.352792 - ThreadPoolTaskScheduler-5
[TaskScheduler-5] o.e.s.service.ScheduledTasks  : taskB - 2024-06-19T21:14:07.352888 - ThreadPoolTaskScheduler-5
[TaskScheduler-5] o.e.s.service.ScheduledTasks  : taskB - 2024-06-19T21:14:08.349221 - ThreadPoolTaskScheduler-5
[TaskScheduler-5] o.e.s.service.ScheduledTasks  : taskB - 2024-06-19T21:14:09.353064 - ThreadPoolTaskScheduler-5
[TaskScheduler-7] o.e.s.service.ScheduledTasks  : taskB - 2024-06-19T21:14:10.353478 - ThreadPoolTaskScheduler-7
[TaskScheduler-1] o.e.s.service.ScheduledTasks  : taskA - 2024-06-19T21:14:10.363399 - ThreadPoolTaskScheduler-1
[TaskScheduler-7] o.e.s.service.ScheduledTasks  : taskB - 2024-06-19T21:14:11.351885 - ThreadPoolTaskScheduler-7

이전에 확인했던 ThreadPoolTaskScheduler poolSize를 수정해주면 taskA와 taskB가 각각 별도의 스레드에서 처리할 수 있습니다.


 

TaskScheduler

@Configuration
public class SchedulerConfig {

    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();

        int n = Runtime.getRuntime().availableProcessors();
        scheduler.setPoolSize(n);
        scheduler.initialize();

        return scheduler;
}

TaskScheduler를 스프링 빈으로 등록하는 방식 또한 SchedulingConfigurer와 동일하게 동작합니다.

이 방식은 동일한 작업에 대해서는 순차적으로 실행되고, 다른 작업에 대해서는 병렬적으로 실행됩니다.


 

Async

@EnableAsync
@Configuration
public class ScheduledTasks {
    private final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);

    @Async
    @Scheduled(fixedRate = 1000) // 1초마다 taskA를 실행합니다.
    public void taskA() throws InterruptedException {
        Thread.sleep(10000); // 10초 동안 일시 중단합니다.
        log.info("taskA - {} - {}", LocalDateTime.now(), Thread.currentThread().getName());
    }

    @Async
    @Scheduled(fixedRate = 1000) // 1초마다 taskB를 실행합니다.
    public void taskB() {
        // taskB의 로직을 실행합니다.
        log.info("taskB - {} - {}", LocalDateTime.now(), Thread.currentThread().getName());
    }
}
[         task-8] o.e.s.service.ScheduledTasks             : taskA - 2024-06-19T22:13:46.341660 - task-8
[         task-8] o.e.s.service.ScheduledTasks             : taskB - 2024-06-19T22:13:46.342379 - task-8
[         task-1] o.e.s.service.ScheduledTasks             : taskA - 2024-06-19T22:13:49.377070 - task-1
[         task-1] o.e.s.service.ScheduledTasks             : taskB - 2024-06-19T22:13:49.377382 - task-1
[         task-3] o.e.s.service.ScheduledTasks             : taskA - 2024-06-19T22:13:50.341048 - task-3
[         task-3] o.e.s.service.ScheduledTasks             : taskB - 2024-06-19T22:13:50.341388 - task-3

@Async 어노테이션을 사용하면 스케줄러가 별도의 스레드에서 비동기적으로 처리 됩니다.

또한 이전 taskA와 달리 1초마다 새로운 스레드에서 독립적으로 수행되는 것을 확인할 수 있습니다.

이렇게 비동기로 수행하면 동일한 작업에 대해서 별도의 스레드에서 독립적으로 작업을 수행하므로, 한 작업의 지연이 다른 작업에 영향을 미치는 것을 방지할 수 있습니다.

 

@Async의 기본 설정인 SimpleAsyncTaskExecutor는 단순히 새로운 스레드를 생성하는 역할만 하므로, 스레드 풀을 사용하지 않으며 스레드를 직접 관리하지 않습니다. 따라서 별도로 TaskExecutor를 설정하여 사용하는 것이 좋습니다.


https://dkswnkk.tistory.com/728

https://cobinding.tistory.com/210