@Async
를 통해서 비동기 처리하는 방법 관련 내용 및 설정값들에 대해서 간단하게 정리한 내용이다.
# @Async
를 통한 비동기 처리
@Async
애노테이션을 통해서 메소드 호출을 비동기로 처리할 수 있는데
적용 방법은 비동기로 수행할 메소드(혹은 타입)에 @Async
를 붙여주면 된다.
모든 메소드에 사용할 수 있는 것은 아니고 public void 혹은 public Future 타입의
메소드에만 적용할 수 있고 같은 클래스 내의 메소드 호출은 적용이 안된다. (다이나믹 프록시 적용이 가능해야 하기 때문임)
(task:executor 설정 시 mode를 aspectj로 지정하면 이 제약이 없을 수도 있을 것 같다.)
작업의 결과값을 알아야하는 경우에는 Future 타입을 리턴 받아야 할 듯하다.
비동기 처리를 위해서는 다음과 같은 추가적인 스프링 설정도 해주어야 한다.
(@EnableAsync
를 통한 어노테이션 기반 설정도 가능하다.)
<task:annotation-driven executor="executor" />
<task:executor id="executor" pool-size="5" queue-capacity="100" rejection-policy="ABORT"/>
어노테이션 기반으로 비동기 처리를 하기 위한 설정과 비동기 처리 시 사용할 executor를 정의한다.
설정을 통해서 생성되는 executor는org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
클래스 객체로,java.util.concurrent.ThreadPoolExecutor
에 부가기능을 추가한 클래스라고 할 수 있다.
# task:executor 설정값
pool-size
스레드 풀 사이즈로, 5-10과 같이 범위로 지정도 가능하다.
각각 corePoolSize, maxPoolSize 값에 해당되는데,
단일 값인 경우는 corePoolSize와 maxPoolSize가 서로 같다고 할 수 있다.
디폴트값은 1-Interger.MAX_VALUE
이다.
queue-capacity
작업 큐의 크기로 요청받은 작업을 저장할 최대 갯수인데, 기본값은 Integer.MAX_VALUE
이다.
executor는 poolSize가 corePoolSize에 도달한 이후에는 큐가 가득찼을 경우에만
poolSize를 maxPoolSize까지 증가시키게 된다.
예를 들어 설명하면, 아래와 같은 설정일 때
<task:executor id="executor" pool-size="5-10" queue-capacity="100" rejection-policy="ABORT"/>
최초 5개의 작업 요청이 동시에 들어오면 corePoolSize인 5까지는 5개의 스레드가 생성되서
5개 작업이 동시에 처리된다.
만약 5개의 작업이 끝나지 않은 상태에서 5개의 요청이 더 들어온다면 스레드가 10개가 되는게 아니라
큐에 5개가 쌓이게 된다.
처리 중인 5개의 스레드가 끝나는대로 큐에 남은 5개를 순차적으로 처리하게 된다.
만약 큐에 100개까지 작업이 쌓인 상황에서 작업이 하나 더 들어온다면 poolSize는 6으로 증가되고
최대 10까지 증가될 수 있다.
queue-capacity를 따로 지정하지 않으면 큐에 기본값인 Integer.MAX_VALUE
개 작업이 쌓일 때까지 poolSize가 더 이상 커지지 않기 때문에 corePoolSize와 maxPoolSize가 다르다면, queue-capacity값 조절도 필요하다고 할 수 있다.
만약 작업큐가 가득차고 poolSize가 maxPoolSize에 도달한 상태에서 작업이 더 들어온다면, 그 때는 rejection-policy에 따라 동작이 결정된다.
rejection-policy
작업을 더 이상 받아들일 수 없을 때의 처리 방법.
기본적으로 다음 4가지 방법을 선택할 수 있고 직접 구현해서 설정하는 것도 가능하다.
- ABORT : 작업이 추가되지 않고 RejectedExecutionException을 던지게 된다. (기본값)
- CALLER_RUNS : 작업을 등록하려 한 스레드에서 작업을 실행하게 함. 따라서 그 시간 동안 스레드 풀의 스레드는 큐의 작업을 처리할 시간을 가지게 된다.
- DISCARD : 추가하려했던 작업을 아무 반응 없이 제거
- DISCARD_OLDEST : 큐에서 가장 오래된 항목 제거 후 추가
위 설정값들은 스프링에서만 제공되는 것은 아니고 자바 concurrent ThreadPoolExecutor의 설정값과
맵핑되는 값으로 동작 방식도 이 클래스에 따르고 있다.
# shutdown
ThreadPoolTaskExecutor 빈이 destroy될 때 shutdown 처리되게 되는데,
기본적으로는 작업을 더 이상받지 않고, 만약 실행 중인 작업이 있다면 중지시키게(interrupt) 된다.
만약 작업이 끝날 때까지 대기하게 하고 싶다면, 다음 설정값으로 조정할 수 있다.
- waitForTasksToCompleteOnShutdown : shutdown 시 대기할지 여부 true/false, 기본값 false.
- awaitTerminationSeconds : 대기할 경우 대기할 초
위 두 속성은 task:executor에는 없기 때문에 ThreadPoolTaskExecutor 빈 직접 정의 시 설정할 수 있다.
비동기 설정 시 너무 작은 값 설정 시 성능 문제, 너무 큰 값 설정 시 리소스 부족 문제 등이
발생할 수 있으니 특성에 맞게 적절히 잘 설정해서 사용할 필요가 있을듯하다.
# 주의점
- 트랜잭션 설정과
@Async
를 동시에 적용할 경우 트랜잭션 처리가 정상적으로 안될 수 있음 - @Async 처리 메소드가 있는 Bean은 스프링 설정 로딩 시 초기화가 빨리 진행되어 순환 참조가 있다면 문제가 될 수 있음
'개발 > Spring' 카테고리의 다른 글
Spring KerberosRestTemplate 사용 예제 (0) | 2021.01.08 |
---|---|
Spring Controller에서 파라미터 검증 방법들 (0) | 2020.06.18 |
MyBatis cannot change the ExecutorType when there is an existing transaction 오류 (0) | 2018.04.14 |
Spring Batch skip 로직 동작 방식 (0) | 2017.04.24 |
Spring Batch commit-interval에 대한 정리 (0) | 2016.12.23 |