본문으로 건너뛰기
Advertisement

13.1 Spring WebFlux 개요 및 MVC와의 차이

스프링 5.0부터 도입된 Spring WebFlux 는 비동기-논블로킹(Asynchronous Non-blocking) 방식의 리액티브 웹 프레임워크입니다. 전통적인 Spring MVC와 어떤 차이가 있고 왜 필요한지 알아봅니다.

1. WebFlux가 등장한 배경

기본적인 Spring MVC(Servlet 기반)는 요청당 스레드 하나를 할당하는 Thread-per-Request 모델을 사용합니다. 이 방식은 동시 접속자가 급증할 경우 스레드 수가 늘어나며 메모리 소모가 커지고, 스레드 간 전환(Context Switching) 비용이 발생하는 한계가 있습니다.

이를 해결하기 위해 적은 수의 스레드만으로 대량의 동시 접속을 효율적으로 처리하기 위해 등장한 것이 바로 WebFlux입니다.

핵심 개념: 반응형 프로그래밍(Reactive Programming)

반응형 프로그래밍이란 데이터 스트림(Data Stream)변경 사항의 전파(Propagation of Change) 에 중점을 둔 프로그래밍 패러다임입니다. 데이터가 생성될 때마다 이를 소비하는 측에 전달(Push)하는 방식으로 동작하며, 비동기 처리를 기본으로 합니다.

2. Spring MVC vs Spring WebFlux 핵심 비교

비교 항목Spring MVCSpring WebFlux
모델동기 + 블로킹 (Blocking)비동기 + 논블로킹 (Non-blocking)
처리 방식Thread-per-requestEvent Loop (적은 스레드로 많은 요청 처리)
스택Servlet API (Tomcat 등)Reactive Streams (Netty 등)
데이터베이스JDBC (Blocking)R2DBC, NoSQL (Non-blocking)
권장 용도일반적인 웹 앱, 전통적인 CRUD대규모 스트리밍, 채팅, 서버 간 호출이 많은 MSA

3. 리액티브 스트림 (Mono와 Flux)

WebFlux를 이해하려면 Reactor 라이브러리의 두 가지 핵심 타입을 알아야 합니다.

Project Reactor와 Publisher-Subscriber 패턴

Reactor는 리액티브 스트림즈 표준을 구현한 라이브러리로, 데이터를 발행하는 Publisher 와 이를 구독하여 소비하는 Subscriber 간의 관계를 정의합니다.

  1. 구독(Subscribe): Subscriber가 Publisher를 구독합니다.
  2. 데이터 요청(Request): Subscriber가 받을 수 있는 데이터의 개수를 Publisher에게 요청합니다. (** 백프레셔, Backpressure**)
  3. 데이터 전달(onNext): Publisher가 데이터를 전달합니다.
  4. 완료/에러(onComplete/onError): 모든 데이터 전달이 완료되거나 에러가 발생하면 종료됩니다.

핵심 데이터 타입: Mono와 Flux

  • Mono<T>: 0개 또는 1개의 데이터를 발행 (단건 응답)
  • Flux<T>: 0개 또는 N개의 데이터를 발행 (다건 응답, 스트리밍)
// Mono 예시: 성공 시 "Hello" 발행 후 종료
Mono<String> name = Mono.just("홍길동")
.map(String::toUpperCase)
.flatMap(s -> Mono.just("Name: " + s));

// Flux 예시: 여러 개일 때 유용 (주로 리스트 대신 사용)
Flux<Integer> numbers = Flux.just(1, 2, 3, 4, 5)
.filter(n -> n % 2 == 0)
.map(n -> n * 10);

4. 컨트롤러 구현 방식

WebFlux는 기존 MVC와 동일한 @Controller 애너테이션 방식(Annotated Controller)과, 새로운 함수형 모델(Functional Endpoint) 두 가지 방식을 지원합니다.

1) 애너테이션 방식 (Annotated Controller)

기존 MVC 사용자에게 익숙한 방식이지만, 리턴 타입이 MonoFlux여야 합니다.

@RestController
public class HelloController {
@GetMapping("/hello")
public Mono<String> hello() {
return Mono.just("Hello, WebFlux!");
}
}

2) 함수형 방식 (Functional Endpoint)

Router와 Handler를 분리하여 더 선언적이고 명확하게 엔드포인트를 정의합니다.

@Component
public class HelloHandler {
public Mono<ServerResponse> hello(ServerRequest request) {
return ServerResponse.ok().bodyValue("Hello, Reactive!");
}
}

@Configuration
public class HelloRouter {
@Bean
public RouterFunction<ServerResponse> route(HelloHandler handler) {
return RouterFunctions.route(RequestPredicates.GET("/hello"), handler::hello);
}
}

5. 실무 고려 사항

  • 논블로킹의 연쇄: WebFlux 환경에서 중간에 블로킹 코드(예: 일반 JDBC, Thread.sleep)가 섞이면 성능이 급격히 저하됩니다. 모든 라이브러리가 논블로킹을 지원해야 효과를 볼 수 있습니다.
  • 이벤트 루프(Multi Event Loop): WebFlux는 적은 수의 고정된 스레드(보통 CPU 코어 수 * 2)로 수많은 요청을 처리하는 멀티 이벤트 루프 모델을 사용합니다. 특정 요청이 스레드를 오랫동안 점유하지 않도록 주의해야 합니다.
  • 백프레셔(Backpressure) 관리: 데이터를 소비하는 쪽의 속도가 공급하는 쪽보다 느릴 때 부하를 제어하는 방식이 포함되어 있어 시스템 안정성을 높일 수 있습니다.
  • 러닝 커브: 리액티브 프로그래밍은 기존 명령형 프로그래밍에 비해 사고의 전환이 필요하며 디버깅이 까다로울 수 있습니다.
Advertisement