리액티브, 리액티브 시스템, 리액티브 프로그래밍, 리액티브 스트림즈......?!

ssongkim·2023년 7월 30일
0
post-custom-banner

Overview

Spring Webflux에 대해 제대로 이해하고자 합니다.

스프링 웹플럭스가 그렇게 좋다던데,, 빠르다던데,, 언제나 Spring MVC보다 절대적으로 좋을까요?

Spring Webflux 를 제대로 알고 사용하려면 왜 쓰는지를 알아야한다고 생각합니다.

자바 언어를 이용하여 Spring Webflux를 사용하고자 한다면 리액티브라는 키워드를 접하게 되며 대표적인 리액티브 스트림즈 구현체인Reactor를 사용하게 됩니다.

오늘은 스프링 웹플럭스를 제대로 알아보고자 먼저 명령형 프로그래밍 선언형 프로그래밍, 함수형 프로그래밍, 리액티브 프로그래밍, 리액티브 시스템 등 헷갈리는 용어를 정리해보려고 합니다.

1. 명령형 프로그래밍 / 선언형 프로그래밍 차이

자바는 객체지향 프로그래밍 언어로 대표적인 명령형 프로그래밍의 일종입니다.

명령형 프로그래밍이란 프로그램의 상태와 상태를 변경시키는 구문의 관점에서 연산을 설명하는 프로그래밍 패러다임의 일종입니다. 쉽게 설명하자면, 컴퓨터가 수행할 명령들을 순서대로 써 놓은 것이라고 볼 수 있습니다.

명령형 프로그래밍에서는 상태(state)가 중요한 역할을 하며 프로그램의 상태를 직접 변경하는 명령어들이 많이 사용됩니다. 이로 인해 프로그램이 길고 복잡해지며, 디버깅과 유지보수가 어려워질 수 있습니다. 또한, 여러 명령어가 연속적으로 실행되는 경우 이들 간의 의존성이 증가하며 프로그램의 가독성이 떨어질 수 있습니다.

자바로 선언형 프로그래밍을 하려는 노력이 있었습니다, 대표적으로 자바8 부터 지원하는 Stream API가 있습니다.

명령형 프로그래밍

	@Test
    @DisplayName("명령형 프로그래밍")
    void 명령형_예제()
    {
        String name = "Craig";
        String capitalName = name.toUpperCase();
        String greeting = "Hello, " + capitalName + "!";
        System.out.println(greeting);
    }

명령형 프로그래밍은 개발자가 작성한 코드가 데이터를 당겨와 계산하는 pull 방식입니다.

선언형 프로그래밍

	@Test
    @DisplayName("선언형 프로그래밍")
    void 선언형_예제()
    {
        Stream.of("Crag")
                .map(String::toUpperCase)
                .map(cn -> "Hello, " + cn + "!")
                .forEach(System.out::println);
    }

명령형 프로그래밍은 어떤 작업을 처리하기 위해 실행할 동작을 코드에 구체적으로 명시하는 방식이고, 선언형 프로그래밍은 실행할 동작을 구체적으로 명시하지 않고 목표만 선언하는 방식입니다.

자바 스트림은 JDK 8부터 도입된 기능으로, 컬렉션(Collection)을 처리하는 기능을 제공합니다. 스트림은 데이터 요소들의 연속적인 흐름으로, 함수형 프로그래밍(선언형 프로그래밍 일종)의 개념을 도입하여 데이터 처리를 수행합니다.

참고로 자바 스트림은 만들어진 스트림에서 데이터를 당겨와 계산하는 pull 방식입니다.

2. 리액티브

소프트웨어를 둘러싼 요즘의 환경은 과거와 많이 달라졌습니다. 인터넷 환경이 발달하면서 트래픽이 전에 비해 엄청나게 증가하여 이전의 소프트웨어 아키텍처는 오늘날의 요구사항에 쉽게 부응하지 못합니다.

요즈음 사용자는 밀리초에 해당하는 빠른 응답 시간과 100%의 가동률을 기대합니다. 요청하는 데이터는 페타 바이트 단위로 측정됩니다.

리액티브란 변화에 민감하게 반응하는 것을 의미합니다. 리액티브는 변화하는 데이터나 이벤트에 대해 신속하고 효율적으로 반응하는 것을 중심으로 구축된 프로그래밍 모델을 가리킵니다.

3. 리액티브 시스템

리액티브 시스템은 리액티브한 원리와 원칙을 적용하여 개발된 소프트웨어 시스템을 의미합니다. 이러한 시스템은 주로 빠른 응답성과 내결함성을 강조하며, 비동기적인 처리, 메시지 기반 아키텍처, 이벤트 주도 구조 등을 활용하여 확장성과 탄력성을 갖출 수 있습니다.

우리는 응답이 잘 되고(클라이언트의 요청에 즉각적으로 응답하고), 탄력적이며 유연하고 메시지 기반으로 동작하는 반응을 잘하는 시스템 을 기대합니다. 우리는 이것을 리액티브 시스템(Reactive Systems)라고 부릅니다.

리액티브 시스템 특징

리액티브 선언문은 리액티브라는 용어의 의미를 올바르게 정의하기 위해 노력하는 사람들이 만든 리액티브 시스템 구축을 위한 일종의 설계 원칙이자 리액티브 시스템의 특징이라고 할 수 있습니다.

MEANS(방법)는 리액티브 시스템에서 주요 통신 수단으로 무엇을 사용할 것인지 표현한 것입니다. 그림에 나와있는 비동기 메시지 기반의 통신을 통해 구성요소들 간의 느슨한 결합, 격리성, 위치 투명성을 보장합니다.

FORM(형태)은 메시지 기반 통신을 통해서 어떠한 형태를 지니는 시스템으로 형성되는지를 나타냅니다. 그림에서는 리액티브 시스템이 비동기 메시지 통신 기반하에 탄력성과 회복성을 가지는 시스템이어야 함을 보여줍니다.

VALUE(가치)는 비동기 메시지 기반 통신을 바탕으로 한 회복성과 예측 가능한 규모 확장 알고리즘을 통해 시스템의 처리량을 자동으로 확장하고 축소하는 탄력성을 확보함으로써 즉각적으로 응답 가능한 시스템을 구축할 수 있음을 의미합니다.

1. 응답성(Responsive)

시스템이 가능한 한 즉각적으로 응답하는 것을 응답성이 있다고 합니다.

응답성 있는 시스템은 신속하고 일관성 있는 응답 시간을 제공하고, 신뢰할 수 있는 상한선을 설정하여 일관된 서비스 품질을 제공합니다. 이러한 일관된 동작은 오류 처리를 단순화하고, 일반 사용자에게 신뢰를 조성하고, 새로운 상호작용을 촉진합니다.

2. 탄력성(Resilient)

시스템이 장애 에 직면하더라도 응답성을 유지 하는 것을 탄력성이 있다고 합니다.

탄력성은 복제, 봉쇄, 격리, 위임에 의해 실현됩니다. 장애는 각각의 구성 요소 에 포함되며 구성 요소들은 서로 분리되어 있기 때문에 이는 시스템이 부분적으로 고장이 나더라도, 전체 시스템을 위험하게 하지 않고 복구 할 수 있도록 보장합니다.

3. 유연성(Elastic)

시스템이 작업량이 변화하더라도 응답성을 유지하는 것을 유연성이라고 합니다. 리액티브 시스템은 입력 속도의 변화에 따라 이러한 입력에 할당된 자원을 증가시키거나 감소키면서 변화에 대응합니다.

4. 메시지 주도(Message Driven)

리액티브 시스템은 비동기 메시지 전달 에 의존하여 구성 요소 사이에서 느슨한 결합, 격리, 위치 투명성 을 보장하는 경계를 형성합니다.

이 경계는 장애 를 메시지로 지정하는 수단을 제공합니다. 명시적인 메시지 전달은 시스템에 메시지 큐를 생성하고, 모니터링하며 필요시 배압 을 적용함으로써 유연성을 부여하고, 부하 관리와 흐름제어를 가능하게 합니다.

논블로킹 통신은 수신자가 활성화가 되어 있을 때만 자원 을 소비할 수 있기 때문에 시스템 부하를 억제할 수 있습니다.

이벤트가 발생하거나 요청이 들어오면 해당 메시지를 큐에 넣고, 수신 컴포넌트들은 이 메시지를 비동기적으로 받아 처리합니다. 이러한 방식으로 컴포넌트 간의 결합도를 낮추고 확장성과 유연성을 강화할 수 있습니다.

리액티브 시스템은 데이터나 이벤트가 발생하면 즉시 반응하여 처리하는 것이 중요합니다. 이를 위해 변화 전파를 하는 과정에서 push 방식을 사용합니다.

리액티브 시스템은 메시지 중심(Message Driven)입니다. 메시지 중심과 이벤트 중심은 다른 용어입니다. (리액티브 프로그래밍은 이벤트 중심)

자세한 내용을 보고자 한다면 리액티브 선언문을 보러가보세용

4. 리액티브 프로그래밍

리액티브 프로그래밍은 선언형 프로그래밍의 일종으로 데이터 흐름(dataflow)과 변화 전파에 반응하여 처리하는데 중점을 둔 프로그래밍 패러다임(programming paradigm)입니다.

선언형 프로그래밍이기 때문에 제어 흐름보다는 데이터가 흘러가는 파이프라인이나 스트림에 중점을 둡니다.

이것은 프로그래밍 언어로 정적 또는 동적인 데이터 흐름을 쉽게 표현할 수 있어야하며, 데이터 흐름을 통해 하부 실행 모델이 자동으로 변화를 전파할 수 있는 것을 의미합니다.

우리는 리액티브 프로그래밍을 이용하여 리액티브 시스템을 구축하는데 사용할 수 있습니다.

리액티브 프로그래밍은 옵저버 패턴에 많은 영감을 받았다고 합니다.
옵저버 패턴이란 GoF가 소개한 디자인 패턴 중 하나로 관찰 대상이 되는 객체가 변경되면 대상 객체를 관찰하고 있는 옵저버(Observer)에게 변경사항을 통지(notify) 하는 디자인 패턴을 말합니다.
리액티브 프로그래밍은 옵저버 패턴의 서브젝트와 옵저버의 개념과 유사한 발행자와 구독자를 사용해 데이터를 통지하고 처리합니다. 데이터를 제공하는 측에서 데이터를 소비하는 측에 통지하는 방식을 일반적으로 푸시 기반 (Push-Based)이라고 부릅니다.

변화의 전파와 데이터 흐름이란 데이터가 변경 될 때마다 이벤트를 발생시켜서 데이터를 계속적으로 전달함을 의미합니다.
즉 리액티브 프로그래밍은 이벤트 중심형 패러다임으로, 이벤트 소스가 바뀌면 그 이벤트를 방출하는 push 방식이라고 볼 수 있습니다.

5. 리액티브 스트림즈란

리액티브 프로그래밍을 구현한 라이브러리들이 정해진 표준없이 구현되기 시작했습니다. 이에 하나의 규칙을 정해서, 여러 리액티브 프로그래밍 구현체들이 상호 변환 가능하도록 만들자는 목소리가 나오기 시작했습니다.

그렇게 만들어진 규칙이 리액티브 스트림즈입니다.

리액티브 스트림은 non-blocking(넌블럭킹), backPressure(역압)을 이용하여 리액티브 프로그래밍을 할 때 비동기 데이터 처리의 표준이 되는 스펙입니다.

대표적인 리액티브 스트림즈 표준을 따르는 구현체들은 RxJava, Reactor, Akka 등이 있습니다.

BackPressure란

과부하 상태의 컴포넌트에서 치명적인 장애가 발생하거나 제어 없이 처리 중인 메시지를 유실해서는 안 됩니다. 컴포넌트는 장애가 발생해선 안 되기 때문에 상위 컴포넌트에 자신이 과부하 상태라는 것을 알려 부하를 줄이도록 해야 합니다.

백프레셔는 리액티브 스트림에서 발생하는 데이터 처리 속도 조절과 관련된 문제를 해결하기 위한 메커니즘입니다. 데이터 생산자와 소비자 간의 처리 속도 차이로 인해 데이터 처리 과정에서 발생하는 문제를 방지하기 위해 사용됩니다.

현실세계의 화장실에서 우리는 물을 사용하기 위해 수도꼭지를 적정량만 틀어 사용합니다. 물이 적절하게 나와야 우리는 그 물들을 받아 처리(사용)할 수 있습니다. 물이 과도하게 나오면 우리의 처리량이 이를 받쳐주지 못하고 나머지 물은 모두 유실될 것입니다. 소프트웨어 세계에서는 이는 곧 장애입니다. 백프레셔는 이런 수도꼭지같은 역할을 수행합니다.

백프레셔는 주로 pull 방식을 채택합니다. 즉, 소비자가 데이터를 필요로 할 때마다 데이터를 요청하여 가져오는 방식입니다. 이를 통해 소비자가 처리할 수 있는 만큼의 데이터만 가져오면서 문제를 방지할 수 있습니다.

🙋🏻 위에서 리액티브 프로그래밍은 Push 방식이라고 하지 않았나요?

리액티브 스트림은 push 방식을 따릅니다. 즉, 데이터 생산자가 데이터를 생성하고 변경되는 즉시 데이터를 소비자에게 푸시(push)하여 전달하는 방식입니다.

리액티브 프로그래밍과 백프레셔는 서로 다른 측면에서 동작하며, 리액티브 스트림은 비동기 데이터 흐름을 기술하고, 백프레셔는 송신자와 수신자 사이의 데이터 처리 속도 조절에 초점을 둡니다.

백프레셔가 없으면 발생하는 일


Subscriber가 처리하는 속도보다 Publisher가 발행하는 속도가 빠른경우, 대기 중인 데이터가 지속적으로 쌓이면서 오버플로가 발생하거나 최악의 경우에는 시스템이 다운되는 사태가 발생할 수 있습니다.

이러한 문제를 해결하기 위해 백프레셔가 필요합니다.

리액티브 스트림즈 컴포넌트

리액티브 스트림즈는 4개의 인터페이스인 Publisher(발행자), Subscriber(구독자), Subscription(구독), Processor(프로세서)로 요약이 가능합니다.

발행자(Publisher)는 데이터를 생성하고 통지(발행, 게시, 방출)합니다.

구독자(Subscriber)Subscription를 통해 Publisher에게 자신이 처리할 수 있는 만큼의 데이터를 요청하고 처리합니다.

Subscription은 Publisher에 요청할 데이터의 개수를 지정하고 데이터의 구독을 취소하는 역할을 합니다.

Processor 인터페이스는 Subscriber, Publisher 인터페이스를 결합한 것라고 보면 됩니다. 즉 Subscriber로서 다른 Publisher를 구독할 수 있고, Publisher로서 다른 Subscriber가 구독할 수 있습니다.

이때 발행자가 제공할 수 있는 데이터의 양은 무한(unbounded) 하고 오퍼레이션에 따라 순차적(sequential) 처리를 보장합니다.

마무리

The reactive-stack web framework, Spring WebFlux, was added later in version 5.0. It is fully non-blocking, supports Reactive Streams back pressure, and runs on such servers as Netty, Undertow, and Servlet containers.

Spring Webflux 공식 문서에 가면 스프링 웹플럭스를 리액티브 스택 웹프레임워크라고 소개하고 있습니다.

또한 스프링 프레임워크는 자바를 기반으로 할 경우 리액티브 스트림즈 구현체인 Reactor를 사용하게 되는데요, 여기서 의미하는 Reactive에 대해 정리해보았습니다.

다음 시간에는 Reactor에 대해 알아보겠습니다.

profile
鈍筆勝聰✍️
post-custom-banner

1개의 댓글

comment-user-thumbnail
2023년 7월 30일

이런 유용한 정보를 나눠주셔서 감사합니다.

답글 달기