개발/TIL

WebFlux 란

ebang 2025. 7. 27. 21:30

본 정리는 스프링으로 시작하는 리액티브 프로그래밍, 황정식 저 서적을 읽고 정리한 내용입니다. 

 

1. 리액티브 스트림즈란

  1. 리액티브 시스템
    1. 리액티브 선언문에 핵심이 들어있다. 기본적으로  빠른 응답성을 바탕으로 유지 보수와 확장이 가능한 시스템을 의미한다. 
    2. 리액티브 선언문 :
      • 빙식 MEANS : 메시지 기반 통신 ,
      • 형태 FORM : 탄력성, 회복성,
      • 값 VALUE : 리액티브 시스템의 핵심 가치 (빠른 응답성을 바탕으로 유지 보수와 확장이 가능한 시스템)
       

  1. 리액티브 프로그래밍
    1. 리액티브 시스템을 구축하기 위한 프로그래밍.
    2. 리액티브 프로그래밍의 특징
      1. 선언형 프로그래밍 방식으로, 실행할 동작을 구체적으로 명시하지 않고 목표만 선언한다.
      2. 데이터 소스의 변경이 있을 때마다 데이터를 전파한다.
      3. 리액티브 프로그래밍 코드는 코드의 간결함과 가독성에 유리한 메서드 체인의 형태로 표현된다.
    3. 리액티브 프로그래밍의 구성 요소
      1. Publisher
      2. Subscriber
      3. Data Source
      4. Operator
  2. 리액티브 스트림즈
    1. 데이터 스트림을 non-blocking 이면서 비동기적인 방식으로 처리하기 위한 리액티브 라이브러리의 표준 사양.
    2. 구현체에는 RxJava, Reactor, Java 9 Flow API 등이 존재. (Spring Framework 와는 Reactor 가 가장 많이 사용되고 있다.)
    3. 리액티브 스트림즈 구성요소
      1. Publisher : 데이터를 생성하고 발행하는 역할
      2. Subscriber : 구독한 Publisher로부터 방출된 데이터를 받아서 처리하는 역할
      3. Subscription : Publisher에 요청할 데이터의 개수를 지정하고, 데이터의 구독을 취소하는 역할
      4. Processor : Publisher 와 Subscriber 의 기능을 둘다 가지고 있다.

 

 

2. Blocking IO 와 Non-Blocking IO

  1. Blocking IO

작업이 완료되기 까지 스레드가 차단된다.

이를 보완하기 위해 멀티 스레딩 기법이 존재하지만 context-switch 비용이 발생하고, 과도한 메모리 사용 문제, 스레드 풀 사용 시 유휴 스레드 부족으로 인해 응답지연 발생 가능한 문제가 존재한다.

  1. Non-blocking IO

한 스레드의 작업이 끝날 때까지 스레드가 차단되지 않는다.

단, cpu-bound 작업에서는 적합하지 않을 수 있다. 또한, 요청에서 응답까지의 과정 중 blocking 요소가 포함된다면, non-blocking 기법이 적합하지 않을 수 있다.

 

Spring WebMVC 는 Blocking IO 방식을, Spring WebFlux 는 Non-Blocking IO 방식을 사용하는 웹 애플리케이션 라이브러리다.

 

Spring WebMVC 는 서블릿 컨테이너 기반으로, 요청당 하나의 스레드를 사용하기 때문에 대량의 요청을 처리하기 위해서 과도한 스레드를 사용함으로써 CPU 대기시간이 늘어나고 메모리 사용시 오버헤드가 발생한다.

Spring WebFlux 는 Netty 와 같은 비동기 Non-Blocking IO 기반의 서버 엔진을 사용함으로써 적은 수의 스레드로 많은 수의 요청을 처리하기 때문에 적은 컴퓨팅 파워로 고성능의 애플리케이션을 운영할 수 있게 해준다.

 

3. Reactor

  • Spring Framework 팀의 주도하에 개발된 리액티브 스트림즈의 구현체. Spring WebFlux 기반의 리액티브 애플리케이션을 개발하기 위한 핵심역할을 담당한다.
  • 사용하는 Publisher 타입의 종류
    • Mono[0|1] : 한 건도 emit 하지 않거나 단 한 건만 emit 하는 데이터에 특화된 Publisher 타입
    • Flux[N] : 0개부터 무한대의 데이터를 emit 할 수 있는 Publisher 타입.
  • 데이터 emit 방식 - Cold, Hot
    • Cold : Subscriber 가 구독할 때마다 데이터 흐름이 처음부터 다시 시작되는 sequence
    • Hot : 데이터 흐름이 먼저 시작되었다가 Subscriber 가 구독하면 그 때 부터 발생되는 흐름의 데이터만 받는 sequence
      • share(), cache() 등의 Operator 를 사용해서 Cold Sequence 를 Hot sequence 로 변경할 수 있다.

4. Backpressure

Publisher 가 끊임없이 emit 하는 데이터를 적절히 제어해서, 데이터 처리에 과부하가 걸리지 않도록 제어하는 역할을 수행한다.

  1. 데이터 개수 제어하는 방식
  2. Reactor 가 제공하는 Backpressure 전략 사용

5. Scheduler

Reactor Sequence 에서 사용되는 스레드를 관리해주는 관리자 역할.

운영체제 레벨에서 프로세스의 라이프 사이클을 관리하는 것처럼, 어떤 스레드에서 무엇을 처리할 지 제어한다.

덕분에 개발자가 직접 스레드를 제어해야하는 부담에서 벗어날 수 있다.

*병렬성: 물리적인 코어에서 실제로 스레드가 동시에 수행되는 것.

*동시성 : 눈에 보이기에 여러 프로그램이 동시에 실행되는 것처럼 보이는 것.

Scheduler Operator 중에서도 subscribeOn, publishOn은 동시성을 가지는 논리적인 스레드에 해당하고 parallel() Operator 는 병렬성을 가지는 물리적인 스레드에 해당한다.

  • parallel() : 라운드 로빈 방식으로 CPU 코어 개수만큼의 스레드를 병렬로 실행한다.
    • 데이터를 사전 분배하는 Operator 이고, 실제 작업을 수행할 스레드의 할당은 runOn() Operator 가 담당한다.

Scheduler 의 종류

  1. Schedulers.immediate() : 별도의 스레드 추가 없이 현재 스레드에서 작업을 처리하고자 할 떄.
  2. Schedulers.single() : 스레드 하나만 생성해서 Scheduler 가 제거되기 전까지 재사용하는 방식
  3. Schedulers.newSingle() : 호출할 때마다 매번 새로운 스레드 하나를 생성.
  4. Schedulers.boundElastic() : blocking IO 작업에 대해 효과적으로 처리할 수 있는 방식. 기본적으로 cpu 코어수 * 10 개의 스레드를 생성, 최대 100000개의 작업이 큐에서 대기할 수 있다.
  5. Schedulers.parallel() : Non-Blocking IO 작업에 최적화됨. cpu 코어 수 만큼의 스레드를 할당.
  6. Schedulers.fromExecutorService() : 기존에 이미 사용하고 있는 ExecutorService가 있다면 그로부터 Scheduler 를 생성하는 방식.
  7. Schedulers.newXXX : 위의 스케줄러와 비슷하지만 커스텀해서 사용할 수 있다.

6. Reactor Sequence

7. Operator

Operator는 목적에 따라 구분할 수 있다. 그리고 공식문서에서 마블 다이어그램을 통해 각 동작 방식을 파악할 수 있다.

실제 WebFlux 를 구현하는데 가장 중요한 요소라고 할 수 있다. 

  1. Sequence 생성을 위한 Operator (justOrEmpty, fromIterable, fromStream, range, defer, using, generate, create)
  2. Sequence 필터링 Operator (filter, skip, take, next)
  3. Sequence 변환 Operator (map, flatMap, concat, merge, zip, and, collectList)
  4. Sequence의 내부 동작 확인을 위한 Operator (doOnXXXX() )
  5. 에러 처리를 위한 Operator (error, onErrorResume, onErrorContinue, retry)
  6. Sequence의 동작시간 측정을 위한 Operator (elapsed)
  7. Flux Sequence 분할을 위한 Operator (window, buffer, bufferTImeout, groupBy)
  8. 다수의 Subsriber에게 Flux를 멀티캐스팅하기 위한 Operator (publish, autoConnect, refCount)

 

8. Spring WebFlux

Spring WebFlux는 Spring 5.0부터 추가된 리액티브 웹 애플리케이션 구현을 위한 웹 프레임워크이다.

Reactor의 Publisher(Mono, Flux 등)를 Spring Web Application에서 사용하여 Non-Blocking 통신을 지원하는 기술이다.

Spring MVC 와 비교해서 WebFlux 가 가진 특징: 

Spring MVC

  • 클라이언트 요청이 들어올 대마다 서블릿 컨테이너(Apache Tomcat)의 스레드 풀에 미리 생성되어 있는 스레드가 요청을 처리하고 요청 처리를 완료하면 스레드 풀에 반납되는 스레드 모델을 사용한다.
  • Blocking I/O 방식이기 때문에 스레드가 차단되고 대용량의 스레드 풀을 사용해서 하나의 요청을 하나의 스레드가 처리한다.
  • 서버 API로 서블릿 API를 사용한다.
  • 보안 시 표준 서블릿 필터를 사용하는 Spring Security 가 서블릿 컨테이너와 통합된다.
  • 데이터 엑세스 시, Blocking I/O 방식인 Spring Data JDBC, Spring Data JPA, Spring Data MongoDB 같은 데이터 엑세스 기술을 사용한다.

Spring WebFlux

  • Non-Blocking I/O를 지원하는 Netty등의 서버 엔진에서 적은 수의 고정된 크기의 스레드(일반적으로 cpu 코어 개수만큼의 스레드) 를 생성해서 대량의 요청을 처리한다.
    • 비동기 프로세스 : 이벤트 루프를 기반으로, 등록해둔 작업이 다시 이벤트 루프에 푸쉬되면 콜백함수를 호출해서 처리 결과를 전달한다.
  • 서버 API로 리액티브 스트림즈 지원 (기본 서버 엔진은 Netty 이지만 Jetty나 Undertow 같은 서버 엔진에서 리액티브 스트림즈 어댑터 지원. )
  • 보안 시 WebFilter를 이용해 Spring Security를 Spring WebFlux에서 사용한다.
  • 데이터 엑세스 계층까지 Non-Blocking I/O 를 지원하는 Spring Data R2DBC 및 Non-Blocking i/O를 지원하는 NoSQL 모듈을 사용한다.

 

Spring WebFlux 의 핵심 컴포넌트

  • HttpHandler
  • WebFilter
  • HandleFilterFunction
  • DispatcherHandler
  • HandlerMapping
  • HandlerAdapter

기존의 Spring MVC 가 어노테이션 기반 컨트롤러를 지원하는 것처럼 WebFlux 역시 Mono 혹은 Flux 를 반환하는 어노테이션 기반 컨트롤러를 지원한다.

뿐만 아니라, WebFlux의 경우에는 함수형 엔드포인트를 지원한다.

 

 

정리하자면, 리액티브 시스템이란  빠른 응답성을 바탕으로 유지 보수와 확장이 가능한 시스템을 의미하며, Controller, WebClient, R2DBC 데이터 엑세스 계층을 활용해서, DB 엑세스부터 시작해서 Controller 단의 응답까지 모두 비동기로 동작하는 것이 기본적인 리액티브 스트림즈를 활용한 비동기처리의 핵심이다.  그리고 그 구현체 중 하나로 WebFlux 가 존재한다.