Java/Spring

[Spring] WebFlux

Yutory 2024. 8. 22. 15:57

1. WebFlux 정의

- Spring5에서 처음 도입된 비동기, 논블로킹 웹 프레임워크 (Spring 5.0, Spring Boot 2.0 부터 사용 가능)

- 전통적인 Spring MVC가 동기적이고 블로킹 방식으로 요청을 처리하는 반면, WebFlux는 비동기 논블로킹 방식으로 동작하여 비동기적인 이벤트 지향 프로그래밍을 통해 높은 확장성과 성능을 제공

- 이는 특히 클라우드 네이티브 애플리케이션과 마이크로서비스 아키텍처에 적합

 

1.1 블로킹(Blocking)

- Spring MVC에서 사용되던 Blocking Request를 수행하면서 클라이언트에서 요청을 보내면 반환될 때까지 대기

- 아래 그림에서는 워커 스레드의 데이터베이스 작업이 진행되는 동안, 서블릿 스레드는 블로킹되어(정지 상태로) 대기함 

- 워커 스레드가 데이터베이스 작업을 완료하면, 서블릿 스레드가 다시 깨어나서 클라이언트에 응답을 반환

출처: https://reflectoring.io/getting-started-with-spring-webflux/

 

1.2 논블로킹(Non-Blocking)

- webFlux에서 사용하는 Non-Blocking Request를 수행하면서 요청을 보내고 결과가 반환되지 않더라도 다른 작업을 수행할 수 있는것을 의미

- 서버는 DB와 비동기적으로 통신하며, 요청을 처리하는 동안 서버 스레드는 블로킹되지 않고 다른 작업을 계속 수행할 수 있음

- 데이터베이스 작업이 완료되면, 해당 작업의 결과가 이벤트 콜백(Event Callback)을 통해 서버에 전달되고, 이때서야 클라이언트에 응답을 반환

출처: https://reflectoring.io/getting-started-with-spring-webflux/

 

 

 

2. WebFlux가 생긴 이유

* 적은 자원으로 높은 동시성을 처리하고, 함수형 프로그래밍의 도입으로 인해 비동기 논블로킹 프로그래밍을 쉽게 구현하기 위해 생김

 

2.1 적은 양의 스레드와 최소한의 하드웨어 자원으로 동시성을 핸들링 하기 위해

* 전통적인 웹 애플리케이션

- 각 요청에 대해 하나의 스레드를 할당하여 처리하는 방식(Synchronous, Blocking)을 사용하는데, 이 방식은 많은 요청이 들어올 때 성능 문제를 일으킬 수 있음

- 각 스레드가 차지하는 메모리와 CPU 자원 사용량이 상당하기 때문에, 서버가 동시에 처리할 수 있는 요청의 수가 제한되기 때문

* Spring WebFlux

- 비동기(Asynchronous)와 논블로킹(Non-Blocking) 방식을 채택하여, 적은 수의 스레드로도 많은 요청을 동시에 처리할 수 있게 함 

- 비동기 방식에서는 요청이 처리되는 동안 스레드가 다른 작업을 수행할 수 있어, 동일한 하드웨어 자원으로 더 많은 요청을 처리할 수 있으므로 더 적은 하드웨어 자원으로도 더 높은 동시성을 핸들링할 수 있음 

 

2.2 함수형 프로그래밍 때문

* 함수형 프로그래밍

- 데이터의 변경 없이 함수를 사용해 애플리케이션의 로직을 작성하는 프로그래밍 패러다임

- Java는 원래 객체지향 언어로 설계되었지만, Java8에서 람다식을 도입하여 함수형 프로그래밍을 지원하기 시작함

- 람다식과 같은 함수형 프로그래밍 개념은 비동기 논블로킹 프로그래밍을 훨씬 더 쉽게 구현할 수 있는 기반이 됨

 

- 비동기적으로 동작하는 여러 함수를 조합하여 복잡한 작업을 간결하게 표현 가능

- Java 8에서 도입된 람다식 덕분에, Spring WebFlux와 같은 프레임워크는 비동기 논블로킹 애플리케이션을 구현하는 데 있어서 훨씬 더 직관적이고 효율적인 방법을 제공할 수 있게 됨

 

3. WebFlux의 주요 특징

3.1 비동기 논블로킹

- WebFlux는 Reactor 기반의 비동기 논블로킹 프로그래밍 모델을 제공함

- 비동기 논블로킹 모델은 I/O 작업 중 쓰레드가 블로킹되지 않기 때문에 고성능을 요구하는 애플리케이션에서 큰 이점을 제공함

 

3.2 함수형 프로그래밍 지원

- WebFlux는 함수형 프로그래밍 스타일을 지원

- 이는 람다 표현식과 메서드 참조를 활용하여 코드를 간결하게 작성할 수 있게 하며, 비즈니스 로직을 더욱 선언적으로 작성할 수 있게 함

 

3.3 서버 선택의 유연성

- Spring WebFlux는 다양한 서버 환경에서 실행될 수 있도록 설계되어있음

기본적으로 Netty 서버를 사용하지만, Undertow, Jetty와 같은 다른 서버도 선택 가능

- 이러한 유연성 덕분에 애플리케이션의 특성과 요구사항에 맞는 서버를 선택할 수 있다는 장점이 있음

 

4. Reactor

- Reactive 라이브러리 중 하나로 Netty 서버를 통해 비동기식 이벤트 기반의 서버 환경을 제공, 이를 이용하여 비동기 방식의 논블로킹 요청을 통해 이벤트 기반의 반응형 프로그래밍을 구현

동기식 이벤트 기반이란 요청이 들어올 때마다 해당 요청을 처리하기 위해 별도의 스레드를 사용하는 대신, 이벤트 루프라는 메커니즘을 이용해 하나의 스레드가 여러 요청을 효율적으로 처리하는 방식

- Publisher-SubScriber 패턴을 중심으로 동작하여 데이터를 생성하고 가공하고 구독자에게 전달하는 역할을 함

- Mono와 Flux라는 두 가지 주요 데이터 스트림 유형을 사용하여 단일 값(Mono)과 다중 값(Flux)을 처리

 

4.1 Publisher-SubScriber 패턴: 반응형 스트림(Reactive Stream)

- 비동기적 이벤트 기반 응용 프로그램을 위한 스트림 처리기술

- 해당 기술의 핵심은 Publisher가 Subscriber에게 데이터를 제공하는 것 (Publisher는 데이터를 생성하고 Subscriber는 이를 처리)

객체 설명
Publisher: 발행자 데이터를 생성하고, Subscriber에게 전송
Subscriber: 구독자 Publisher로부터 데이터를 받아들이고, 소비
Subscription: 구독 Subscriber가 처리할 데이터의 양을 정의

 

4.2 반응형 스트림(Reactive Stream) 수행 과정

 

출처: https://adjh54.tistory.com/232

 

단계 설명
1. subscribe - Subscriber를 Publisher에 등록하고 데이터 스트림을 수신할 준비가 되었음을 Publisher에게 알림
2. onSubscribe - Publisher가 Subscriber에게 데이터 스트림을 전송하기 전에 호출 됨
- 이 메서드를 통해 Subscriber는 Subscription 객체를 받아들여 데이터의 양을 제어할 수 있음
3. request(n) / cancel - request(n) 메서드는 Publisher에게 n개의 데이터를 요청하고, cancel 메서드는 데이터 스트림을 취소함
4. onNext (data) - Publisher가 생성한 데이터를 Subscriber에게 전달, 이 메서드는 데이터가 전송될때마다 호출
5. onComplete / onError - Publisher가 모든 데이터를 전송하고, 더 이상 데이터가 없을 때 호출
- onComplete 메서드는 모든 데이터가 성공적으로 전송됐음을 나타내며, onError 메서드는 데이터 전송 중 오류가 발생했음을 나타냄

 

 

4. Spring MVC와의 차이점

4.1 명령형 프로그래밍 VS 반응형 프로그래밍

- 명령형 프로그래밍의 경우 컴퓨터가 수행해야 하는 일을 명령의 목록으로 나열하고 이를 순서대로 처리

- 반응형 프로그래밍은 데이터 스트림을 처리하며 이 데이터 스트림이 변경될 때마다 반응함

분류 명령형 프로그래밍 반응형 프로그래밍
처리 방식 명령어를 순서대로 실행 데이터 스트림을 처리하며, 데이터 변화에 반응함
동기 / 비동기 대부분 동기적 대부분 비동기적
코드 구성 명령형 코드 선언형 코드
사용 예시 계산기, 루프 등 이벤트 처리, UI 업데이트 등
프로그래밍 종류 Spring MVC Spring WebFlux

 

4.2 Spring MVC VS Spring WebFlux

- Spring MVC는 여전히 대부분의 전통적인 웹 애플리케이션에 적합하지만, 대규모 트래픽 처리나 고성능이 요구되는 경우 Spring WebFlux가 더 적합할 수 있음

- WebFlux에 제약이 있다면 논블로킹으로 동작해야 하며, 블로킹 라이브러리가 필수적으로 사용되어야 한다면, 워커 스레드가 아닌 외부 별도의 스레드를 요청해야 함 (이는 요청을 처리하는 Event Loop가 절대 블로킹 되지 않아야 하기 때문에)

 

- Spring MVC는 1:1로 요청을 하기 때문에 트래픽이 몰리면 많은 쓰레드가 생겨나고, 쓰레드가 전환될 때 문맥 교환(Context Switching) 비용이 발생하게 되므로 쓰레드가 많을수록 비용이 커지지만, WebFlux는 Event-Driven과 Async Non-Blocking I/O를 통해 리소스를 효율적으로 사용할 수 있게 만들어줌

 

분류 Spring MVC Spring WebFlux
요청 처리 방식 동기(블로킹) 비동기(논블로킹)
쓰레드 모델 하나의 요청당 하나의 쓰레드

ex. 100명의 사용자가 동시에 웹 애플리케이션 요청을 하면 서버는 100개의 스레드를 생성하여 요청을 처리
(이는 많은 요청이 동시에 들어오면 서버의 성능이 급격히 저하될 수 있다는 단점이 있음)
이벤트 루프 기반

ex. 100명의 사용자가 동시에 요청을 보내더라도 서버는 몇 개의 스레드만으로 이 요청을 처리할 수 있음 
(비동기적으로 동작하기 때문에, 하나의 스레드가 요청을 처리하는 동안 블로킹 되지 않고 다른 작업을 수행하기 때문)
리액티브 스트림 지원 안 함 지원 (Reactor 사용)
적용 사례 대부분의 전통적인 웹 애플리케이션 고성능, 대규모 트래픽 요구 애플리케이션

 

4.1 WebFlux가 무조건 빠르고 좋은것인가?

- WebFlux 프로젝트의 비즈니스 로직들이 모두 Async + NonBlocking으로 되어 있다면 빠를 수 있음

- 그런데 이러한 코드를 작성하는 건 어려움

- 그렇기 때문에 MVC는 못해도 본전이지만 Reactive는 못하면 MVC 적용보다 못한 결과를 가져올 수 있음 (트래픽이 높은 서비스가 아니라면 MVC 만으로도 감당 가능)

 

 

5. Spring WebFlux의 주요 컴포넌트

5.1 Handler Functions

- WebFlux는 전통적인 Spring MVC의 @Controller와 @RequestMapping을 사용하는 방식 외에도 함수형 방식인 핸들러 함수를 제공

- 이 방식은 요청을 처리하는 로직을 함수형으로 작성할 수 있게 하여 코드의 가독성을 높이고 테스트를 용이하게 함

RouterFunction<ServerResponse> route = RouterFunctions.route(
    RequestPredicates.GET("/hello"),
    request -> ServerResponse.ok().bodyValue("Hello, WebFlux!")
);

5.2 WebClient

- WebClient는 Spring WebFlux에서 제공하는 비동기적인 방식으로 HTTP 요청을 보내고 응답을 받을 수 있는 라이브러리

- 비동기 논블로킹 방식으로 외부 API와의 통신을 가능하게 함, RestTemplate의 대안으로, 비동기 요청을 쉽게 처리할 수 있음

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

Mono<String> response = webClient.get()
    .uri("/data")
    .retrieve()
    .bodyToMono(String.class);

response.subscribe(System.out::println);

 

 

6. Spring WebFlux의 사용 사례

Spring WebFlux는 다음과 같은 용도로 사용함

6.1 실시간 데이터 스트리밍

- 예를 들어, 주식 시세나 소셜 미디어 피드의 실시간 업데이트.

6.2 마이크로서비스 간 통신

- 여러 마이크로서비스가 비동기적으로 상호작용하는 시스템 (서비스간 호출이 많은 MSA에 적합)

6.3 고성능 API 서버

- 대규모 트래픽을 처리해야 하는 API 서버로서, 효율적으로 동작하는 고성능 웹어플리케이션 개발에 사용