개발/Spring Boot

Spring Boot 시리즈 21편 – 비동기 처리와 병렬 프로그래밍: @Async, CompletableFuture, Reactor 실전 사용법

B컷개발자 2025. 4. 30. 12:41
728x90
SMALL

Spring Boot에서 @Async, CompletableFuture, Reactor를 활용한 비동기 및 병렬 처리 전략을 소개합니다. 서비스 성능을 높이는 실전 적용 예시 포함.


Spring Boot 시리즈 21편 – 비동기 처리와 병렬 프로그래밍: @Async, CompletableFuture, Reactor 실전 사용법

애플리케이션 성능 병목의 90%는 IO 지연 또는 외부 시스템 호출에서 발생합니다.
이럴 때 가장 효과적인 전략은 비동기화와 병렬 처리입니다.

이번 글에서는 Spring Boot에서 @Async, CompletableFuture, Project Reactor를 활용해
실제로 서비스 성능을 높일 수 있는 실전 비동기 처리 전략을 소개합니다.


📌 1. 비동기 처리란?

비동기(Asynchronous)란 요청과 응답을 기다리지 않고 작업을 위임하거나 병렬로 처리하는 방식입니다.

비교 항목 동기(Sync) 비동기(Async)

응답 대기 호출자가 결과를 기다림 결과를 기다리지 않고 다음 작업 수행
쓰레드 점유 대기 중에도 점유됨 쓰레드 효율적 사용 가능
장점 코드 직관적 응답 지연 감소, 병렬 처리 가능

✅ 2. @Async 기반 비동기 처리

1️⃣ 설정

@SpringBootApplication
@EnableAsync
public class MyApp {}

2️⃣ 서비스에 @Async 적용

@Service
@Slf4j
public class NotificationService {

    @Async
    public void sendEmail(String to, String content) {
        log.info("Sending email to {}...", to);
        // 실제 이메일 발송 코드
    }
}
  • 호출자는 즉시 리턴되며, 이메일 발송은 별도 쓰레드에서 수행됨

3️⃣ 비동기 메서드의 반환값 – Future or CompletableFuture

@Async
public CompletableFuture<User> getUserInfo(Long id) {
    return CompletableFuture.completedFuture(userRepository.findById(id).orElseThrow());
}

⚙️ 3. CompletableFuture 활용

병렬 호출 및 결과 병합 예시

public CompletableFuture<User> getUser(Long id) { ... }
public CompletableFuture<Order> getOrder(Long userId) { ... }

public UserDashboard loadDashboard(Long userId) {
    CompletableFuture<User> userFuture = getUser(userId);
    CompletableFuture<Order> orderFuture = getOrder(userId);

    return userFuture.thenCombine(orderFuture, (user, order) ->
        new UserDashboard(user, order)
    ).join();  // 결과 대기
}
  • thenCombine: 두 결과를 병합
  • join(): 결과를 기다림 (주의: 블로킹 발생 가능)

🔥 4. Project Reactor – 비동기 스트림 처리

기본 개념

개념 설명

Mono 0 또는 1개의 비동기 결과
Flux N개의 스트림 비동기 결과
non-blocking Netty 등 비동기 HTTP 클라이언트 기반 (WebFlux에서 사용됨)

Mono 예시

public Mono<User> findUser(String id) {
    return Mono.fromCallable(() -> userRepository.findById(id).orElseThrow());
}

Flux 예시

public Flux<Product> getPopularProducts() {
    return Flux.fromIterable(productRepository.findTop10ByOrderByHitsDesc());
}

WebClient와 함께 비동기 외부 API 호출

public Mono callExternalApi(String query) {
    return WebClient.create()
        .get()
        .uri("https://api.example.com/search?q=" + query)
        .retrieve()
        .bodyToMono(ExternalResponse.class);
}
  • Spring WebFlux 기반의 비동기 클라이언트
  • Netty 기반의 non-blocking I/O 구조로 대규모 트래픽에 적합

🧠 실무 적용 전략

항목 전략

@Async 간단한 비동기 위임 처리에 적합
CompletableFuture 비동기 병렬 작업 간 결과 조합에 효과적
WebClient + Reactor 외부 API 다수 호출, 고성능 비동기 처리에 적합
반환값 관리 비동기 Future는 적절한 예외 처리 필수 (handle, exceptionally)
리소스 관리 별도 TaskExecutor 설정 권장 (@Async thread pool 조절)

Executor 커스터마이징 예시

@Configuration
public class AsyncConfig {

    @Bean(name = "asyncExecutor")
    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("Async-");
        executor.initialize();
        return executor;
    }
}

✅ 마무리 요약

항목 요약

기본 비동기 @Async + @EnableAsync로 쉽게 구현 가능
병렬 처리 CompletableFuture의 thenCombine, allOf 등 사용
고급 처리 Project Reactor의 Mono, Flux로 비동기 스트림 처리
외부 호출 WebClient로 비동기 HTTP 연동 가능
실무 주의 예외처리, 스레드풀 조절, 결과 병합 시점 명확히 설계

📌 다음 편 예고

Spring Boot 시리즈 22편: API 버전 관리 전략 – URL, Header, Accept 기반 버전 설계 Best Practice

728x90
LIST