스프링 부트는 어떻게 동시에 수많은 요청을 처리할까요?

Tomcat 알아보기 첫 번째 포스팅으로 Servlet 과 Servlet Container 에 대해 알아보겠습니다.

 


 

Servlet

 

자바 docs 에서 정의하는 서블릿은 다음과 같습니다.

A servlet is a small Java program that runs within a Web server. Servlets receive and respond to requests from Web clients, usually across HTTP, the HyperText Transfer Protocol.

서블릿은 웹 서버 내에서 실행되는 작은 자바 프로그램입니다. 서블릿은 일반적으로 HTTP 를 통해 웹 클라이언트의 요청을 수신하고 응답합니다.

 

우리는 웹 페이지를 사용할 때 서버로부터 정적인 리소스를 받기도 하지만, 대부분은 우리가 입력하는 데이터에 따라 변경되는 동적인 리소스를 응답 받습니다. 서블릿을 사용하면 사용자로부터 입력을 받고, 그 데이터에 맞는 웹페이지를 동적으로 생성할 수 있습니다.

 

'그냥 스프링의 컨트롤러 아냐?' 라고 생각할 수도 있지만, 

사실 우리가 구현하는 컨트롤러는 HTTP 를 통해 client 와 request/response 를 주고받지 않습니다.

 

그 기능은 스프링의 DispatcherServlet 이 하고 있죠.

 

DispatcherServlet 클래스의 상속 관계를 보면, Servlet API 의 구현체인 HttpServlet 을 확인 수 있습니다.

스프링은 사용자의 요청이 오면 DispatcherServlet(Front Controller) 라는 서블릿을 통해 요청을 받고 적합한 컨트롤러를 찾아 위임해주기 때문에, 개발자들은 컨트롤러에서 간편하게 클라이언트의 요청을 처리할 수 있게 됩니다.

 

 

서블릿 인터페이스는 다음과 같습니다.

package javax.servlet;

import java.io.IOException;

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}

init(), service(), destory() 메서드를 확인하고 이어서 서블릿의 라이프 사이클에 대해 살펴보겠습니다.

 

 

 

Servlet Container 와 LifeCycle

 

우리가 자바 코드에서 new 키워드를 사용하면 JVM 이 객체의 인스턴스를 생성합니다.

그렇다면 서블릿 클래스의 인스턴스를 생성하고 관리하는 것은 무엇일까요?

그것 또한 별도의 JVM 위에서 동작하는 자바 프로그램입니다. 이를 Servlet Container 라고 부릅니다.

 

그리고 Servlet Container 를 포함하고 있는 것이 바로 Tomcat, Jetty 등의 WAS(Web Application Server) 입니다.

Tomcat 에서 동작하는 서블릿의 LifeCycle 을 알아봅시다.

 

 

 

  1. Client 가 request 를 합니다.
  2. 톰캣이 가지고 있는 Connector 중 하나를 통해서 request 를 전달받습니다.
  3. 톰캣은 해당 요청을 처리하기 위해 적절한 Engine 을 거쳐 적절한 Servlet 에 매핑합니다.
  4. 요청이 서블릿에 매핑되면, 톰캣은 힙 메모리에 해당 서블릿의 인스턴스가 존재하는지 확인합니다. 존재하지 않는다면, 인스턴스를 생성하고 인스턴스의 init() 메서드를 호출하여 인스턴스를 초기화합니다.
  5. 서블릿이 초기화되면 톰캣은 여러 스레드를 생성하여 여러 요청을 처리합니다.
  6. 톰캣은 각 스레드의 단일 서블릿 인스턴스의 service() 메서드를 호출해서 HTTP 요청을 처리합니다. HTTP request method 에 따라 doGet, doPost 등의 메서드를 적절하게 호출합니다.
  7. 서블릿이 살아있는 동안, 다양한 상태 변화를 모니터링할 수 있는 listener 클래스를 사용할 수 있습니다. 톰캣은 다양한 방식으로 상태 변화를 저장 및 조회할 수 있고 다른 서블릿들도 해당 데이터에 접근할 수 있습니다.
  8. 사용하지 않는 서블릿은 destroy() 메서드를 호출하여 DB Connection 을 닫고, GC 의 대상이 되도록 합니다.

 

 

Servlet 의 필요성

Servlet 의 필요성을 두 가지로 정리해 보겠습니다.

 

Java 스레드를 통한 요청 처리

서블릿이 생기기 이전에는 CGI(Common Gateway Interface) 를 사용해서 외부와 데이터를 주고받았습니다.

CGI 는 C 또는 C++ 로 CGI 프로그램을 만들어서 request 마다 프로세스를 생성하여 동적인 컨텐츠를 만드는 방법입니다.

매 요청마다 프로세스를 생성했기 때문에 클라이언트 수가 증가할수록 많은 비용이 들었습니다.

 

 

이러한 CGI 의 단점을 극복하기 위해 Java 기반의 서블릿이 등장했습니다. 

 

 

서블릿의 장점은 다음과 같습니다.

  • 프로세스가 아닌 각 요청에 대해 스레드를 생성하기 때문에 비용이 낮습니다.
  • JVM 에 의해 관리되기 때문에 메모리 릭, GC 등을 걱정하지 않아도 됩니다.
  • Java EE Platform 의 표준으로 동일한 컨테이너와 동일한 서블릿을 사용하기 때문에 확장성이 있습니다.

 

 

SRP(Single Responsibility Principle) 원칙을 따른 설계

 

 

서블릿 컨테이너에서 Socket Connection, Thread 등의 관리를 하기 때문에 서블릿은 제공하고자 하는 서비스에만 집중할 수 있습니다.

또한 서블릿은 서블릿 컨테이너의 책임에 대한 의존성이 없어서, 다른 환경에서도 쉽게 재사용할 수 있게 됩니다.

 

 

 

Thread Pool 의 등장

Tomcat 3.2 이전 버전에서는 유저의 요청이 들어올 때 마다 Thread 를 하나씩 생성하고, 사용이 끝나면 destory 했습니다.

이러한 방식은 다음과 같은 단점이 있습니다.

  • 모든 요청에 대해 스레드를 생성하고 소멸하는 것은 OS 와 JVM 에게 필요없는 부하를 일으킵니다.
  • 동시에 다수의 요청이 들어올 경우 CPU 또는 메모리 리소스 소모에 대한 제한이 어렵습니다. 결국 순간적으로 서버가 다운되거나 동시 처리에 문제가 생길 수 있습니다.

 

문제에 대한 해결책으로 Tomcat 3.2 부터는 디폴트로 Thread Pool 을 사용합니다.

다음 포스팅에서는 Tomcat Thread Pool 에 대해 알아보도록 하겠습니다.

 

 

 

Reference

포스팅 내 이미지를 클릭하면 출처로 이동합니다.

 

복사했습니다!