Reactive Programming
실전! 스프링 5를 활용한 리액티브 프로그래밍
왜 리액티브인가?
- 전통적인 개발 방법대로 작성했을 때 발생했던 문제?
- 시간 당 리퀘스트, 처리 시간, 스레드 수를 고려해서 초당 1000건 처리할 수 있을 거라고 가정하고 시스템을 작성했는데
- 블랙 프라이데이 등 트래픽이 몰릴 시 응답 시간 증가 / 서비스 중단
- 사용자 응답성에 영향을 미칠 수 있는 변화(갑작스러운 트래픽 변화 등)에 반응 하기 위하여 만족해야 하는 것?
- 탄력성(elasticity)
- 요청이 많아지면 시스템 처리량이 자동으로 증가했다가, 요청이 감소하면 자동으로 감소
- 수직적 또는 수평적 확장 ( Scale-Up, Scale-Out )
- 하지만 이게 어려운 경우가 있음 (“6장 웹플럭스 - 비동기 논블로킹 통신”에서 다룬다)
- 복원력(resilient) {탄성(resiliency)}
- 시스템의 한 부분에 장애가 발생해도 나머지 기능에는 문제가 없을 것
- 시스템의 기능 요소를 격리해 모든 내부 장애를 격리하여 독립성을 확보함으로써 달성
- 결제 서비스가 중단된 경우라도 일단 사용자 주문을 접수하고 이후 자동 재시도 하는 등
- 이 두가지를 만족하기 위한 프로그래밍 패러다임이 바로 Reactive
- 탄력성(elasticity)
- 전통적인 개발 방법이 어떤 점에서 사용자 응답성에 불리한지는 아래에.
메시지 기반 통신
- 보편적으로 스프링에서 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 Systems)라고 부릅니다.
- 왜 이름이 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
를 사용하는 방식- 이 것도 단점이 있음.[Java8] CompletableFuture
- 이러한 문제점들 때문에 스프링에서 리액티브 프로그래밍 지원을 위한 새로운 모듈을 구현하기로 했음. (이후 챕터에서 소개)
리액티브 프로그래밍의 단점?
- 공통적으로 얘기하고 있는 것이, “문제가 발생했을 때 코드 추적이 어렵다” 는 것
- 해당 이벤트를 구독하고 있는 observer들을 다 뒤져봐야 할 수도 있음
- 반대로 해당 이벤트를 발행하는 subject들을 다 뒤져봐야 할 수도 있고
리액티브 라이브러리 추상화 수준 정리
- Reactive Streams = spec 명세
- back pressure를 사용하는 async component 사이의 interaction을 정의하는 small spec이다.
- github.com/reactive-streams/reactive-streams-jvm
- 이 스펙의 구현체 가 reactive library.
- 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 동시에 잡기
This post is licensed under CC BY 4.0 by the author.