티스토리 뷰
현재 서버에서 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를 설정하여 사용하는 것이 좋습니다.
'spring' 카테고리의 다른 글
Java 시간 다루기 (1) | 2024.06.19 |
---|---|
어댑터 패턴을 활용기 (0) | 2024.06.16 |
ShedLock 도입기 (0) | 2024.04.11 |
Spring Boot 로그에 바인딩 매개변수가 표시되지 문제 해결 (0) | 2023.10.24 |
FeignClient @FormProperty 트러블 슈팅 (0) | 2023.10.15 |
- Total
- Today
- Yesterday
- 구글 소셜로그인
- ServletContainerInitializer
- BasicBinder
- org.springframework:spring-webflux
- ValidateException
- DispatcherServletInitializer
- CreationTimestamp
- java 17
- @FormProperty
- JPA SQL Injection
- HTTPInterface
- 유저 시나리오
- defer-datasource-initialization
- dto 검증
- feignClient
- FormProperty
- 레이어드 아키텍처
- 유저 스토리
- User Scenario
- setDateFormat
- entity 검증
- dto 위치
- @Converter
- 구글 OpenID
- Attribute Converter
- Spring Boot 3
- WebFlux 의존성
- CreatedDate
- @ElementCollection
- HandlesTypes
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |