비동기적인 방식은
A가 B에게 작업을 요청하고, B의 작업이 실행되는 것과 상관없이 A 자신의 작업을 이어서 진행하는 방식을 의미한다.
동기/비동기에 대한 구체적인 설명은 아래 글에 정리하였었다.
동기/비동기(Sync/Async), Blocking/Non-Blocking
시작하기에 앞서, 간단히 기준이 되는 용어를 보겠습니다.Blocking/Non-Blocking : '제어권'을 두고 구분동기/비동기 : 결과값을 기다리느냐로 구분 1. Blocking VS Non-Blocking : 제어의 관점요청한 작업이 전
thisisprogrammingworld.tistory.com
Spring Boot에서는 몇개의 애노테이션 설정만으로 비동기 방식을 구현할 수 있도록 제공한다.
- @EnableAsync, @Async 이용
1. 설정 클래스에 @EnableAsync 적용
@Configuration 애노테션이 붙은 설정 클래스에 적용하면 된다.
보통 해당 클래스 내에 비동기 작업을 처리하는 스레드 풀에 대한 설정도 함께 해준다.
@EnableAsync
@Configuration
public class AsyncConfig {
/*
비동기 작업들 실행 시 사용될 Thread Pool
모두 Default 값으로 설정
*/
@Bean
public Executor asyncThreadPoolExecutor() {
ThreadPoolTaskExecutor executer = new ThreadPoolTaskExecutor();
executer.setCorePoolSize(1);
executer.setMaxPoolSize(Integer.MAX_VALUE);
executer.setQueueCapacity(Integer.MAX_VALUE);
executer.setThreadNamePrefix("AsyncThread-");
executer.initialize();
return executer;
}
}
일반적으로, Spring 은 SimpleAsyncTaskExecutor 클래스를 사용해 비동기 처리를 수행한다.
다만, 아래 두가지 방법으로 오버라이딩할 수 있다.
1. 애플리케이션 레벨에서 오버라이딩 : AsyncConfigurer 인터페이스를 implements하면서 getAsyncExecutor() 메서드를 오버라이딩하고, 이 메서드 내에서 원하는 구현체를 반환하도록 한다.
2. 개별 메서드 레벨에서 오버라이딩 : @Async 애노테이션의 name 속성을 이용하여 특정 Bean 이름을 지정할 수 있다. 이 애노테이션이 붙은 메서드에서는 지정된 클래스에서 관리되는 스레드 풀을 이용하게 된다. ex) @Async("threadPoolTaskExecutor")
ThreadPoolTaskExecutor 는 일반 요청 시에는 사용되지 않는 스레드 풀이고
비동기 메서드를 수행할 때 (@Async를 적용하여 비동기 작업을 수행하려할 때, @EventListener를 적용한 비동기 이벤트 리스너가 수행될 때), 혹은 직접 이 Executor를 사용하겠다고 정의한 경우 사용된다.
Spring Boot 3.x 에서의 ThreadPoolTaskExecutor 설정 방법
Spring Boot 3점대 를 사용한다면, ThreadPoolTaskExecutor 를 직접 설정하지 않고 설정 파일에 입력하는 것만으로도 설정 가능하며, Spring Boot가 자동으로 관리해준다.
다만, 여러 스레드 풀의 설정을 해줘야한다면 별도 설정 클래스에서 빈을 등록해주는 것이 관리 측면에서 좋을 것으로 보인다.
# application.yml
spring:
task:
execution:
pool:
core-size: 5
max-size: 10
queue-capacity: 100
thread-name-prefix: "Async-"
ThreadPoolTaskExecutor 클래스 에 대해 설정할 수 있는 값들은 아래와 같다.
- spring.task.execution.pool.core-size: core thread 갯수. 가상 스레드가 활성화되어 있으면 의미 없음. (Default : 8)
- spring.task.execution.pool.max-size: 늘릴 수 있는 최대 스레드 갯수. 큐가 가득 차버리면 (즉, 100개의 작업이 대기 중인 상황) 스레드 풀은 최대 16개까지 증가하도록 하여 부하를 수용할 수 있다. 다만 큐 용량이 무한대라면 무시되는 값이다. (Default : Integer 범위 최대 값)
- spring.task.execution.pool.queue-capacity : 큐 용량. 이 용량이 무제한이라면, max-size 속성이 무시된다. (Default : Integer 범위 최대 값)
- spring.task.execution.pool.keep-alive : 스레드가 회수되기 전 유휴 상태로 유지하는 시간 제한. 10초 설정하면, 10초 동안 유휴 상태가 유지되면 회수된다. (Default : 60s)
2. 비동기로 수행시키고 싶은 메서드에 @Async 적용
간략히 0부터 9까지 순회 출력하는 메서드를 작성하였다.
@Component
public class AsyncTask {
@Async("asyncThreadPoolExecutor")
public void async() {
for (int i=0; i<10; i++) {
log.info("async() - {}", i);
}
log.info("2. async() Method Finished. (time: {})", LocalTime.now());
}
}
위 메서드를 호출하는 테스트코드를 작성하여,
테스트 코드에서 async() 메서드를 호출 했을 때 async() 메서드 호출이 완료되기 전에
테스트 코드 내 다음 로직이 실행되는지 확인하였다.
@SpringBootTest
class AsyncTaskTest {
@Autowired
private AsyncTask asyncTask;
@Test
void async() {
System.out.println("1. Before async() Calling : " + LocalTime.now());
asyncTask.async();
System.out.println("3. async() finished : " + LocalTime.now());
}
}
동기적으로 처리된다면 1 > 2 > 3. 순으로 메세지가 출력되지만, 비동기로 처리되어 1 > 3 > 2. 순으로 메세지가 출력되는 것을 확인하였다.
추가 알아놓을 내용.
@Async 애노테이션은 AOP 기반의 프록시 패턴을 사용하여 동작한다.
Spring에 의해 생성되는 프록시 객체를 통해서 메서드에 접근하기 때문에 private 메서드이면 안되고,
@Async 애노테이션이 적용된 메서드는 외부 클래스에서 호출할 때에만, 예상대로 비동기 처리된다.
동일 클래스 내부에서 this를 통해 스스로를 호출하면, 프록시 객체를 거치지 않고 직접 메서드 자체를 호출하기 때문에 비동기 처리가 적용되지 않게 된다.
그리고 위에 작성한 것처럼 @EnableAsync 애노테이션을 사용하여 비동기 기능을 활성화해야한다.
참고
- https://docs.spring.io/spring-boot/reference/features/task-execution-and-scheduling.html
- https://www.baeldung.com/spring-async
'백엔드 개발하며 작성한 > Spring' 카테고리의 다른 글
[Spring Batch] 1. 스프링 배치 공부 순서와 간단 소개 (0) | 2023.06.11 |
---|---|
Spring Framework와 Spring Boot (0) | 2022.08.01 |
@Component에 대해 (@Bean) (0) | 2022.07.26 |
@RequestBody 붙였어?? (0) | 2022.07.13 |
Spring Boot에서 Connection Pool 이용하기 (HikariCP) (0) | 2022.04.10 |