[Spring] WebFlux
1. WebFlux 정의
- Spring5에서 처음 도입된 비동기, 논블로킹 웹 프레임워크 (Spring 5.0, Spring Boot 2.0 부터 사용 가능)
- 전통적인 Spring MVC가 동기적이고 블로킹 방식으로 요청을 처리하는 반면, WebFlux는 비동기 논블로킹 방식으로 동작하여 비동기적인 이벤트 지향 프로그래밍을 통해 높은 확장성과 성능을 제공
- 이는 특히 클라우드 네이티브 애플리케이션과 마이크로서비스 아키텍처에 적합
1.1 블로킹(Blocking)
- Spring MVC에서 사용되던 Blocking Request를 수행하면서 클라이언트에서 요청을 보내면 반환될 때까지 대기
- 아래 그림에서는 워커 스레드의 데이터베이스 작업이 진행되는 동안, 서블릿 스레드는 블로킹되어(정지 상태로) 대기함
- 워커 스레드가 데이터베이스 작업을 완료하면, 서블릿 스레드가 다시 깨어나서 클라이언트에 응답을 반환
1.2 논블로킹(Non-Blocking)
- webFlux에서 사용하는 Non-Blocking Request를 수행하면서 요청을 보내고 결과가 반환되지 않더라도 다른 작업을 수행할 수 있는것을 의미
- 서버는 DB와 비동기적으로 통신하며, 요청을 처리하는 동안 서버 스레드는 블로킹되지 않고 다른 작업을 계속 수행할 수 있음
- 데이터베이스 작업이 완료되면, 해당 작업의 결과가 이벤트 콜백(Event Callback)을 통해 서버에 전달되고, 이때서야 클라이언트에 응답을 반환
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) 수행 과정
단계 | 설명 |
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 서버로서, 효율적으로 동작하는 고성능 웹어플리케이션 개발에 사용