본문 바로가기

Backend/Spring

Spring Boot @Async Callback 비동기처리

기본적으로 Spring은 동기식 처리 모델이기 때문에 메소드를 호출하면 이후의 작업들은 Blocking되어 수행하지 않아요.
Spring Boot에서는 @Async라는 어노테이션을 지원하여 상황에 따라 비동기 처리가 가능하도록 하고 있어요.
@Async를 어떻게 사용하면 되는지에 대해서 알아볼게요.

설정 파일을 하나 추가해줄게요.
@EnableAsync는 서버에서 비동기 처리를 할 수 있도록 기능을 ON하였다라고 보면되요.
또한, AsyncConfigurerSupport를 상속받아서 Thread Pool을 커스텀해볼게요.
corePoolSize : 미리 스레드를 만들어 놓아 요청이 들어오면 바로 수행
maxPoolSize : 쓰레드 풀의 최대 사이즈
queueCapacity : corePoolSize가 꽉차면 이후에는 queue에 등록하여 순차적으로 수행, 최대로 maxPoolSize 만큼 쌓임

@Configuration
@EnableAsync
public class AsyncConfig extends AsyncConfigurerSupport {

	@Override
	public Executor getAsyncExecutor() {
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.setCorePoolSize(10);
		executor.setMaxPoolSize(100);
		executor.setQueueCapacity(50);
		executor.initialize();
		return executor;
	}

}


이제 @Async를 사용하여 비동기 처리를 해볼게요.
※ 주의 사항이 있어요.

  • 반드시 public 으로 메서드를 선언해야해요.
  • 같은 클래스의 메서드에 @Async 설정하여 호출할 경우 동작하지 않아요.
  • 리턴 타입은 void나 Future<V> 인터페이스만 가능해요.


예제 1) void

테스트를 위해 메서드 내에서 스레드에 sleep을 1초 간 줄게요.

@Async
public void logger() throws InterruptedException {
	Thread.sleep(1000);
	logger.info("서비스");
}

testService.logger();
logger.info("컨트롤러");


서버를 실행하고 http://localhost:8080/test 를 호출해보겠습니다.


예제 2) Future<V>

이번엔 비동기 처리 후 리턴 값을 받아서 처리하는 방법을 알아볼게요.
처리가 끝나면 future.isDone() 이 true로 바껴요.
while 문을 돌려서 받을 수 있을 때까지 기다립니다.
이 방식은 Async라고 하기에는 먼가 억지스러운 면이 있는 것 같습니다.

@Async
public Future<String> logger() throws InterruptedException {
	Thread.sleep(1000);
	logger.info("서비스");
	return new AsyncResult<>("결과");
}

Future<String> future = testService.logger();
logger.info("컨트롤러");
while (true) {
	if (future.isDone()) 
		return future.get();
}


예제 3) ListenableFuture<T> extends Future<T>

예제 2에서 결과값을 리턴받아 while문으로 처리하는 모습이 코드 상에서 보기 좋지 않네요.
자바스크립트 callback 같이 깔끔하게 처리할 수 있으면 좋겠어요.
ListenableFuture<T> 인터페이스의 addCallback이라는 메서드를 사용하면 람다식으로 리턴 값을 비교적 보기좋게 처리할 수 있습니다.

@Async
public ListenableFuture<String> logger() throws InterruptedException {
	Thread.sleep(1000);
	logger.info("서비스");
	return new AsyncResult<>("결과");
}

testService.logger().addCallback((result) -> {
	logger.info(result);
}, (e) -> {
	logger.error(e.getMessage(), e);
});
logger.info("컨트롤러");


테스트해보면 예제 2와 동일한 결과를 확인할 수 있어요.


예제 4) CompletableFuture implements Future, CompletionStage

예제 3의 ListenableFuture를 사용하면 Callback 안에서 또 비동기처리를 할 경우 코드가 굉장히 복잡해질 수 있어요. 콜백 지옥에 빠질 수가 있죠.
CompletableFuture을 사용하여 연속된 비동기 코드를 아래와 같이 말끔하게 작성할 수 있습니다.