목표

  • 리액티브 프로그래밍을 이해한다.
  • Spring WebFlux 내부 동작 방식을 이해한다.

리액티브 프로그래밍

리액티브 프로그래밍

  • 비동기 이벤트 처리와 데이터 스트림에 대한 아이디어를 기반으로 한다.
  • 비동기 이벤트 처리는 다른 이벤트 처리를 차단하지 않는다는 것을 의미한다.
  • 이벤트 큐와 병렬 이벤트 처리를 도입하여 성능과 확장성을 향상시킨다.
  • 기본적으로 리액티브 프로그래밍은 3가지 구성요소를 가진 옵저버 패턴이다.(RxJava 기준)
    • Observable: 데이터 스트림을 표현한다. 한 스레드에서 다른 스레드로 전달할 수 있는 데이터를 담는다.
    • Observer: Observable이 방출하는 데이터 스트림을 소비한다. Observable이 데이터를 방출할 때마다 데이터를 통해 작업을 수행하거나 예외를 던진다.
    • Scheduler: ObservableObserver가 어떤 스레드에서 실행해야할지 알려주는 구성요소다.

리액티브 스트림

  • 위 3가지 구성요소를 가진 방식에는 문제점이 있다.
    • Observable에 발생 시키는 이벤트의 수나 타이밍을 제어할 수 없기 때문에, 예측할 수 없는 부하를 관리할 수 없다.
  • 리액티브 스트림은 non-blocking backpressure를 사용해서 위 문제를 해결한 표준을 제공한다.
  • 리액티브 스트림은 4개의 인터페이스인 Publisher, Subscriber, Subscription, Processor로 요약할 수 있다.
  • Publisher는 하나의 Subscription당 하나의 Subscriber에 발행하는 데이터는 생성한다.
  • Publisher 인터페이스에는 SubscriberPublisher를 구독 신청할 수 있는 subscribe() 메서드 한 개가 선언되어 있다.
  • Subscriber가 구독 신청되면 Publisher로부터 이벤트를 수신할 수 있다.
    • Subscriber가 수신할 첫 번째 이벤트는 onSubsribe() 호출을 통해 이루어진다.
    • PublisheronSubsribe()를 호출할 때 이 메서드의 인자로 Subscription 객체를 Subscriber에 전달한다.
    • SubscriberSubscription 객체를 통해서 구독을 관리할 수 있다.
  • SubscriberSubscriptionrequest()를 호출하여 전송되는 데이터를 요청하거나, 또는 더 이상 데이터를 수신하지 않고 취소한다는 것을 나타내기 위해 cancel()을 호출할 수 있다.
    • request()를 호출할 때 Subscriber는 받고자 하는 데이터 항목 수를 나타내는 long 타입의 값을 인자로 전달한다. 바로 이것이 _백 프레셔_이며, Subscriber가 처리할 수 있는 것보다 더 많은 데이터를 Publisher가 전송하는 것을 막아준다.
    • 요청된 수의 데이터를 Publisher가 전송한 후에 Subscriber는 다시 request()를 호출하여 더 많은 요청을 할 수 있다.
  • Subscriber의 데이터 요청이 완료되면 데이터가 스트림을 통해 전달되기 시작한다. 이 때 onNext() 메서드가 호출되어 Publisher가 전송하는 데이터가 Subscriber에게 전달되며, 만일 에러가 생길 떄는 onError()가 호출된다.
  • Publisher에서 전송할 데이터가 없고 더 이상의 데이터를 생성하지 않는다면 PublisheronComplete()를 호출하여 작업이 끝났다고 Subscriber에게 알려준다.
  • Processor 인터페이스는 Subscriber 인터페이스와 Publisher 인터페이스를 결합한 것이다.
    • Subscriber 역할로 Processor는 데이터를 수신하고 처리한다. 그다음에 역할을 바꾸어 Publisher 역할로 처리 결과를 자신의 Subscriber 들에게 발행한다.
  • 리액티브 스트림의 구현체로 RxJava, Reactor 등이 존재한다.
    • Reactor의 Flux, MonoPublisher 인터페이스의 구현체다.

Spring Webflux

사용법

  • Spring Webflux는 2가지 방식으로 API를 제공하고 있다.
    • 어노테이션 기반 컨트롤러: Spring MVC에서 제공하는 어노테이션을 사용을 지원.
    • 람다 기반: 함수형 프로그래밍 지원.

내부 동작

HttpHandler

  • HttpHandler: HTTP 요청을 논블록킹, 리액티브 스트림 백프레셔로 처리해주는 낮은 레벨의 인터페이스
  • Reator Netty, Undertow, Tomcat, Jetty 등 리액티브 스트림 웹 서버를 지원하기 위한 어댑터 클래스도 지원해주고 있다.

WebHandler

  • WebHandler: 어노테이션 기반 컨트롤러, 람다 기반 요청 처리를 지원하기 위한 높은 레벨의 핸들러
  • Spring WebFlux에서는 Spring MVC와 비슷하게 프론트 컨트롤러 패턴으로 설계되어, WebHandler의 구현체인 DispatcherHandler를 제공한다.
  • DispatcherHandlerDispatcherServlet과 비슷하게 동작한다.
    • 요청이 들어오면 HandlerMapping이 적절한 핸들러를 찾는다.
    • 핸들러가 발견되면 적잘한 HandlerAdapter를 통해 실행하고, 반환 값은 HandlerResult로 노출된다.
    • HandlerResult는 응답에 직접 쓰거나 렌더링할 뷰를 사용하여 처리하기 위해 적절한 HandlerReseulthandler에 제공된다.
  • DispatcherHandlerDispatcherServlet과 다르게 정체 과정이 리액티브 스트림으로 처리된다.

스레드 동작 방식

  • WebFlux 서버는 기본적으로 서버를 실행하기 위한 하나의 스레드와 요청 처리를 위한 여러 개의 다른 스레드(일반적으로 CPU 코어 수 만큼)로 동작한다.
  • 만약, 외부 서버 호출이나 DB 조회같은 오래동안 블로킹되는 API를 호출해야되는 경우 publishOn을 사용해서 다른 스레드풀에서 실행되도록 할 수 있다.

참고 자료