

Spring MVC는 Servlet 기반으로 만들어졌고, sync + blocking 방식으로 동작하고 결국 하나의 처리를 할 때 Response를 기다리며 thread를 지연시키는 부분이 있다.
기본적으로 사용자 요청이 Spring 서버에 들어오게 된다면, ThreadPool에서 존재하는 스레드를 하나 할당하고 해당 스레드는 그 요청을 처리해야한다. 만약 사용자 요청이 디스크에 오래 걸리는 작업을 요청할 경우 해당 스레드는 디스크 쓰기 작업이 완료될 때까지 기다리는 상황이 생기는데 이를 Blocking이라고 하는 것이다.
따라서 Spring MVC 같은 경우 요청이 들어오면 그 요청을 Queue에 쌓고 순서에 따라서 Thread를 하나 점유해 요청을 처리한다. 동시 다발적으로 스레드 수를 초과하는 요청이 발생한다면 계속해서 요청이 큐에 대기하게 되는 Thread Poll Hell 현상이 발생할 수 있다. 이를 해결하기 위해 시스템의 트래픽을 측정해서 thread pool size를 잘 조정해야한다. 하지만 사이즈를 조정을 하더라도 사용자의 요청을 대량으로 받아내는데는 한계가 있을 것이다. 이를 해결하는 것이 Webflux인 것이다.
Webflux는 요청을 처리하는 방식이 Event-Driven 방식이고 async + nonblocking 방식이다.
작은 수의 스레드(일반적으로 CPU 코어 수에 비례)로 많은 수의 동시 요청을 처리한다.
간단하게 설명하자면, 이벤트 루프가 돌아서 요청이 발생할 경우 맞는 핸들러에게 처리를 위임하고 처리가 완료되면 callback 메소드 등을 통해 응답을 반환한다.
요청이 처리될 때까지 기다리지 않기때문에 사용자의 요청을 대량으로 받아낼 수 있다는 장점이 있다.
즉, 서버 프로그램이 효율적으로 동작해서, cpu, thread, memory에 자원을 낭비하지 않고 효율적으로 동작하는 고성능 웹 애플리케이션을 개발하는 목적으로 한다. 서비스간 호출이 많은 MSA에 적합한 것이다.
spring mvc는 스레드 풀을 만들어서 비동기적으로 여러 스레드를 동시에 실행해도 요청 처리 중 I/O 작업이 필요하면 해당 스레드가 작업이 끝날때까지 기다린다.
Spring MVC는 각 요청을 개별 스레드에서 처리합니다. 이 스레드들은 스레드 풀에서 관리되며, 요청 중에 I/O 작업이 필요할 때 해당 스레드는 데이터가 도착하거나 I/O 작업이 완료될 때까지 블로킹 상태가 된다.
이는 스레드가 작업을 기다리는 동안 다른 작업을 수행할 수 없다는 것을 의미하며, 이로 인해 리소스 사용이 비효율적이 될 수 있다.
이와 대비하여, Spring WebFlux는 비동기적으로 I/O 작업을 처리하여, 스레드가 블로킹되지 않고 다른 요청을 계속 처리할 수 있다.
| Spring MVC | WebFlux |
|---|---|
| 동기(Synchronous)적인 방식 | 비동기(Asynchronous)적인 방식 |
| 블로킹(Blocking) 방식으로 구현 | 논 블로킹(Non-Blocking) 방식으로 구현 |
| 명령형 프로그래밍 | 반응형 프로그래밍 |
| JDBC JPA 네트워킹 지원 | 반응형 라이브러리(Reactor, RxJava) 지원 |
Netty는 프로토콜 서버 및 클라이언트와 같은 네트워크 응용 프로그램을 빠르고 쉽게 개발할 수 있는 NIO (Non-Blocking Input / Output) 클라이언트 서버 프레임 워크이다. TCP 및 UDP 소켓 서버와 같은 네트워크 프로그래밍을 크게 단순화하고 간소화한다.
Spring Boot는 기존의 서블릿 기반의 Tomcat을 기반으로 동작한다. 반면 Spring Boot WebFlux는 여러 가지를 고를 수 있지만 기본적으로 Netty를 사용한다. Tomcat은 요청 당 하나의 스레드를 동작하는 반면, Netty는 1개의 이벤트를 받는 스레드와 다수의 Worker 스레드로 동작하게 된다. 조금 더 깊게 살펴보자.
Netty는 채널에서 발생하는 이벤트를 EventLoop가 처리하는 구조로 동작하게 된다.
이벤트 루프는 이벤트를 실행하기 위한 무한루프 스레드를 뜻한다.
객체에서 발생한 이벤트는 이벤트 큐에 push되고 이벤트 루프는 이벤트 큐에 입력된 이벤트가 있을 때 해당 이벤트를 꺼내서 실행하게 된다. 이벤트 루프는 스레드의 종류에 따라서 싱글 스레드, 멀티 스레드로 나누어지게 된다.
이러한 문제점을 해결하기 위해 아래와 같은 방법으로 해결한다.
루프들이 이벤트 큐를 공유하여 이벤트 발생순서와 실행 순서가 일치하지 않는다.
Netty는 이벤트 큐를 이벤트 루프 스레드의 내부에 둠으로써 실행 순서의 불일치 원인을 제거하게 된다.
Http 프로토콜 방식은 stateless이기 때문에 클라이언트가 요청을 보내고 server가 응답을 한 번 보내면 연결을 끊어버린다. 그렇게 된다면, 이벤트 루프에서 기억하고 있었던 응답을 다시 보낼 수가 없다.
이러한 문제점을 해결하기 위해 응답을 Stream으로 만들어서 끊기지 않게 계속 유지하는 방법을 SSE 프로토콜 방식이라고 한다.
이벤트 루프와 Stream 같은 SSE 프로토콜 방식으로 반응형 프로그래밍을 할 수 있다.
Webflux는 구독과 출판의 개념을 가지고 있는데 구독은 Response가 유지되고 있다. Publish는 유지되고 있는 선으로 계속적으로 데이터를 응답해준다.
다른 사람이 어떤 이벤트를 요청해서 데이터의 변경이 일어나면, 서버가 그 데이터를 바라보고 있는 상대들한테 즉각적으로 Push 해줄 수 있다. 일반적으로 RDBMS는 지원하지 않고 리액티브를 지원하는 MongoDB, Redis를 사용해야하지만 최근에는 R2DBC 라이브러리로 MySQL처럼 RDBMS에서도 비동기 방식으로 연결이 가능하다고 한다.

Spring Webflux에서 사용하는 reactive library가 Reactor이고 Reactor가 Reactive Streams의 구현체이다.
Reactor의 주요 객체로 Mono와 Flux가 있다.

