3. 스트림의 새로운 표준 - 리액티브 스트림

API 불일치 문제

CompletableStage를 이용하는 자바 코어 라이브러리와 RxJava 같은 다양한 라이브러리가 있어서, 코드를 작성할 때 다양한 선택을 할 수 있지만 과도하게 많은 선택지는 시스템을 지나치게 복잡하게 만들 수 있다.

핵심적인 문제는 라이브러리 공급자가 일관된 API를 만들어낼 수 있는 표준화된 방법이 없다는 사실이다.

풀 방식과 푸시 방식

리액티브 초기 단계에서 모든 라이브러리의 데이터 흐름은 소스에서 구독자에게 푸시되는 방식이었다.

  • 풀 방식으로 요소를 하나씩 요청할 경우 비동기 논블로킹 방식을 사용하더라도 각 요소에 대한 요청을 처리 하면서 대기시간이 발생하여 전체 처리시간 중 많은 시간을 유휴 상태로 있게 된다.

  • 푸시 방식을 도입하면서 요청하는 횟수를 최소화하여 전체 처리 시간을 최적화할 수 있었다.

하지만 푸시 모델만 사용하는 것은 기술적 한계가 있는데

  • 메시지 기반 통신의 본질은 요청에 응답하는 것인데
  • 프로듀서가 컨슈머의 처리 능력을 무시하면 전반적인 시스템 안정성에 영향을 미칠 수 있기 때문이다.

흐름제어

  • 느린 프로듀서와 빠른 컨슈머

    순수한 푸시 모델은 동적으로 시스템의 처리량을 증가시키는 것이 불가능하다.

  • 빠른 프로듀서와 느린 컨슈머

    프로듀서는 컨슈머가 처리할 수 있는 것보다 더 많은 데이터를 전송할 수 있으며 이로 인해 부하를 받는 컴포넌트에 치명적인 오류가 발생할 수 있다.

이를 해결하기 위한 직관적인 방법은 큐에 수집하는 것인데 3가지 유형으로 구분할 수 있다.

  1. 무제한 큐: 메모리 한도에 도달하면 전체 시스템이 손상될 가능성이 있다.(복원력이 떨어짐)
  2. 크기가 제한된 드롭 큐: 메시지의 중요성이 낮을 때 사용되는 방법으로 큐가 가득 차면 메시지를 무시하는데 중요한건 데이터 세트가 변경된다는 점이다.
  3. 크기가 제한된 블로킹 큐: 가장 느린 컨슈머의 처리량에 의해 시스템의 전체 처리량이 제한된다. 시스템의 비동기 동작을 모두 무효화하여 절대 받아들일 수 없는 시나리오다.

이런 시스템 부하에 적절하게 대응하는 방법으로 배압 제어 메커니즘이 있다.

리액티브 스트림의 기본 스펙

리액티브 스트림 스펙에는 Publisher, Subscriber, Subscription, Processor의 네 가지 기본 인터페이스가 정의돼 있다.

  • Publisher : Observable과 비교하면 Publisher와 Subscriber를 연결하기 위한 표준화된 진입점을 의미

  • Subscriber : Observer와 비슷한데 onSubscribe라는 추가 메서드를 제공하는데 Subscriber에게 구독이 성공했음을 알리는 API 메서드

  • Subscription : 원소 생성을 제어하기 위해 기본적인 사항을 제공

    • cancel() : 스트림에서 구독을 취소하거나 발행을 완전히 취소 가능

    • request(long n) : 요청하는 Publisher가 보내줘야 하는 데이터 크기를 알려줄 수 있음 ▶️ Publisher에서 유입되는 원소의 개수가 처리할 수 있는 제한을 초과하지 않을 것을 확신할 수 있다.

      리액티브 스트림은 순수 푸시 모델과는 달리 배압을 적절하게 제어할 수 있는 하이브리드 푸시-풀 모델을 제공한다.

      • 순수 푸시 모델을 사용하고 싶으면 최대 개수 요청 request(Long.MAX_VALUE)
      • 순수 풀 모델을 사용하고 싶으면 onNext()가 호출될 때마다 요청
  • Processor : Publisher와 Subscriber의 혼합 형태로 Publisher와 Subscriber 사이에 몇가지 처리 단계를 추가하도록 설계됐다.

1
2
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}

리액티브 스트림 기술 호환성 키트(TCK)

모든 동작을 검증하고 반응 라이브러리를 표준화하여 서로 호환하는지 확인하는 공통 도구로 모든 리액티브 스트림 코드를 방어하고 지정된 규칙에 따라 구현을 테스트 한다.

TCK github : https://github.com/reactive-streams/reactive-streams-jvm/tree/master/tck


리액티브 스트림을 활용한 비동기 및 병렬

  • 리액티브 스트림 API는 Publisher가 생성하고 Subscriber가 소비한 모든 신호는 처리 중에 논블로킹이어야 하며 방해받지 않아야 한다고 규칙에 명시되어 있다.

  • 모든 프로세서나 코어를 효율적으로 사용하려면 병렬처리가 필요하고 이는 일반적으로 onNext 메서드를 병렬로 호출하는 것을 뜻하지만 on*** 메서드의 호출은 스레드 안전성을 보장하는 방식으로 신호를 보내야 하며 다중 스레드에서 수행되는 경우 외부적인 동기화를 사용해야 한다. 즉, 스트림의 요소를 병렬 처리할 수 없다.

  • 자원을 효율적으로 활용하기 위해 스트림 처리 파이프의 각 단계에 메시지를 비동기적으로 전달하는 것이다.

    • 상황에 따라서 처리단계를 각각 별도의 스레드로 처리하고 각 스레드 사이에 큐와 같은 데이터 구조를 적용하여 메시지를 독립적으로 제공하고 사용하도록 할수 있다.

3. 스트림의 새로운 표준 - 리액티브 스트림

https://yoo0926.github.io/2021/11/28/book/spring5-reactive/3/

Author

Jaeyong Yoo

Posted on

2021-11-28

Updated on

2023-06-10

Licensed under

댓글