실전! 스프링 5를 활용한 리액티브 프로그래밍

 

왜 리액티브인가?

  • 전통적인 개발 방법대로 작성했을 때 발생했던 문제?
    • 시간 당 리퀘스트, 처리 시간, 스레드 수를 고려해서 초당 1000건 처리할 수 있을 거라고 가정하고 시스템을 작성했는데
    • 블랙 프라이데이 등 트래픽이 몰릴 시 응답 시간 증가 / 서비스 중단
  • 사용자 응답성에 영향을 미칠 수 있는 변화(갑작스러운 트래픽 변화 등)에 반응하기 위하여 만족해야 하는 것?
    • 탄력성(elasticity)
      • 요청이 많아지면 시스템 처리량이 자동으로 증가했다가, 요청이 감소하면 자동으로 감소
      • 수직적 또는 수평적 확장 ( Scale-Up, Scale-Out )
      • 하지만 이게 어려운 경우가 있음 ("6장 웹플럭스 - 비동기 논블로킹 통신"에서 다룬다)
    • 복원력(resilient)   {탄성(resiliency)}
      • 시스템의 한 부분에 장애가 발생해도 나머지 기능에는 문제가 없을 것
      • 시스템의 기능 요소를 격리해 모든 내부 장애를 격리하여 독립성을 확보함으로써 달성
      • 결제 서비스가 중단된 경우라도 일단 사용자 주문을 접수하고 이후 자동 재시도 하는 등
    • 이 두가지를 만족하기 위한 프로그래밍 패러다임이 바로 Reactive
  • 전통적인 개발 방법이 어떤 점에서 사용자 응답성에 불리한지는 아래에.

 

메시지 기반 통신

  • 보편적으로 스프링에서 HTTP를 이용해 컴포넌트 간의 통신을 수행하는 예제는 다음과 같다.
  • 하지만 RestTemplate은 Blocking I/O 기반의 API다. 그래서 getForObject를 호출하면 스레드A가 block된다.

  • 스레드를 여러개 띄워서 Blocking 되어 있는 동안 다른 스레드가 일하도록 하면 되는 것 아닌가?
    • Context Switching 비용 때문에 스레드를 무작정 많이 띄운다고 빨라지지는 않음
    • "6장 웹플럭스 - 비동기 논블로킹 통신"에서 다룬다
  • I/O 측면에서 리소스 활용도를 높이려면 비동기 논블로킹(async non-blocking) 모델을 사용해야 함
    • 현실에서 이런 종류의 커뮤니케이션은 문자 메시지
    • 분산 시스템에서도 서비스 간 통신에는 메시지 기반(message-driven) 통신 원칙을 따라야 함
      • 메시지 브로커(message broker)
    • 메시지 기반 통신을 사용하면 resiliency, elasticity가 향상됨
      • 한 수신 객체가 장애여도 다른 수신 객체가 메시지를 읽을 수 있으니 resiliency 향상
      • 메시지 대기열을 모니터링해 elasticity 제어 가능
      • "8장 클라우드 스트림으로 확장하기"에서 다룸

 

정리하면, 리액티브 시스템이란?

  • 분산 시스템으로 구현되는 모든 비즈니스의 핵심 가치는 응답성
  • 높은 응답성을 확보한다는 것은 곧 탄력성, 복원력을 가지고 있다는 것을 의미
  • 응답성, 탄력성, 복원력을 확보하기 위해 메시지 기반 통신을 사용
  • 리액티브 선언문  
    • 즉, 응답이 잘 되고, 탄력적이며 유연하고 메시지 기반으로 동작하는 시스템 입니다. 우리는 이것을 리액티브 시스템(Reactive Systems)라고 부릅니다.
      • 사실 응답성, 탄성력, 복원력을 확보하기 위한 가장 핵심적인 개념은 BackPressure라고 생각함.
      • 단순히 메시지 기반 통신을 쓴다고 해서 다 Reactive는 아니니까.
  • 왜 이름이 Reactive인가? 트래픽이 몰릴 때 Reactive하게 동작하면서 요청을 제대로 처리해낸다는 컨셉이기 때문.
    • 트래픽이 몰릴 때 어떻게 안터지고 Reactive하게 동작할거냐? => BackPressure
  • BackPressure란?
    • 기존 옵저버 패턴은 Push 방식으로, Publisher가 보내고 싶으면 막 보내는 반면 (이러면 터지거나 거절내려주거나.)
    • BackPressure는 Pull 방식으로, Subscriber가 Publisher한테 자기가 처리할 수 있는 만큼만 요청하는 방식!
    • 구독자가 자기가 처리할 수 있는 만큼만 데이터를 당겨오는 방식이 백 프레셔다.
    • Define "Reactive" - Spring docs. Reactive의 본질과 BackPressure에 대한 내용 까지.

 

Publisher 구분

projectreactor.io/docs/core/release/reference/#reactor.hotCold

  • hot publisher : 구독자가 없어도 계속 데이터 생산하는 데이터 시퀀스. e.g., 센서
  • cold publisher : 구독자가 있어야 데이터를 생산하는 데이터 시퀀스

 

왜 리액티브 스프링인가?

  • 지금까지는 시스템, 아키텍쳐 관점에서의 리액티브 시스템에 대해서 얘기한 반면, 구현 관점에서의 프로그래밍 기술로서의 리액티브 프로그래밍도 있음
  • "큰 시스템은 더 작은 규모의 시스템으로 구성되기 때문에 구성 요소의 리액티브 특성에 의존합니다. 즉, 리액티브 시스템은 설계 원칙을 적용하고, 이 특성을 모든 규모에 적용해 그 구성 요소를 합성할 수 있게 하는 것을 의미합니다."
    • 따라서 구성 요소 수준에서도 리액티브 설계 및 구현을 제공하는 것이 중요
    • 결국 리액티브 프로그래밍을 통해 리액티브 시스템을 작성해야 함

 

서비스 레벨에서의 반응성

  • 전통적인 방식인, 명령형 프로그래밍(imperative programming) 방식
    • imperative
    • `` calculate()`` 메서드에서 스레드가 blocking된다는 문제가 있음
    • 위에 서술했듯 스레드가 blocking 된다고 추가적인 스레드를 만드는 것은 리액티브 시스템의 관점이 아님
  • 콜백(callback) 방식
    • callbacks
    • `` SyncShoppingCardService``는 동기식이라 성능상 별 이점은 없음. 어차피 람다를 넘겨도 메서드 내에서 blocking이 걸릴 테니까
    • `` AsyncShoppingCardService``는 비동기식이라 별도 스레드에서 요청을 처리하게 됨
    • 이러한 콜백 방식의 장점은 동기,비동기 상관 없이 컴포넌트가 콜백 함수에 의해 분리된다는 것에 있음
      • 응답 이후 작업을 callback 람다로 넘겨 관심사를 분리
      • `` calculate()`` 자체는 void를 리턴하므로 클라이언트 메서드에서는 리턴값과 상관 없이 이후 로직을 작성할 수 있음
    • 하지만 콜백 지옥을 피할 수 없음
  • `` Future / CompletableFuture``를 사용하는 방식
  • 이러한 문제점들 때문에 스프링에서 리액티브 프로그래밍 지원을 위한 새로운 모듈을 구현하기로 했음. (이후 챕터에서 소개)

 

 

리액티브 프로그래밍의 단점?

  • 공통적으로 얘기하고 있는 것이, "문제가 발생했을 때 코드 추적이 어렵다"는 것
    • 해당 이벤트를 구독하고 있는 observer들을 다 뒤져봐야 할 수도 있음
    • 반대로 해당 이벤트를 발행하는 subject들을 다 뒤져봐야 할 수도 있고

 

리액티브 라이브러리 추상화 수준 정리

LINK

  • Project Reactor = 구현체(reactive library)
    • Mono와 Flux API 및 다양한 operator를 지원함.
    • reactive stream 명세에 따라 구현한 library니까, 모든 operator는 non-blocking back pressure를 지원함.
    • 또 다른 reactive library로는 RxJava 등이 있음.
  • Spring WebFlux는 Reactor를 코어 디펜던시로 사용함.
    • 하지만 Reactive Streams기반의 다른 reactive library와도 상호 작용 가능함. 당연하지 같은 스펙이니까

 

좋은 링크

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

Spring WebFlux와 Armeria를 이용하여 Microservice에 필요한 Reactive + RPC 동시에 잡기

 

'Java Stack > Java-async' 카테고리의 다른 글

[Java8] CompletableFuture  (0) 2020.10.13
Netty  (2) 2020.03.12