티스토리 뷰

BackEnd

Netty VS Tomcat 비교 분석하기

Hero_O 2022. 11. 8. 22:38

Tomcat

톰캣은 대표적인 서블릿 컨테이너이며 Spring Boot (MVC) 에 기본 내장된 Embede WAS이다. 전통적인 서블릿 컨테이너는 Thread Per Request 해서 요청당 하나의 스레드를 할당하여 생성부터 소멸까지 관리한다. 이 서블릿 스레드는 풀에 의해 관리되고 해당 스레드의 작업이 끝날 때 까지 다른 요청을 받을 수 없는 Blocking I/O 다.


해당 문제 해결을 위해 Servlet 3.0 에서 NIO Connector 가 등장하였다. Servlet 3.0 은 자바 6 이상에서 사용이 가능한데 자바 4에서 추가된 Java NIO에 Selector 를 활용한 Reactor Pattern 을 통해 요청 스레드와 응답 스레드의 분리가 가능하게 되었다.

하지만 Worker Thread에게 작업을 넘길 뿐 뒤에서는 작업스레드를 요청 당 하나를 생성하여 무거운 작업을 할때 Servlet Thread를 잡아두는 경우에만 유용할 수 있다. 또한 서블릿 컨테이너에서 서블릿에서 요청 및 응답을 전달하거나 전달 받는 과정이 Blocking 되어 Thread 부족을 겪을 수 있다.


내부적으로 Servlet 3.1 에서 서블릿 Request Read 와 Request Write 가 비동기 방식으로 작업할 수 있도록 변경되어 해당 문제를 해결하였다.


하지만 여전히 Servlet에 다양한 API가 동기식으로 제공되고 있기 때문에 동기식으로 작동할 위험을 가지고 있다. 때문에 Spring Webflux에서도 내장 컨테이너로 Servlet 3.1+ 를 사용할 수는 있지만 기본적으로 Netty를 기본 컨테이너로 지정하게 된 동기가 되었다.

Netty

네티는 비동기 이벤트 기반 네트워크 애플리케이션 프레임워크로써 유지보수를 고려한 고성능 프로토콜 서버와 클라이언트를 빠르게 개발할 수 있다는 특징을 가졌다.


이벤트 기반 프로그래밍은 어쩌면 백엔드보다는 GUI가 포함된 프론트엔드 개발자에게 더 익숙할 수 있다. 이벤트를 정의하고 그 이벤트에 따라 코드가 실행하도록 하는 프로그램을 이벤트 기반 프로그램 이라고 한다.


보통 GUI가 존재하는 이벤트 기반 애플리케이션은 이벤트 리스너와 그 것을 처리하는 스레드를 기반으로 작동한다. 하지만 서버에서는 일반적으로 이벤트 큐에 이벤트를 등록하고 이벤트 루프가 이벤트 큐에 접근하여 처리하는 방법을 주로 사용한다.


일반적으로 이벤트루프는 단일 스레드 방식과 멀티 스레드 방식이 존재하는데 단일 스레드 방식을 지원하는 실행환경으로는 Node.js 가 대표적이다. 단일 스레드 방식의 경우 순서대로 처리가 가능하고 구현이 단순하다는 장점을 가지지만 멀티코어의 효율을 제대로 낼 수 없다는 단점을 가지기도 한다.


반대로 멀티 스레드 방식의 경우 멀티 코어를 효율적으로 사용할 수 있지만 이벤트 순서와 처리 순서가 다를 수 있고 하나의 이벤트 큐에 여러 스레드가 동시에 접근하여 Race Condition 이 발생할 수 있다는 단점역시 가지고 있다.


네티의 경우 멀티 스레드 방식과 단일 스레드 방식을 모두 사용할 수 있다. 그리고 네티는 멀티스레드 방식을 사용하면서도 경쟁조건이나 이벤트 순서문제에서 자유롭다.


그것이 가능한 이유는 네티의 이벤트 루프는 각각의 이벤트 큐를 가져 (소켓)채널에서 발생하는 이벤트(요청)를 각각의 이벤트 큐에 쌓는다. 그리고 이벤트가 발생한 채널은 항상 동일한 이벤트 루프 스레드에서 처리하게 해 이벤트 발생 순서와 처리 순서를 일치시킬 수 있다. 즉, 여러개의 이벤트 루프들이 이벤트 큐를 공유하지 않기 때문에 순서를 보장할 수 있고 Race Condition도 최소화 할 수 있다.

Tomcat과 Netty

Tomcat과 다르게 네티의 경우 태생적으로 고성능 비동기 논블로킹 통신을 지원하도록 만들어져 Spring Web Flux에 기본 엔진으로 선택되었다.


물론 Tomcat 3.1+ 부터는 비동기 논블로킹을 지원한다고 볼 수 있지만 Servlet 엔진에서 사용되는 다수의 API 가 NonBlocking 으로 작동하고 있어 정확히 알지 못하면 개발하는 과정에서 언제나 동기적으로 작동할 수 있다는 위험이 도사리고 있다.

참조

댓글