Synchronous Blocking I/O 아키텍쳐로 동작하며 request-per-thread 모델 구조를 갖는다. 이는 동기적인 처리를 위해 웹 요청부터 응답까지 비지니스 로직을 하나의 Thread가 점유해서 사용한다. Servlet Containers는 Java에서 Servlet 스펙을 구현한 것으로 주로 사용하는 오픈소스는 Tomcat이고 API를 추상화 함으로써 웹 어플리케이션 개발에 필요한 기능을 제공한다.
Client Http Request 요청에 대해 queue에 먼저 넣고 요청을 Thread Pool에서 처리한다. 많은 요청이 오는경우 요청 대기열을 넘어서면 다음 요청은 실패 처리가 된다.
Thread Pool: 매번 Thread를 생성하는것은 성능적으로 낭비이기 때문에 Thread를 미리 만들어놓고 필요할때 사용한다.
Client 웹 요청시 Servlet을 거쳐 Spring container로 부터 비지니스 로직 처리를 진행한다. 그리고 동기적으로 DB에 데이터 요청을하는 흐름까지 하나의 Thread로 진행하게 되는데 문제파악,흐름을 이해하는데 직관적이여서 장점이다. Blocking I/O로 운용되기 때문에 DB요청의 쿼리 응답이 늦어지면 응답이 올때까지 기다려서 response도 느려진다. 대량의 I/O요청이 예상 되는경우 그에 맞는 Thread 숫자 운용이 필요하기 때문에 서버 리소스 관점에서 유용하다 볼수 없다. 이를 해결하기 위해 Reactive Stack을 사용한다.
Client 요청을 받으면 Netty 이후로 DB,외부 api서버에 대한 요청들은 Reactor를 통해 비동기 처리를 하게 된다. Client와의 비동기,이벤트 기반 통신을 Netty가 담당하고 그 이후는 Reactor가 담당한다.
Non-blocking 웹 프레임워크를 지원하며 대량의 I/O 요청에 대해 동시 처리하는데 유용한 구조를 갖는다. 이유는 Netty를 활용하게 되는데 Non-blocking Event 기반 네트워크 프레임워크이며 내부적으로 I/O Multiplexing 기반 이벤트 루프를 사용하여 높은 성능을 보장한다. 또한 TCP/UDP,WebSocket 등 다양한 프로토콜을 지원한다. Reactive Stream Adapter를 통해 구현체와 연결하며 보안,webflux를 지원하며 Repository에서 RDB는 JPA,ORM을 지원하지 않기 때문에 Reactive Relational Database Connectivity를 의미하는 R2DBC를 활용해야 한다.
Netty가 웹 요청을 받는 첫번째 컴포넌트이다. 동시성 요청 처리를 위한 Single Thread 기반 스케쥴러인 Event Loop를 다수 활용하여 각각 queue에 적재하고 이벤트 loop는 반복해서 해당 queue의 내용을 꺼내며 그에 따른 handler를 실행한다.
Java의 고성능 네트워크 프레임워크로 내부적으로 2개의 큰 Event Loop Group을 활용한다. 따라서 여러개의 Thread로 운용이 가능하다. 하나의 Event Loop는 다수의 Channel(클라이언트와의 연결)을 다루는 Non-blocking 방식과 I/O Multiplexing을 지원한다.
각 Channel에는 Channel Handler를 pipeline 방식으로 추가하고 해당 구조를 통해 데이터 송수신,필터링,프로토콜 처리등 다양한 이벤트를 단계적으로 처리한다.
Reactive Stream은 비동기 데이터 스트림 처리를 위한 표준화된 스펙을 이야기한다. 이런 스펙의 구현체가 Reactor이고 Spring Webflux에서 전반적으로 Reactor를 활용함으로써 웹 요청에 대한 비동기 처리를 진행한다.
1.stream
a.publisher
public interface Publisher<T> {
public void subscribe(Subscriber<? super T> s);
}
Publisher는 Reactive Streams의 핵심 인터페이스 중 하나로, 구독자(Subscriber)에게 데이터를 발행하는 역할을 한다.
subscribe 메서드를 통해 구독자와 연결되며, Subscriber 객체가 인자로 전달된다.
b.subscriber
public interface Subscriber<T> {
public void onSubscribe(Subscription s);
public void onNext(T t);
public void onError(Throwable t);
public void onComplete();
}
Subscriber는 발행자로부터 데이터를 수신하는 역할을 한다.
onSubscribe: 구독이 시작될 때 호출되며, Subscription 객체를 전달받는다.
onNext: 새로운 데이터가 수신될 때 호출된다.
onError: 에러가 발생했을 때 호출된다.
onComplete: 모든 데이터가 정상적으로 전달된 후 호출된다.
c.subscription:
public interface Subscription {
public void request(long n);
public void cancel();
}
Subscription은 발행자와 구독자 간의 연결을 관리하며, 구독자에게 데이터를 요청하거나 구독을 취소할 수 있다.
request: subscriber가 처리 가능한 수준을 조절하는 데이터 완급 조절을 위해 사용된다. publisher에게 가용한 처리량을 알리는데 사용된다.
cancel: 구독을 취소할 때 사용된다.
d.processor
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}
Processor는 Subscriber와 Publisher를 모두 구현하는 인터페이스로, 입력 데이터를 받아 처리하고 다른 데이터 형태로 변환하여 발행할 수 있다.
데이터를 가공하는 중간 단계 역할을 하며, 입력과 출력의 타입이 다를 수 있다.
2.asynchronous
데이터 처리 단계에서 같은 Thread나 별도의 Thread로 동작시키며 병렬 처리가 가능하다.
3.back pressure
반대되는 압력을 의미하며 publisher에 의해 생산되는 데이터를 subscriber가 충분히 처리하지 못할때 처리 속도 불균형을 해결하는 장치이다. subscription 객체의 request 메서드가 대표적인 back pressure이다. request 메서드를 Non-blocking 방식으로 동작 시킨다.
해당 스펙을 구현한 Project Reactor를 Spring webflux에서 사용하게 된다.