메이쁘

[IT갈피] 실전! 스프링5를 활용한 리액티브 프로그래밍 - 1장 : 왜 리액티브 스프링인가? 본문

나의 갈피/IT갈피

[IT갈피] 실전! 스프링5를 활용한 리액티브 프로그래밍 - 1장 : 왜 리액티브 스프링인가?

메이쁘 2022. 11. 20. 16:29

안녕하세요.

 

요즈음 개인적으로 시간내서 IT공부를 하고 있지 않았고,

최근들어 다시 공부의 필요성을 느꼈습니다.

 

회사의 도움으로 교재를 구매하여 따로 공부하며 잊지않고 기록하기 위해 갈피를 잡아보려합니다.

비단 교재 뿐 아니라 IT와 관련된 내용이라면 여기 IT갈피 에 잡으려고요.

 

저의 IT갈피를 보러 와주신 분들께 감사인사 드리면서,

시작하겠습니다!


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

올레 도쿠카,이호르 로진스키 지음 | 김시영 옮김

 

https://search.shopping.naver.com/book/catalog/32482369871

 

실전! 스프링 5를 활용한 리액티브 프로그래밍 : 네이버 도서

네이버 도서 상세정보를 제공합니다.

search.shopping.naver.com

 

 


1장 - 왜 리액티브 스프링인가?

  1. 왜 리액티브인가?
    1. 메시지 기반 통신
  2. 반응성에 대한 유스케이스
  3. 왜 리액티브 스프링인가?
    1. 서비스 레벨에서의 반응성
  4. 요약

 

1장은 위와 같은 순서대로 작성되어있다.

 

장별 소개

<1장 왜 리액티브 스프링인가?> 는 리액티브와 매우 잘 어울리는 비즈니스 사례를 다룹니다.
리액티브 솔루션이 사전 대응형(proactive) 솔루션보다 우수한 이유를 알아봅니다.
또한, 다양한 서버 간 통신 방법을 보여주는 몇 가지 코드 예제와 현재의 비즈니스 요구 사항 및 현대 스프링 프레임워크의 요구 사항에 대해 이해할 수 있습니다.

 

이에 더해, 1장에서는 아래와 같은 내용을 다룬다고 한다.

 

  • 왜 반응성이 필요한가?
  • 리액티브 시스템의 기본 원리
  • 리액티브 시스템 설계에 완벽하게 일치하는 비즈니스 사례
  • 리액티브 시스템에 좀 더 적합한 프로그래밍 기술
  • 스프링 프레임워크가 리액티브로 전환하는 이유

 

 

왜 리액티브인가?

- 리액티브라는 용어의 설명 전에 전통적인 개발 방법으로 만든 온라인 쇼핑몰에 대한 사례를 들어줌.

그 사례를 통해 왜 리액티브가 필요한지에 대해 명확하게 이해할 수 있다.

 

해당 온라인 쇼핑몰에 시간당 평균 약 1,000명의 사용자가 방문한다고 해봅시다.
요구 사항을 충족하기 위해 최신 컴퓨터를 구입해 톰캣(Tomcat) 웹 서버를 실행하고, 500개의 쓰레드로 톰캣 쓰레드 풀을 구성했습니다. 사용자 요청에 대한 평균 응답 시간은 약 250밀리초입니다.(0.25 second = 1개의 요청 처리 시간은 0.25초) 

--> 1개의 쓰레드는 1개의 요청을 처리할 수 있다.
--> 해당 쇼핑몰은 1초 당 2,000개의 요청을 처리할 수 있다.

통계에 따르면, 이전에 언급한 사용자 수 기준으로 평균 요청 건수는 초당 1,000건 정도였습니다.
결과적으로 현재 시스템의 용량은 평균 부하를 처리하기에 충분합니다.

현재 이러한 애플리케이션이 구현되어 있는데, 핵심은 지금부터다.

-> 블랙 프라이데이날 폭발적인 고객 증가로 인해 트래픽 또한 폭발적으로 증가했고, 쓰레드 풀에는 사용자 요청을 처리할 쓰레드가 남아있지 않게 되었다.

-> 결국 주기적으로 서비스 중단되며, 주문도 누락되면서 고객들은 경쟁사로 발길을 돌렸다..

 

ex) 아마존닷컴 정전 사건

ex2) 월마트의 블랙 프라이데이 재난

 

그럼, 이러한 문제를 어떻게 해결할 것인가?

탄력성.

- 즉, 사용자 요청에 대한 응답 능력에 영향을 미칠 수 있는 모든 변화에 대응하는 능력

- 더 많은 사용자가 작업을 시작할 때 시스템 처리량이 자동으로 증가해야 하고 수요가 감소하면 자동으로 감소해야 하는 것.

 

 

수평적 또는 수직적 확장으로 비즈니스 비용을 절감해야함.

 

중요한 것은,

탄력성과 복원력이 밀접하게 결합돼 있으며, 이 두 가지를 모두 사용할 때만 시스템의 진정한 응답성을 달성할 수 있다는 것.

 

 

 

메세지 기반 통신

@GetMapping("/resource")
    fun api() {
        val template: RestTemplate = RestTemplate()
        val result = template.getForObject(
            "http://www.naver.com",
            String::class.java
        )
        
        // 이후 result 처리
    }

대략 이런 느낌의 HTTP 통신 기본 로직. (kotlin)

그렇다면, 위 API를 호출하면 요청 처리 타임라인은? -> 블로킹이 된다.

API 요청받아 서블릿을 물고있는 쓰레드A.

해당 API는 RestTemplate로 다른 외부 요청을 찌르게 되는데..

그렇게 되면, 외부 요청은 다른 쓰레드가 받아서 처리하고, 그 사이에 쓰레드A는 외부 요청 결과를 받기 전까지 아무것도 못하고 그저 멍때립니다.

 

-> I/O 측면에서 리소스 활용도를 높이려면 비동기 논블로킹 모델을 사용해야 한다.

 

 

그래서, Message-driven 통신 원칙을 따르면 좋다. -> 메세지 브로커를 사용해도 됨.

(개인적으로 궁금한 점: Message-driven 과 Event-driven 방식의 차이점은??)

-> 참고블로그 : https://developer.lightbend.com/docs/akka-guide/concepts/message-driven-event-driven.html

 

Message Driven vs Event Driven :: Akka Guide

As discussed above, in Message Driven systems, each component send items to a fixed recipient. In Event Driven systems, on the other hand, each component produces items of data with a fixed sender and shares them with any consumer. This comparison, though,

developer.lightbend.com

In Message Driven systems, Component A produces a message indicating it must be delivered to the address of Component B. Component A then send the message and gets control back immediately instead of waiting for Component B to complete the handling of the message. Components on a message driven system often have a queue where incoming messages can be stored in case of a load spike. Message passing is a building block for space decoupling.

In Event Driven systems, components announce a location where they expose their events. So a Component A emitting events will use a well-known location to publish them but not know which components are consuming the events. This well-known location is implemented using an ordered queue. Sometimes the queue is indexable so consumers can keep track of the events already consumed and the events pending.

 

-> 참고블로그 : https://www.techtarget.com/searchapparchitecture/tip/Event-driven-vs-message-driven-It-comes-down-to-complexity

 

Event-driven vs. message-driven: It comes down to complexity

Event-driven and message-driven design represent a dichotomy of service integration approaches. Learn how each one handles communication in its own way.

www.techtarget.com

If communications generally occur in a one-to-one manner, and receiving regular status updates or confirmations is a priority, you'll want to lean towards the message-based approach. However, if the interactions between systems are particularly complex, and the delays caused by confirmations and status updates make waiting for them unrealistic, an event-driven design might be more appropriate.

Keep in mind, however, that most large organizations will end up with a hybrid strategy, with some customer-facing/API calls using messages and the enterprise itself using events. As such, it never hurts to familiarize yourself as much as possible with both.

결국, Message-driven 방식은 메세지 대상자가 누군지 알고, 발신자 주소도 알고 있는 상황이라 비동기 논블라킹으로 메세지 보내놓고 다른 일을 하는 상태이고,

Event-driven 방식은 이벤트 발행자가 누군지 모르고 그저 이벤트가 발생하면 별도의 Worker가 처리하여 전달해주는 상태.

 

 

그렇다면 결국 리액티브 시스템의 기본 원리는?

 

중요한 건 위 리액티브 선언문이 제공하는 모든 기본 원칙은 전체 도메인 또는 비즈니스 아이디어와 관계없이 유효하다.

또한, 구성 요소 수준에서도 리액티브 설계 및 구현을 제공하는 것이 중요하다.

 

 

이후 계속 자바 클래스를 활용한 비동기 예시가 나온다.

(각 클래스에 대한 설명과 예시는 구글링을 통해 더 자세히 알 수 있을 것 같다.)

 

  • Consumer<>
  • Future
  • CompletableFuture (자바8)
  • CompletionStage
  • AsyncRestTemplate

 

지금 현재 스프링 프레임워크(Spring MVC) 는 블로킹 네트워크 호출을 별도의 쓰레드로 래핑 한다.

또한, 모든 구현체가 각각의 요청에 별도의 쓰레드를 할당하는 서블릿(Servlet) API 를 사용한다.

스프링 프레임워크 5와 새로운 리액티브 웹 클라이언트가 출시되면서 많은 부분이 변경됐으며 WebClient의 도입을 통해 모든 서비스 간 통신에 논블로킹 통신을 지원합니다.
또한 서블릿 3.0은 비동기적인 클라이언트-서버 통신을 도입했는데, 서블릿 3.1은 Non-Blocking I/O 쓰기를 허용합니다.
서블릿 3 API 에 포함된 대부분 비동기 Non-Blocking 기능은 스프링 MVC에 잘 통합돼 있습니다. 그러나 한 가지 문제는 스프링 MVC가 비동기 Non-blocking 클라이언트를 제공하지 않음으로써 개선된 서블릿 API의 모든 이점을 무효로 만들었다는 것입니다.

--> 즉, 스프링 MVC 프레임워크에서는 비동기 Non-blocking 방식으로 API 통신 코드는 작성할 수 있지만, 실제로 비동기 Non-blocking 으로는 동작하지 않는다는 것.
--> 서블릿 3.0 부터는 가능하지만 그 이전 서블릿 버전에서는 무조건 blocking 방식으로 동작할 수 밖에 없음.

위 설명에 들어가있는 서블릿 3.0과 3.1에 대한 내용이 아래 블로그 포스팅에 잘 담겨있는 것 같아서 가져와봅니다.

-> https://velog.io/@hyunjong96/Spring-Servlet-3.0-Servlet-3.1

 

[Spring] Servlet 3.0 & Servlet 3.1

이전 내용에서는 많은 사용자의 요청을 처리하기 위한 클라이언트 요청과 서블릿 컨테이너간의 Connector에 대해서 공부했었다.이전 내용을 잠깐 설명하자면 다수의 커넥션을 관리하기 위해 커넥

velog.io

 

 

컨텍스트 스위칭(context switching) 을 고려했을 때, 결과적으로 적은 수의 CPU에 동시에 많은 수의 쓰레드를 활성화시키는 응용 프로그램은 비효율적이다.

 

일반적인 자바 쓰레드는 메모리 소비에 오버헤드가 존재함.

-> 64-bit JVM에서 쓰레드의 일반적인 Stack Size는 1,024KB 이다.

-> 그럼, 만약 64,000개의 쓰레드를 가진 쓰레드 풀을 만들어두면? 대략 64GB의 메모리를 필요로 하다..

 

그렇다고, 너무 적은 수의 쓰레드를 만들어놓으면 그것 또한 문제가 될 수 있다.

- 제한된 크기의 쓰레드 풀을 제공하는 기존 모델로 전환하고, 요청에 대해 미리 구성된 대기열을 사용하면 (만약 해당 쓰레드 풀 크기 이상의 클라이언트 요청이 대기열에 쌓이면)???

  -> 클라이언트가 응답을 받기까지의 대기 시간이 길어지고 평균 응답 시간이 길어지면서 결국 응용프로그램이 응답하지 않을 수 있다.

 

 

 

요약

- 리액티브 선언문을 통해 반응성이라는 용어를 이해함.

- 디지털 시대(요즈음~)의 주요 비기능적 시스템 요구 사항인 탄력성, 복원력 및 메세지 기반 접근 방식이 응답성 확보에 도움이 되는 이유와 그 방법에 관해서도 설명함.

 

 

Comments