AsyncConfigurer를 사용하여 return value있는 @Async 적용하기

cotchan·2021년 7월 26일
3
post-custom-banner
  • 개인 공부를 위해 작성한 글입니다.
  • 아래 출처를 참고하여 작성하였습니다.

1. Async-NonBlocking 방식

  • Spring에서 쓰레드 풀을 사용하여 Async-NonBlocking 방식으로 요청을 처리하는 방법을 알아봅시다.

2. Thread Pool 만들기

2-1. SimpleAsyncTaskExecutor

  • 별도의 Thread Pool을 만드는 이유
    • Spring 기본설정으로 되어 있는 TaskExecutorSimpleAsyncTaskExecutor입니다.
    • SimpleAsyncTaskExecutor에서는 어떤 스레드도 재사용하지않고 호출마다 새로운 스레드를 시작합니다.
    • 동시접속 제한(concurrency limit)을 지원 제한 수가 넘어서면 빈 공간이 생길 때까지 모든 요청을 block합니다.
    • 이런 방식은 리소스 낭비가 심하니 Thread Pool을 먼저 만들어줍니다.

2-2. AsyncConfigurer

  • 저는 AsyncConfigurer를 사용해서 공통된 Thread Pool을 사용하는 방법을 쓸 것입니다.

    • AsyncConfigurer 를 implements 하여 getAsyncExecutor method를 override 합니다.
  • 기본 구조는 아래와 같습니다.

    • @Async를 사용해서 다른 스레드에서 돌리고자 한다면 @EnableAsync 설정을 해야 합니다.
//기본 구조

@EnableAsync
@Configuration
public class AsyncConfigure implements AsyncConfigurer {

}

  • 저는 새로운 ThreadPool의 Executor로 ThreadPoolTaskExecutor를 사용했습니다.
  • ThreadPoolTaskExecutor
    • 자바 5에서 가장 일반적으로 사용하는 TaskExecutor입니다.
    • java.util.concurrent.ThreadPoolExecutor를 구성하는 bean 프로퍼티를 노출하고 이를 TaskExecutor로 감쌉니다.
//Sample Code

@EnableAsync
@Configuration
public class AsyncConfigure implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.setThreadNamePrefix("async-thread-");
		executor.setCorePoolSize(10);
		executor.setMaxPoolSize(50);
		executor.setQueueCapacity(100);
                executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
		executor.initialize();
		return executor;
    }
}


3. @Async는 return void

  • 아쉽게도 @Async는 기본적으로 method의 return value가 void인 경우만 적용됩니다.
  • 그러므로 @Async를 사용해서 return value를 주고받고 싶다면 CompletableFuture를 사용해야 합니다.

4. return CompletableFuture

  • 이제부터 비동기 작업의 플로우 관리를 CompletableFuture 사용해 할 것입니다.
  • 즉, @Async 어노테이션을 사용하고 return value는 CompletableFuture를 사용합니다.
    • 그러면 이제 다른 쓰레드에 요청을 위임하면서도 결과값을 받을 수 있습니다.
//기본 구조
@Async
@GetMapping(path = "_hcheck")
public CompletableFuture<Long> healthCheck() {
    return CompletableFuture.completedFuture(healthCheckService.healthCheck());
}

5. 완성

  • 아래 세 가지를 조합하여 멀티쓰레드 환경(쓰레드 풀 사용)에서 Async-NonBlocking 방식으로 요청을 처리하고 return value를 받을 수 있도록 완성하였습니다.

    • @Async
    • Thread Pool(AsyncConfigurer)
    • CompletableFuture
  • 아래는 샘플 소스를 첨부하였습니다.

//HealthCheckRestController
@RequestMapping("api")
public class HealthCheckRestController {

    private final HealthCheckService healthCheckService;

    @Async
    @GetMapping(path = "_hcheck")
    public CompletableFuture<Long> healthCheck() {
        return CompletableFuture.completedFuture(healthCheckService.healthCheck());
    }
}
//HealthCheckService
@Service
public class HealthCheckService {

    public Long healthCheck() {
        return System.currentTimeMillis();
    }
}
//AsyncConfigure.java
@EnableAsync
@Configuration
public class AsyncConfigure implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.setThreadNamePrefix("async-thread-");
		executor.setCorePoolSize(10);
		executor.setMaxPoolSize(50);
		executor.setQueueCapacity(100);
                executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); //추후 설명
		executor.initialize();
		return executor;
    }
}

profile
https://github.com/cotchan
post-custom-banner

0개의 댓글