비동기 처리(asynchronous, async)는
지금 당장 결과가 올 때까지 기다리지 않고, 요청만 보내고 다음 작업을 계속 진행하는 방식을 의미한다.
동기(Sync)는 작업 A가 끝나야 작업 B를 실행할 수 있다.
비동기(Async)는 작업 A가 끝날 때까지 기다리지 않고 B를 바로 실행할 수 있다.
정확한 근거를 들면 아래 3가지가 핵심이다.
요청 발생 → 작업을 별도 스레드에게 위임 → 메인 흐름은 즉시 다음 작업 수행
작업이 끝나면:
같은 방식으로 결과를 알려줌.
Spring에서는 @Async 를 가장 많이 사용한다고 한다.
@Configuration
@EnableAsync
public class AsyncConfig {}
@Service
public class MyService {
@Async
public void processAsync() {
System.out.println("비동기 작업 실행");
}
}
myService.processAsync();
System.out.println("이 문장이 먼저 출력됨");
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
executor.initialize();
return executor;
}
}
스레드 개수를 튜닝하면 고성능 비동기 구성 가능.
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
executor.initialize();
return executor;
}
// @Async 메서드가 void 리턴일 때 예외 처리용
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) -> {
// 여기서 로깅, 모니터링, 알림 등 처리
log.error("Async error in method: {}", method.getName(), ex);
};
}
}
void 리턴 @Async 방법@Async
public void sendSlackLogAsync(String message) {
// 예: 슬랙 전송 중 예외 발생
throw new AppException(...);
}
slackService.sendSlackLogAsync("msg");
System.out.println("이 문장은 바로 실행됨");
AppException은 호출자에게 절대 가지 않음AsyncUncaughtExceptionHandler로 넘어감MSA에서 “슬랙/이메일 로그 전송, 분석 로그 적재” 같은 부가 작업은
보통 이렇게void @Async+ 핸들러에서 로깅/알림 처리로 끝냄.
중요 포인트 하나:
@Transactional
public void createOrder(...) {
// DB insert
orderRepository.save(...);
// 비동기로 슬랙 전송
notificationService.sendSlackAsync(...); // @Async
}
createOrder의 트랜잭션이 커밋되기 전에그래서 패턴은 보통:
MSA에서는 3번(도메인 이벤트 + 메시지 브로커)로 가는 게 더 안전함.
→ CompletableFuture/Future로 리턴해서
호출자가 join()/get()에서 CompletionException 처리
→ 그걸 다시 AppException으로 래핑 → @ControllerAdvice에서 HTTP 응답
→ void @Async + AsyncUncaughtExceptionHandler
→ 또는 Kafka 이벤트 Consumer 내부 try-catch에서 로깅 & DLQ
→ CompletableFuture.allOf() + 각 future에서 exceptionally로 개별 예외 처리
→ 마지막에 CompletionException 잡아서 공통 예외로 변환
초기 버전에서 async로 처리하고, 나중에 message queue로 확장하기 좋음.