지나공 : 지식을 나누는 공간

Reactive Stream과 구현체 : Project Reactor, RxJava, WebFlux 본문

Tech/Reactive - Rxjava & Reactor

Reactive Stream과 구현체 : Project Reactor, RxJava, WebFlux

해리리_ 2022. 5. 9. 01:21

전에 RxJava 첫 포스팅에서 Reactive Stream에 대해 적었는데 여기서 한번 더 정리하고, 그것들과 관련된 Reator와 RxJava, WebFlux 등이 어떤 건지 알아본다.

 

Reactive Streams

Reactive Streams is a standard for asynchronous data processing in a streaming fashion with non-blocking back pressure.

 

논블로킹 백프레셔를 이용한 비동기 데이터 처리의 표준이라고 말하고 있다. 그렇다면 아래 세 가지를 더 알아봐야 한다.

  • 스트리밍 처리
  • 비동기 방식
  • 백 프레셔
  • 표준. 뭐가 표준이란거??

 

스트리밍 처리

전통적인 데이터 처리 방식

전통적인 데이터 처리 방식

  • 요청이 오면 저장소에 쿼리해서 모든 데이터를 메모리로 가져온(적재한) 후에야 4번 같이 다음 처리를 한다. 추가로 필요한 데이터가 있다면 이것도 전부 메모리에 적재해야 한다.
  • 전달된 데이터는 물론 저장소에저 조회한 데이터까지 모든 데이터가 애플리케이션 메모리에 적재되어야만 응답 메시지를 만들 수 있다.
    • 만약 필요한 데이터 크기가 메모리 용량보다 크다면 out of memory 에러가 발생하고, 순간적으로 많은 요청이 몰릴 때에도 다량의 GC(Garbage Collection)가 발생하면서 정상응답하지 못하는 경우가 나타난다.

스트리밍 처리 방식

스트리밍 처리방식

  • 크기가 작은 시스템 메모리로도 많은 양의 데이터를 처리할 수 있다. 메모리에 다 올릴 필요가 없기 때문에.
  • 입력 데이터에 대한 파이프 라인을 만들어서 데이터가 들어오는 대로 물 흘듯이 구독(subscribe)하고, 처리(process)한 뒤 발행(publish)까지 한번에 연결해서 처리한다.

 

비동기 방식

동기 방식에서는 클라이언트가 서버에 요청을 보내면 응답을 받기 전까지 blocking 된다.

  • 즉 현재 스레드가 다른 일을 하지 못하고 기다린다. 
  • 따라서 두 개의 요청을 A와 B 서버로 보내면 A의 응답이 끝나야만 B서버에게 요청을 보낼 수 있다.

반면 비동기 방식에서는 현재 스레드가 블로킹되지 않기 때문에 다른 일을 계속할 수 있다.

  • A서버에게 요청을 보낸 뒤 다른 일을 처리할 수도 있고, 혹은 B 서버에게 또다른 요청을 보낼 수도 있다.

그래서 비동기 방식이 동기 방식과 달리 갖는 장점으로는 아래와 같다.

  1. 속도가 빠름 : 두 개의 요청을 동시에 보낼 수 있으니 더 빠른 응답 속도를 보임.
  2. 적은 리소스 사용 : 현재 스레드가 블로킹되지 않고 다른 업무를 처리할 수 있어서 더 적은 수의 스레드로 더 많은 양의 요청을 처리할 수 있다.

백프레셔

Observer Pattern, Push 방식, Pull 방식을 알아봐야 한다.

푸시 방식

옵저버 패턴에서는 발행자가 구독자에게 밀어 넣는 푸시 방식으로 데이터가 전달됨. 발행자는 구독자의 상태를 고려하지 않고 데이터를 전달하는 데에만 충실하므로, 만약 발행자의 속도를 구독자의 처리 속도가 따라가질 못한다면 버퍼에 계속 쌓일 것이다. 그러다가 버퍼에 오버플로가 발생할 수 있다.

 

풀 방식

풀 방식에선 구독자가 10개를 처리할 수 있다면 10개만 딱 요청할 수 있다. 그러면 발행자는 요청받은 만큼만 전달하고, 구독자는 더 이상 out of memory 에러를 걱정하지 않아도 된다. 

 

백프레셔

폴 방식은 전달되는 모든 데이터의 크기를 구독자가 결정한다. 다이나믹 풀 방식은 좀 더 탄력적으로, 구독자가 이미 8개의 일을 처리하고 있으면 2개만 더 요청해서 현재 처리 가능한 범위 내에서만 메시지를 받는다. 다이나믹 풀 방식으로 요청해서 구독자가 수용할 수 있는 만큼의 데이터만 요청하는 게 백프레셔 방식이다.

 

Reactive Streams / 표준

Netflix는 RxJava, Pivotal은 WebFlux, Lightbend는 분산처리 actor 모델을 구현한 회사이다.

모두 스트림 API 가 꼭 필요한 회사였는데, 스트림은 유기적으로 엮어서 흘러야 의미가 있고, 데이터가 지속적으로 흐르기 위해서는 서로 다른 회사가 공통의 스펙을 설정하고 구현해야 한다. 그래서 표준화가 필요했다.

 

Reactive Streams는 표준화된 스트림 API이다. 내부는 아주 간단히 아래 API 들의 조합으로 구성되어 있다.

  • Publisher
  • Subsriber
  • Subscription
public interface Publisher<T> {
   public void subscribe(Subscriber<? super T> s);
}
 
public interface Subscriber<T> {
   public void onSubscribe(Subscription s);
   public void onNext(T t);
   public void onError(Throwable t);
   public void onComplete();
}
 
public interface Subscription {
   public void request(long n);
   public void cancel();
}

 

Reactive Stream의 사용 흐름은,

  1. Subsriber가 subsribe()를 통해 Publisher에게 구독을 요청하고
  2. Publisher는 onSubsribe()를 통해 Subsriber에게 Subsription을 전달하고
  3. Subscription은 Subsriber - Publisher 간 통신 매개체가 된다. 즉 Subsriber는 Publisher에게 직접 데이터 요청을 하지 않고 Subsription의 request 함수를 통해 Publisher에게 전달한다.
  4. Publisher는 Subsription을 통해 Subsriber의 onNext에 데이터를 전달하고, 작업이 완료되면 onComplete, 에러가 발생하면 onError 시그널을 전달한다.
  5. Subsriber와 Publisher, Subscription이 서로 유기적으로 연결되어 통신을 주고받으면서 subsribe 부터 onComplete 까지 연결되고, 이를 통해 백프레셔가 완성된다.

Reacrtive Stream 전체 흐름

Reactive Streams의 구현체들

Spring Frameswork + Reactor + Rx

Reactive Streams에는 다양한 구현체가 존자해고 서로의 특성이 조금씩 다르므로 필요에 따라 고르면 된다.

  • RxJava, Reactor Core : 순수하게 스트림 연산 처리
  • Armeria, Spring WebFlux : 웹프로그래밍과 연결된 Reactive Streams가 필요할 때

RxJava는 JVM을 위한 Reactive X 라이브러리로, 넷플릭스가 공개했다. RxJava1은 Reactive Streams 표준이 정의된 이전에, RxJava2는 이후에 나왔는데 backpressure를 Flowable만 지원하고 있다. Reactive Streams에 대한 브릿지(toPublisher, toObservable, toSingle...)가 있고, Reactive 분류 체계에서 2세대 라이브러리로 되어 있다.

 

Reactor는 Spring 팀이 만든 Java 프레임워크다. Reactive Streams 상에서 직접 빌드되므로 브릿지가 필요 없고, Reactor IO 프로젝트는 Netty, Aeron과 같은 저수준 네트워크 런타임에 대한 래퍼를 제공한다. Reactive 세대 분류 체계에서 4세대 라이브러리에 해당된다.

 

RxJava와 Reactor / WebFlux

RxJava에서 publisher의 역할을 하는 애들이 Observable, Flowable, Single, Maybe 등인데 Reactor에서는 이런 역할을 하는 게 Mono와 Flux다.

RxJava에서는,

  • 한 건의 데이터만 통지할 때 Single
  • 데이터를 한 건도 통지하지 않거나 한 건만 통지할 때는 Maybe
  • 데이터를 한 건 이상 통지할 때 Observable 또는 Flowable

Reactor에서는 이걸 좀 더 단순화해서,

  • 한 건만 처리하는 건 Mono
  • 한 건도 통지하지 않거나 한 건 이상 통지하는 건 Flux

로 구현해놨다.

 

WebFlux는 Web에서 사용하는 Flux로, Spring MVC가 웹 계층에 특화된 웹 프레임워크인 것처럼 WebFlux는 웹 계층에서 비동기 처리를 하는 데에 특화된 리액티브 프로그래밍 기반의 웹 프레임워크다. 그러므로 우리가 WebFlux를 잘 쓰려면 Flux, 즉 Reactor를 먼저 이해해야 한다. Reactor의 Mono와 Flux 타입을 보면 둘다 backpressure-ready를 구현한다. 

 

근데, Reactive Stream을 잘 알려면 스트림을 알아야 한다...! 스트림을 알아보자 곧.ㅎㅎ

 

참고

https://engineering.linecorp.com/ko/blog/reactive-streams-with-armeria-1/

 

https://intrepidgeeks.com/tutorial/help-beginners-develop-open-source-armeria-2-streammessage-analysis

728x90
Comments