HTTP 200 OK

Memento mori & Carpe diem

CS

WebFlux

sjoongh 2022. 11. 6. 22:40

WebFlux란?

  • WebFlux는 Spring 5에 새롭게 추가된 Reactive-stack의 웹 프레임워크이다.
  • WebFlux는 클라이언트와 서버에서 리액티브 애플리케이션 개발을 위한 논블로킹 리액티브 스트림을 지원한다.
  • WebFlux 논블로킹으로 동작하는 웹 스택의 필요성 때문에 등장하게 되었다.
  • 블로킹 : 네트워크 통신에서 요청이 발생하고 완료될 때까지 모든 일을 중단한 상태로 대기
  • 논블로킹 : 네트워크 통신이 완료될 때까지 기다리지 않고 다른 작업을 수행

WebFlux로의 전환 이유

SpringMVC로 개발하고 동작이 잘 되던 서버를 굳이 왜 WebFlux로 전환할까? 전환을 한다면 이에 대한 합리적인 이유가 필요하다.

기존의 springMVC는 하나의 요청에 하나의 스레드가 사용된다.

  • 블로킹 콜 : 동작중인 스레드가 블로킹 상태가 되면 다른 스레드에서 CPU사용을 위해 문맥 교환이 일어나게 된다.
  • 많은 요청량 : 스레드 수가 증가해 서버가 감당해내지 못할 만큼의 메모리를 먹을 수 있다.
  • 위와 같은 에러 상황이 발생하기 때문에 전환이 필요할 수 있다.


WebFlux

리액티브 프로그래밍은 논블로킹과 고정된 스레드 수 만으로 모든 요청을 처리함으로 위의 문제들을 해결한다. 논블로킹 리액티브 웹 스택 중 하나인 WebFlux는 위의 리액티브의 특징들을 가진다.

서버는 스레드 한 개로 운영하며, 디폴트로 CPU 코어 수 개수의 스레드를 가진 워커 풀을 생성하여 해당 워커 풀 내의 스레드로 모든 요청을 처리한다. 제약이 있다면 논블로킹으로 동작하여야 하며, 블로킹 라이브러리가 필수적으로 사용되어야 하지만, 워커 스레드가 아닌 외부 별도의 스레드로 요청을 처리해야 한다(이는 요청을 처리하는 Event Loop가 절대 블로킹되지 않아야 하기 때문이다).

 

장점

  • 고성능, spring와 완벽한 통합, netty 지원, 비동기 non-blocking 메시지 처리

단점

  • 오류 처리가 복잡하다. back Pressure 기능 없음

전환이유

  • 내부 작업의 유형 : 내부 연산보다는 블로킹콜인 파일 업로드와 외부 api호출, db조회가 주된 작업일 경우
  • 리액티브 라이브러리 유무 : 사용하는 라이브러리들이 블로킹이라면 이는 쉽게 논블로킹으로 전환하기 어렵다.
  • 하나의 사용자 요청을 처리하기 위해 수십여개의 외부 시스템에 대한 요청이 필요한 시스템에서 가장 빠른 응답을 주기 위해
  • 수많은 요청을 처리해야 할 때 많은 스레드의 유지비용을 벗어나기 위해

단 Webflux 프로젝트의 모든 비즈니스 로직이 Async + NonBlocking로 적용되어야 효율적인 성능을 발휘할 수 있다.


WebFlux 전환

Reactor - Reactive API

  • Reacotr는 WebFlux에서 사용하는 리액티브 스트림 API의 구현체이다.
  • 고수준의 API Mono와 Flux를 제공한다

  • Mono는 0~1개의 데이터를 발행하는 Publisher 구현체이며, Flux는 0~N개의 데이터를 발행하는 Publisher 구현체이다.

Programming Model

Annotated Controller

SpringMVC와 같은 @Controller, @RequestMapping 어노테이션들을 활용한 방식의 프로그래밍 모델이다. 이 방법으로 택할 시, 변경할 사항이 가장 적었으며 MVC와 동일하게 spring-web 모듈의 어노테이션을 그대로 사용가능하다.

Functional Endpoints

WebFlux가 나오며 Spring에 추가된 프로그래밍 방식이다. 자바 8의 람다 표현식으로 자바에서 함수형 API를 작성할 수 있게되며, 이러한 방식으로 웹 서버를 개발할 수 있게 되었다. annotated controller와 다른 점으로는 애노테이션으로 의도를 선언하여 콜백받는 방식이 아닌, 요청을 애플리케이션이 처음부터 끝까지 다 제어한다(콜백받기 위해 어노테이션을 scan, mapping 과정이 생략되어서인지 애플리케이션 부팅 시간이 좀 더 빠르다 한다).

 

설정

MVC의 경우 WebMvcConfigurer 를 implements하고 원하는 추가 설정은 오버라이드하여 구현한다. WebFlux는 WebFluxConfigurer 를 implements하고 동일하게 원하는 설정을 오버라이드하여 구현하면 된다.

필터와 인터셉터

WebFlux는 필터와 인터셉터의 기능을 WebFliter 클래스로 제공한다. 인증과 Acl을 위해 구현되었던 인터셉터를 WebFliter로 전환하였다.


Mono와 Flux

  • spring Webflux에서 사용하는 reactive libray가 Reactor이고 Reactor가 Reactive Streams의 구현체이다.
  • Flux와 mono는 Reactor 객체이며, 차이점은 발행하는 데이터의 개수이다.
  • Flux : 0 ~ N 개의 데이터 전달
    • flux는 0-N 개의 데이터를 발행(전달, 방출) 할 수 있다. 하나의 데이터를 전달할 때마다 onNext 이벤트를 발생시킨다. Flux내의 모든 데이터의 전달 처리가 완료되면 onComplete이벤트가 발생하며, 데이터를 전달하는 과정에서 오류가 발생하면 onError 이벤트가 발생한다.
  • Mono : 0 ~ 1 개의 데이터 전달
    • ex : HTTP 요청은 응답을 하나만 생성하기 때문에 굳이 count 연산을 실행할 필요가 없다 HTTP 호출 결과를 Mono<HttpResponse>로 표현하면 아이템이 0 ~ 1개 일때 사용할만한 연산자를 제공하기에 Flux<HttpResponse>보다 더 낫다.
    • 최대 카디널리티를 바꿔주는 연산자로 타입을 변환할 수도 있다. 예를 들어 Flux엔 count 연산자가 있는데, Mono<Long>을 리턴한다.
    • 또한 Mono로 값은 필요 없고 완료 개념만 있으면 되는(Runnable과 유사) 비동기 처리도 표현할 수 있다. 비어있는 Mono<Void>를 사용해 만들면 된다.
  • 보통 여러 스트림을 하나의 결과로 모아 줄 때 Mono를
  • 각각의 Mono를 합쳐서 하나의 여러 개의 값을 처리 할 때 Flux를 사용한다.
  • Flux도 0 ~ 1개의 데이터 전달이 가능하지만 하나의 결과값만 받는 것이 명백한 경우 List나 배열을 사용하지 않는 것처럼, Multi Result가 아닌 하나의 결과셋만 받게 될 경우에는 불필요하게 Flux를 사용하지 않고 Mono를 사용한다.

리턴 타입 변환 T → Mono, Flux

컨트롤러와 서비스 메소드에서의 리턴 타입을 전환하였다. 기존에 일반 타입으로 반환하였다면, Reactor는 리액티브 스트림을 지원하는 Mono와 Flux를 제공하기에 리턴 타입도 Mono, Flux로 전환되어야 한다.

리액티브 라이브러리의 경우 이미 Mono와 Flux의 리턴타입을 가질것이며, 직접 데이터를 생성하는 경우라면 just() 메서드로 감싸서 전달하도록 한다.

비동기 작업의 순서유지를 위해 flatMap으로 체이닝

WebFlux 전환 시, 내부엔 블로킹이 존재하지 않는다. 작업들은 비동기적으로 처리될 것이며 라인 순서에 따른 실제 코드의 동작 순서는 일치하지 않게된다(메서드를 호출해도 실제 동작은 나중에 처리될것이기 때문이다). 이때 flatMap을 통해 이전 비동기 작업이 끝난 후 다음 로직들이 처리되도록 순서를 보장시켜줄 수 있다.

내부가 동기적인 동작이라면 map으로 체이닝

flatMap과 map의 차이점은 전달하는 함수의 리턴 타입이다. flatMap에 전달하는 함수의 리턴 타입은 Mono나 Flux와 같은 리액티브 API이며, 이는 비동기 동작이 있는 함수를 전달하기 위해서이다.

  • 하지만 블로킹될 필요가 없는 로직으로만 구성되고 데이터를 직접 생성한다면 map 함수를 통해 체이닝 할 수 있다. 그렇기에 map에 전달하는 함수는 일반적인 오브젝트(T)를 리턴한다.

예외 처리는 Mono.error(Throwable), onErrorXX로

WebFlux에서 리턴 타입은 Mono나 Flux로 구성된다. 그렇기에 예외를 던져야할 때 throw 대신 Mono.error API를 사용하자. Mono.error를 리턴 시키도록 수정하고 던지던 예외는 error() 메서드에 인자로 전달한다.

예외 처리 시 Exception을 발생시키는 것 이외에도 catch 실패 시 처리할 로직을 태울때도 있다. 그런경우는 onErrorXX API를 사용하자. 이는 예외 발생 시 다른 Mono나 Flux 형태로 리턴하여 반환할 수 있도록 한다.


블로킹 라이브러리

블로킹 라이브러리가 사용되어야 한다면 별도의 스레드 풀을 생성하여 이를 이용해야한다. 찾아보니 publishOn  연산자를 통해 연산을 다른 스레드 풀로 전환하여 사용할 수 있다고 하는데, 필자와 같은 경우 ThreadPoolTaskExecutor을 통해 별도의 스레드 풀을 생성하고 필요한 작업만을 할당 할 수 있다.

  • 필요한 작업만을 외부 라이브러리에서 동작할 수 있게 한다.

Blocking Call 검출

  • 리액티브 애플리케이션에서 블로킹 콜은 매우 치명적이기 때문에 BlockHound를 사용하여 내부 블로킹 콜을 검출할 수 있다.
  • BlockHound는 논블로킹으로 동작해야만 하는 스레드에 대해 블로킹 콜을 감지하여 예외를 발생시킨다. 이때 논블로킹으로 동작해야만 하는 스레드는 서버 역할의 하나의 메인 스레드와 CPU 코어 수 만큼 생성하는 워커 스레드들을 의미한다.
  • BlockHound 사용시 주의할 점은 스레드에서 블로킹 콜 여부를 감시하기 때문에 개발 환경이 아닌 리얼 환경에서 사용한다면 성능에 영향을 미치게된다. 그렇기에 개발 환경에서 적용하여 블로킹 콜이 없는지 최대한 테스트하고 리얼 환경에선 이 라이브러리를 사용해야 한다.
  • pathvariable 과 param 모두 @Param으로 넘길 수 있음
  • buildAndAwait 사용시 uri를 지정하지 않으면 core로 안넘어가는 에러 발생

 

'CS' 카테고리의 다른 글

API 헬스체크  (0) 2023.08.17
Connection pool  (0) 2022.12.06
pinPoint  (0) 2022.11.06
JWT 토큰이란  (0) 2022.05.28
SSR과 CSR 의 차이  (0) 2022.02.22