우리가 Spring에서 @Async와 @EnableAsync 어노테이션을 통해
간단하게 메소드가 비동기로 동작하도록 설정할 수 있다.
하지만 비동기에 관한 설정을 커스텀하기가 어렵다.
기본적으로 @Async어노테이션은 SimpleAsyncUncaughtExceptionHandler를 상속받고 있기 때문이다.
🔽 SimpleAsyncUncaughtExceptionHandler를 사용하는 자세한 코드
AsyncConfig
때문에 AsyncConfigurerSupport를 상속받는 클래스를 생성하여 커스텀해보자.
주의!
AsyncCongifurer를 상속받아야 할 까, AsyncConfigurerSupport를 상속받아야 할까?
-> 둘 다 가능. 하지만 AsyncConfigurerSupport(클래스)의 상위 인터페이스가 AsyncConfigurer(인터페이스)이다.
때문에 클래스를 상속받는 것보다 인터페이스를 상속받는 것이 더 좋다고 생각하여
AsyncConfigurer로 수정하는 방향으로 해야겠다.
수정 전 : extends AsyncCongifurerSupport
수정 후 : implements AsyncCongifurer
여기서는 AsyncConfig라고 하겠다.
이렇게 쓰레드를 어떻게 사용할 것인지 커스텀할 수 있다.
@EnableAsync : 비동기 활성화
@Configuration : 클래스를 빈으로 등록
@ThreadPoolTaskExecutor : 쓰레드 실행을 돕는 녀석
corePoolSize : 기본 실행대기하는 쓰레드 개수
maxPoolSize : corePoolSize와 QueueCapacity가 다 찼을 때 최대 실행 대기할 수 있는 쓰레드 개수
threadNamePrefix : 쓰레드이름 접두사
ThreadPoolTaskExecutor 기본적인 동작원리
corePoolSize ➡ queueCapacity ➡ maxPoolSize ➡ queueCapacity ➡ RejectedExecutionException
1. 현재 점유하고 있는 쓰레드 개수가 corePoolSize 만큼 있을 때 요청이 오면 queueCapacity의 개수만큼 요청을 큐에 넣는다.
2. 현재 점유하고 있는 쓰레드 개수가 corePoolSize 만큼 있고 queueCapacity개수만큼 큐에 요청이 차있을 때
요청이 오면 maxPoolSize만큼 쓰레드풀을 생성한다.
3. 현재 점유하고 있는 쓰레드 개수가 maxPoolSize만큼 있고 queueCapacity 개수만큼 큐에 요청이 차있을 때
요청이 오면 RejectedExecutionException 예외가 발생한다.
때문에 이 경우를 대비해 방어로직을 추가해야 한다.
실험
corePoolSize를 1, maxPoolSize를 1, queueCapacity를 0으로 설정하고
@Async 설정된 메소드에 Thread.sleep(3)을 각각 추가하니 RejectedExecutionException 리젝티드엑세큐션익셉션 이 발생하였다.
Rejected Executor Exception :
비동기 요청이 너무 많아서 해당 요청을 처리하는 쓰레드가 쓰레드풀에 들어가지 못하여 발생한 예외
해당 방어로직은 @Async 설정된 메소드를 사용하는 서비스 단에서 try catch로 해주었다.
방어로직은 아래와 같이 해주었다.
결과
비동기 요청이 정상적으로 처리가 되었다. corePoolSize로 설정해준 쓰레드풀 사이즈 5 만큼 쓰레드가 생성되어 처리가 되고 있다. 추가 요청은 Queue에 쌓이게 된다.
비동기 요청이 많아지면 RejecteExecutor 익셉션 발생하고
이에 대한 방어로직이 정상적으로 동작했다.