Async와 ThreadPoolTaskExecutor
@Async
개발을 하다보면 비동기로 처리하고 싶은 기능이 있다.
아래 task 를 수행한다면 10초의 지연시간이 발생한다.
public void task() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
task를 Thread 로 쪼개서 실행한다면 더 짧은 시간내에 수행할 수 있을 것이고
혹은 비동기 처리로 돌린다면 사용자는 10초의 지연시간을 기다리지 않고 다른 작업을 진행할 수 있다.
Spring에서 이러한 비동기 작업을 간단하게 사용할 수 있도록 @Async 라는 어노테이션을 제공한다.
@Async 어노테이션을 적용한 비동기 작업
@SpringBootApplication
@EnableAsync
@Slf4j
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@Async
public void task() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Application 에 @EnableAsync 어노테이션을 추가하고,
원하는 함수에 @Async 어노테이션을 붙여주는 것 만으로 비동기 작업을 처리할 수 있다.
@Async는 기본적으로 Spring에 내장된 ThreadPool 을 사용하여 비동기 작업을 처리한다.
별도의 configuration 없이도 기능에 문제가 없으나, 각 서버의 스펙에 맞는 ThreadPool을 설정해주어야 무분별한 리소스 사용을 막을 수 있다.
ThreadPoolTaskExecutor 빈 설정하기
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(5);
taskExecutor.setMaxPoolSize(30);
taskExecutor.setQueueCapacity(10);
taskExecutor.setThreadNamePrefix("Executor-");
taskExecutor.initialize();
return taskExecutor;
}
}
corePoolSize | 기본 thread 수 |
maxPoolSize | 최대 thread의 수 |
queueCapacity | thread-pool에서 사용할 최대 queue의 크기 |
threadNamePrefix | thread의 이름의 prefix |
Spring 의 ThreadPool 은 corePoolSize 만큼의 스레드를 동작하다가 스레드가 초과될 경우,
maxPoolSize 만큼 바로 스레드가 증가하는 것이 아닌, queueCapacity에 설정한 수만큼 큐에서 대기 상태가 된다.
여기서 큐에 대기하는 스레드가 큐의 사이즈를 넘어갈 경우, 그때 maxPoolSize 까지 스레드가 늘어나게 되는 구조이다.
Graecfully Shutdown
만약 위와 같은 비동기 처리가 진행 중인 서버가 shut down 이 된다면 스레드는 어떻게 될까 ?
별 다른 처리가 없다면 진행 중인 요청은 stop 될 것 이고, 서버가 재가동 되더라도 해당 요청은 다시 실행되지 않는다.
사용자 입장에서는 요청이 멈추었는지 확인할 수 없고 무한정 기다릴 수도 있다.
위와 같은 상황을 방지하기 위해 spring 에서 제공하는 몇몇 기능을 사용할 수 있다.
1. ContextClosedEvent
springboot 에서 제공하는 EventListener 구현을 통해서 CloseEvent를 확인 후 처리 작업을 구현할 수 있다.
비동기 작업을 기다리는 코드를 추가하거나 예제처럼 기다리지 않고 종료 후, 비정상으로 종료된 작업에 대한 처리를 진행할 수 있다.
@Component
@Slf4j
public class CloseHandler implements ApplicationListener<ContextClosedEvent> {
@Autowired ThreadPoolTaskExecutor executor;
@Override
public void onApplicationEvent(ContextClosedEvent event) {
executor.shutdown();
log.info("후 처리 작업");
}
}
3. @PostConstruct, @PreDestroy 어노테이션
@PostConstruct : Application 시작 전 실행하는 코드
@PreDestroy : Application 종료 전 실행되는 코드
@SpringBootApplication
@EnableAsync
@Slf4j
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@PostConstruct
public void onInit() {
// 시작 시 작업
}
@PreDestroy
public void onExit() {
// 종료 시 작업
}
}
그 외 Application gracefully shutdown ( Spring 2.3 이상 )
Spring에서 제공해주는 우아하게(?) shut down 하는 설정이 있다.
application.yml >
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 20s
application 설정파일에 shutdown : graceful 설정으로 비동기 작업이 있을 경우, 해당 작업이 끝날 때 까지 기다릴 수 있는
기능을 제공하고 있다.
*timeout-per-shutdown-phase : graceful shutdown 진행 중 최대 기다리는 시간 (default : 30s)
*기다리는 작업이 있으면 shutdown-phase에 적용된 시간만큼 기다려주는 것 같으나 @Async 작업에 대해서는 바로 종료되는하다
*적절한 shutdown-phase으로 무한 대기시간이 걸리지 않게 조심해야 한다
'Java > Spring' 카테고리의 다른 글
[SpringBoot] SpringBoot에서 JPA 사용하기 (0) | 2023.08.05 |
---|---|
[Spring] JPA 란 무엇인가 (정의, 장단점) (0) | 2023.06.01 |
[SpringBoot] SpringBoot 에 RabbitMQ 연동하기 (0) | 2022.08.05 |
[Spring] Spring Bean 등록과 의존관계 설정 (0) | 2022.06.20 |
[SpringBoot] Junit 으로 단위테스트 하기 (0) | 2022.06.17 |