Spring

[Spring]고성능 비동기 웹 개발의 시작: Spring WebFlux 알아보기

누구세연 2024. 11. 11. 20:06

최근 고성능 비동기 API를 구현해야 할 필요가 생기면서 기존의 Spring MVC로는 처리 성능에 한계가 있음을 느꼈다..🤔

더 많은 요청을 효율적으로 처리할 방법을 찾던 중, Spring이 기존 MVC 외에 논블로킹 비동기 웹 애플리케이션을 지원하는 Spring WebFlux를 제공한다는 것을 알게 되었습니다.

 

이글에서는 Spring WebFlux의 개념에 대해 알아보겠습니다.

 

Spring WebFlux 개요

Spring WebFlux는 비동기 및 논블로킹 I/O를 기반으로 한 웹 프레임워크입니다.

전통적인 Spring MVC의 동기적 구조와 달리 WebFlux는 Reactive Streams API를 기반으로 논블로킹 방식의 고성능 비동기 웹 애플리케이션을 구축할 수 있습니다.

 

전통적인 Servlet Stack과 Reactive Stack의 차이

 

  • Spring MVC: Servlet 기반의 동기적 요청-응답 처리 모델을 사용하여 서버가 요청을 처리하는 동안 스레드를 블로킹합니다. 이는 다수의 요청을 처리할 때 성능상의 제한을 초래할 수 있습니다.
  • Spring WebFlux: Reactive Stack을 사용하여 요청-응답을 논블로킹 방식으로 처리하므로 I/O 작업에서의 효율이 매우 높습니다. 이는 높은 동시성을 요구하는 환경에서 특히 유리합니다.

 

 

WebFlux의 핵심 구성 요소

Mono와 Flux

Spring WebFlux의 비동기 데이터 흐름은 MonoFlux라는 두 가지 리액티브 타입으로 이루어집니다.

  • Mono
    단일 값(또는 빈 값)을 나타내는 Publisher입니다.
    예를 들어, 특정 사용자 정보를 조회할 때 하나의 결과가 반환되면 Mono로 처리할 수 있습니다.
  • Flux
    다중 값 스트림을 나타내며 여러 개의 데이터를 순차적으로 방출할 때 사용합니다.
    예를 들어, 모든 사용자의 정보를 스트리밍 방식으로 가져올 때 사용할 수 있습니다.

Non-blocking I/O와 Backpressure

WebFlux는 논블로킹 방식으로 I/O 작업을 처리하며, 데이터 소비 속도를 조절하기 위해 Backpressure를 지원합니다. Backpressure는 데이터가 너무 빠르게 흘러올 때 처리 속도를 제어하는 기능으로, 안정적인 데이터 흐름을 유지하는 데 도움이 됩니다.

 

Spring WebFlux를 이용한 비동기 REST API 개발

WebFlux에서는 두 가지 방식으로 REST API를 구현할 수 있습니다.

@RestController와 같은 전통적인 어노테이션 기반 방식을 사용할 수도 있지만, Router FunctionHandler Function을 사용한 함수형 프로그래밍 스타일로도 구현 가능합니다.

 

Router Function과 Hanler Function 사용 예제

아래의 간단한 사용자 API 예제를 살펴보겠습니다.

// Handler Function
@Component
public class UserHandler {
    private final UserService userService;

    public UserHandler(UserService userService) {
        this.userService = userService;
    }

    public Mono<ServerResponse> getUserById(ServerRequest request) {
        String userId = request.pathVariable("id");
        return userService.findUserById(userId)
                .flatMap(user -> ServerResponse.ok().bodyValue(user))
                .switchIfEmpty(ServerResponse.notFound().build());
    }
}

// Router Function
@Configuration
public class UserRouter {
    @Bean
    public RouterFunction<ServerResponse> route(UserHandler handler) {
        return RouterFunctions.route(GET("/users/{id}"), handler::getUserById);
    }
}

위 코드에서 `UserHandler`는 요청을 처리하는 비즈니스 로직을 포함하고, `UserRouter`는 HTTP 경로와 Handler를 연결합니다.

 

WebClient로 외부 API 호출

Spring WebFlux에서는 비동기 HTTP 요청을 위해 WebClient를 사용합니다. WebClient는 RestTemplate의 대안으로 비동기, 논블로킹 HTTP 클라이언트 역할을 합니다.

WebClient webClient = WebClient.create("https://rosytest.com");

public Mono<User> getUserById(String userId) {
    return webClient.get()
            .uri("/users/{id}", userId)
            .retrieve()
            .bodyToMono(User.class);
}

위 코드는 특정 사용자 ID에 따라 외부 API에 비동기 요청을 보내고 Mono<User> 타입으로 결과를 받습니다.

 

예외 처리와 예외 관리

WebFlux에서 에러는 데이터 흐름 내에서 처리할 수 있으며, onErrorResume 같은 에러 핸들링 메서드를 통해 예외 상황을 대처할 수 있습니다. 또한, WebFlux 애플리케이션의 전역 예외 처리를 위해 GlobalExceptionHandler를 구현하여 에러를 일관성 있게 관리할 수도 있습니다.

public Mono<User> getUser(String userId) {
    return userService.findUserById(userId)
            .onErrorResume(e -> {
                log.error("Error occurred", e);
                return Mono.empty();  // 에러 시 빈 Mono 반환
            });
}

 

Spring WebFlux의 장단점

장점

  • 고성능 비동기 처리: 논블로킹 I/O를 통해 높은 트래픽을 효율적으로 처리할 수 있습니다.
  • CPU 효율성 증가: 비동기 방식이므로 CPU 사용률을 최적화하여 더 많은 요청을 동시에 처리할 수 있습니다.
  • 유연성: RESTful 서비스뿐만 아니라 WebSocket, Server-Sent Events 같은 실시간 서비스 구현에도 유리합니다.

단점

  • 코드 복잡성: 비동기 프로그래밍의 특성상 로직이 복잡해질 수 있습니다.
  • 디버깅 어려움: 비동기 처리가 스택 트레이스 추적을 어렵게 하여 문제 해결이 복잡할 수 있습니다.

 

💡 Spring WebFlux는 비동기 논블로킹 웹 애플리케이션을 구축할 수 있는 강력한 도구로, 특히 높은 트래픽 환경에서 효율적인 성능을 발휘할 수 있습니다. WebClient, Router Function, 그리고 Mono와 Flux를 적절히 활용하면 고성능의 확장 가능한 애플리케이션을 개발하는 데 큰 도움이 됩니다.