모던 자바 인 액션 17

라떼는

  • 응답속도가 느려? 서버 더 늘려~
  • 스토리지가 부족해? 하드 더 사~
  • 응답시간이 느려? CS로 때워~
  • 유지보수 그거 당연한거임 ㅇㅇ

하지만 이제는

상황은 변하기 마련이다.

  • 빅데이터
    • 보통 빅데이터는 페타바이트(테라*1024) 단위로 구성되며, 그마저도 매일매일 증가함
  • 다양한 환경
    • PC.. 모바일.. 코어가 수천개인 클러스터까지, 어플리케이션을 배포하는 환경이 매우매우 다양해진다
  • 사용패턴
    • 사용자는 24시간 7일내내 항상 서비스를 사용할 수 있고, 밀리초 단위의 응답시간을 원함

‘라떼’ 방식으로는 요즘의 트렌드처럼 절대로 사용자의 요구사항을 맞출수 없다.

그래서 생긴 개념이 ‘리액티브’ 프로그래밍 기법이다.

데이터 항목 스트림을 비동기적으로 처리하고, 처리한걸 합치기도 하면서 결과를 뱉는 구조를 리액티브(반응형) 프로그래밍이라고 일컫는다.

리액티브 매니패스토(선언서)

  • 반응성
    • 빠른것도 빠른거지만, ‘일정하고 예측 가능한 반응시간 제공’ 이 더 중요하다. time-critical system이랑 비슷한 수준의 요구사항을 리액티브 어플리케이션도 충족해야 한다.
  • 회복성
    • 장애가 발생해도 시스템 자체는 반응해야 한다. 컴포넌트 실행 복제, 여러 컴포넌트의 시간&공간분리, 각 컴포넌트가 비동기적으로 작업을 다른 컴포넌트에 위임하는 등 다양한 방법이 존재한다.
  • 탄력성
    • 수신받는 요청건들의 부하가 각각 다 다른데, 이 때 리소스를 많이 잡아먹는 요청건들에 대해 자원 수를 늘린다던지 하는 기법.
  • 메시지 주도(message-driven)
    • 회복성,탄력성을 만족하기 위해 결합도를 약화시키기 위한 가장 심플하면서 좋은 방법은 메시지 주도 처리방식이다.

어플리케이션 레벨의 리액티브

리액티브 프로그래밍에서 가장 중요한건, ‘비동기로’ 작업을 수행할 수 있다는 점이다.

비동기로 작업을 수행한다는 것은, 여러개의 스레드(CPU코어) 로 작업을 수행한다는 것이고, 그 CPU의 사용율을 극대화 시켜야 한다.

그리고, 또 그 비동기에서 또 제일 중요한 부분은, ‘메인이벤트 루프 안에서는 절대 블록I/O를 하지 말아야 한다.’ 는 것이다.

(뭐….. 개념만 알아두고…. 내가 직접 리액티브 프로그래밍의 아키텍쳐를 설계할 것 아니면, RxJava, Akka같은 기존 프레임워크는 다~ 알아서 이런 짓을 하지 않는다.ㅋㅋ)

시스템레벨의 리액티브

어플리케이션을 암만 잘 짜도, 그 어플리케이션을 관리하는 시스템이 받쳐주지 못하면 말짱 꽝이다. (멀티스레드 프로그램을 짜도, H/W가 싱글코어이면 별 효과가 없듯이.)

리액티브 시스템의 가장 중요한 개념은, 메시지 드리븐 방식이다. 메시지 발신/수신을 ‘비동기’로 처리해야 한다. 동기로 처리하면(수신자는 발신자가 메시지를 쏠때까지 기다려야 하고, 그 상황에서 발신자가 뻗으면..) 발신자와 수신자의 결합도가 생긴다는 뜻인거고, 이는 리액티브 매니페스토에서 말한 ‘회복성’ 과 ‘반응성’ 등에 매우 안좋은 영향을 끼친다.

(한마디로… ‘죽을거면 너 혼자 죽어. 나라도 살아서 시스템을 돌리고 있어야겠다.’ 정신으로 장애영향도 최소화)

리액티브 스트림!?

리액티브 스트림은, pub-sub 형태로 설계되어 있다.

publish는 이론적으로는 무한대로 퍼블리싱하고, subscriber는 그 처리를 비동기로 받아, 블록킹 동작 없이(다른 블로킹 전용 처리 스레드?를 이용) 작업을 수행하는 과정이다.

여기서 리액티브 스트림의 제한조건이 하나가 필요한데, 다음과 같다

  • pub이, subscriber가 처리 가능한 한도를 넘어선 과도한 작업량을 퍼블리싱 하는 경우
    • 부하에 허덕이는 컴포넌트는 pub에게 이벤트 발생속도를 늦추라고 하거나
    • 내가 얼만큼의 작업량을 처리할 수 있는지 노티를 주거나

상기의 조건과 같이 ‘역압력’기능이 존재해야 한다.

뭐….이건 개념적인 설명의 영역이고, 우리같이..누구나 다 하고있는 발에 치이는 ‘자바 두명이요’ 개발자들은 이 개념에 대한 이해만 하고, 직접 이 spec까지 구현할 일은 잘 없다.(남이 잘 만들어둔거 이해해서 잘 갖다쓰기만 해도 될듯..)

대표적으로 이름만 대도 알 수 있는 거대 IT기업들이 리액티브 스트림 프로젝트들에 참여했고, 이를 자바에서 구현하기 쉽게 인터페이스 제공을 해 주었다.

  • java 자체적으로 만든 Flow
  • 라이트벤드의 Akka
  • 피보탈의 리액터
  • 넷플릭스의 RxJava
  • 레드햇의 Vert.x

그럼 우선 Flow부터 보자

자바9에서 제공해 주는 클래스 spec이다.

  • Publisher
  • Subscriber
  • Subscription
  • Processor

요렇게 네개의 인터페이스를 포함하였고, 하나씩 설명하도록 한다

publisher는 말 그대로 발행자이고, 내부 메소드로 subscribe 를 갖고 있다.

메인 스레드에서 Publisher.subscribe()를 통해 subscriber에게 작업 수행 메시지를 던지는 구조임을 알 수 있다.

Subscriber는 onSubscribe 메소드를 통해, Publisher로부터 Subscription 객체를 받을 수 있다.

Subscriprion은 구독 인스턴스라고 생각하면 된다. Publisher에게 ‘나 n개만큼의 작업을 처리할 수 있어요’ 라고 알려주는 역압력 메소드 하나와(request메소드), ‘나 이제 Publisher에게 작업 안받을래’ 라고 선언할 수 있는 메소드 하나가 있다(cancel 메소드)

Flow 클래스의 가장 기본적인 흐름을 시퀀스 다이어그램으로 그리면 다음과 같다

Flow를 통해 만들어보는 간단한 온도체크 클래스

사실 무슨 소린지 잘 이해가 안간다. 그림이 막 틀린 것 같기도 하고…..

그러면 실제 예제를 하나 보면 조금 이해하기 쉽지 않을까?

다음과 같은 spec을 갖는, 온도 체크 프로그램을 만들어 본다.

  • TempInfo 컴포넌트
    • 원격 온도계를 흉내낸다. 온도 측정이 가능한 기구.
  • TempSubsciber 컴포넌트
    • 온도 기록계. TempInfo가 제공해주는 온도를 기반으로 기록을 하는 컴포넌트

뭐…. 온도계는 더이상 자세한 설명을 생략한다

이 TempSubscription이, Subscriber로부터 명령을 받아 ‘실제 수행할 동작’에 대해 구현을 한 핵심부위라고 생각하면 된다.

(request메소드 구현부 참고)

Publisher로부터 subscription 객체를 하나 받으면, 그 즉시 Subscriber는 onSubscribe메소드를 통해 하고자 하는 일을 한다.(여기서는 subscription의 request를 1번 실행시키는 것.)

마지막 Publisher와 메인메소드 코드.

getTemperatures메소드가 람다식인데, Subscriber 타입을 인수로 받고, 메소드이름이 onSubscribe()이고, Publisher인터페이스가 함수형 인터페이스라 자동으로 저 람다식은 Publisher로 변경된다.

실행시켜보면

적당히 뉴욕의 온도를 찍다가 StackOverflowError를 찍는다.

왜냐면, 메소드 콜이 chaining된 상태로 계속 재귀적 호출을 하는 구조이다.

최초 TempSubscriber.onNext()에서 TempSubscription.request()를 호출함 -> 이 TempSubsciption.request() 에서 subscriber.onNext()를 다시 호출 –> 근데 빨간색 onNext와 녹색 onNext는 다른 호출임. –> onNext() 와 request()의 무한루프같은 호출 –> 메소드 수행 종료없이 재귀호출로 스택 오버플로우

그래서

요런 식으로 별도의 스레드를 만들어, main에서 돌고 있는 메소드가 정상 종료 되도록 하면 스택 오버플로우 없이 로직이 수행됨을 알 수 있다.

섭씨를 화씨로

이때 쓰이는게 Processor이다.

Processor는 사실 pub임과 동시에 sub라고 한다. 하지만 그 목적은 pub과 sub을 둘 다 구분없이 쓰라는게 아니라, publisher를 구독한 다음, 수신받은 데이터를 중간부분에서 다시 가공해서 제공하라는 의도로 만들어진 것이다.

Processor.onNext 메소드에서 섭씨를 화씨로 변환해서 subsciber의 onNext를 대신 호출해주는 것을 볼 수 있다.

메인 스레드에서는, 이렇게 중간단계에 processor를 하나 생성하여 pub-sub 중간에 껴주는 구조로 작업을 생성하면 된다.

Leave a Comment