- Today
- Yesterday
- Total
메이쁘
[IT갈피] 실전! 스프링5를 활용한 리액티브 프로그래밍 - 1장 : 왜 리액티브 스프링인가? 본문
안녕하세요.
요즈음 개인적으로 시간내서 IT공부를 하고 있지 않았고,
최근들어 다시 공부의 필요성을 느꼈습니다.
회사의 도움으로 교재를 구매하여 따로 공부하며 잊지않고 기록하기 위해 갈피를 잡아보려합니다.
비단 교재 뿐 아니라 IT와 관련된 내용이라면 여기 IT갈피 에 잡으려고요.
저의 IT갈피를 보러 와주신 분들께 감사인사 드리면서,
시작하겠습니다!
실전! 스프링 5를 활용한 리액티브 프로그래밍
올레 도쿠카,이호르 로진스키 지음 | 김시영 옮김
https://search.shopping.naver.com/book/catalog/32482369871
1장 - 왜 리액티브 스프링인가?
- 왜 리액티브인가?
- 메시지 기반 통신
- 반응성에 대한 유스케이스
- 왜 리액티브 스프링인가?
- 서비스 레벨에서의 반응성
- 요약
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
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.
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
컨텍스트 스위칭(context switching) 을 고려했을 때, 결과적으로 적은 수의 CPU에 동시에 많은 수의 쓰레드를 활성화시키는 응용 프로그램은 비효율적이다.
일반적인 자바 쓰레드는 메모리 소비에 오버헤드가 존재함.
-> 64-bit JVM에서 쓰레드의 일반적인 Stack Size는 1,024KB 이다.
-> 그럼, 만약 64,000개의 쓰레드를 가진 쓰레드 풀을 만들어두면? 대략 64GB의 메모리를 필요로 하다..
그렇다고, 너무 적은 수의 쓰레드를 만들어놓으면 그것 또한 문제가 될 수 있다.
- 제한된 크기의 쓰레드 풀을 제공하는 기존 모델로 전환하고, 요청에 대해 미리 구성된 대기열을 사용하면 (만약 해당 쓰레드 풀 크기 이상의 클라이언트 요청이 대기열에 쌓이면)???
-> 클라이언트가 응답을 받기까지의 대기 시간이 길어지고 평균 응답 시간이 길어지면서 결국 응용프로그램이 응답하지 않을 수 있다.
요약
- 리액티브 선언문을 통해 반응성이라는 용어를 이해함.
- 디지털 시대(요즈음~)의 주요 비기능적 시스템 요구 사항인 탄력성, 복원력 및 메세지 기반 접근 방식이 응답성 확보에 도움이 되는 이유와 그 방법에 관해서도 설명함.
'나의 갈피 > IT갈피' 카테고리의 다른 글
[IT갈피] 실전! 스프링5를 활용한 리액티브 프로그래밍 - 2장 : 스프링을 이용한 리액티브 프로그래밍 - 기본 개념 (1/2) (1) | 2022.11.24 |
---|---|
[IT갈피] 마이크로서비스 도입, 이렇게 한다 - 1장 : 더도 덜도 아닌 딱 마이크로서비스 (0) | 2022.10.13 |