본문 바로가기

3. 기술 공부/Java (Spring, Spring Boot)

[Java Servlet] 2. Servlet과 Servlet Container

Connected Posts

2021.07.16 - [3. 기술 공부/Java (Spring, Spring Boot)] - [Java Servlet] 1. Sync? Async?, Blocking? Non-Blocking?

2021.07.16 - [3. 기술 공부/Java (Spring, Spring Boot)] - [Java Servlet] 2. Servlet과 Servlet Container

2021.07.16 - [3. 기술 공부/Java (Spring, Spring Boot)] - [Java Servlet] 3. Servlet 3.0, 3.1 그리고 Spring MVC

Goal

[Java Servlet] 시리즈 포스트에서는 Java Servlet에 대한 간단한 개념과, Java Servlet 역사에 큰 breakpoint가 있었던 Servlet 3.0(Async Servlet), 3.1(Non-blocking I/O)에 대해 정리해보려고 한다. 이번 포스팅에서는 breakpoint를 이해하기 위해 기초가 될 개념인 Servlet과 Servlet Container에 대해 알아보도록 하겠다.

1. Servlet?

 

 

  • javax.servlet.Servlet 인터페이스의 구현체

  • 자바를 이용하여 웹서비스를 만들기 위해 정의된 인터페이스

  • Java EE의 표준 API 중 하나

서블릿이 어떤것이냐고 물으면 꼬집어 말하기가 어려운데, 이는 컨텍스트에 따라서 의미하는 바가 조금씩 달라서 그런 같다.

원칙적으로 Servlet javax.servlet 패키지의 Servlet 인터페이스를 칭하는 것이다. 인터페이스는 자바를 이용하여 웹서비스를 만들기 위한 목적으로 정의되었고, Java EE 표준 API 포함되어있다.

또 다른 의미에서 서블릿이라고 하면, 서블릿 인터페이스를 구현한 구현체를 뜻할 때도 있다. 대표적으로는 스프링에서 사용하는 DispatcherServlet이 있다.

 

서블릿 프로그램이 보통의 자바 프로그램과 다른 점이 하나 있는데, 메인 메소드가 없다는 점이다. 이유는 바로 서블릿은 메인 메소드에 의해 라이프사이클이 관리되지 않고, 서블릿 컨테이너라는 별도의 프로그램에 의해 관리되기 때문이다.

 

2.Servlet Conainer?

  • 서블릿을 관리하는 역할
  • 서블릿 클래스의 로드, 초기화, 호출, 소멸까지의 라이프사이클을 관리해줌

  • 대표적으로 Tomcat

서블릿 컨테이너는 서블릿의 생성부터 소멸까지의 라이프 사이클을 관리한다. 이런 서블릿 컨테이너가 등장하기 이전에는 개발자가 웹서버와 통신하는 애플리케이션을 만들기 위해서, 소켓을 생성하거나 포트에 리스닝하거나 IO stream 생성하는 등의 처리를 직접해주어야 했는데, 이런 부분들을 서블릿컨테이너를 사용함으로써 블랙박스화하여 보다 쉽게 프로그래밍을 있게 되었다.

우리가 알고 있는 대표적인 Servlet Container 톰캣이 있다.

그림은 Http Request 전달 과정을 간략하게 정리한 것인데, 먼저 사용자가 URL 호출하면 HTTP Request 클라이언트에서 서블릿 컨테이너로 전달이 된다.

서블릿 컨테이너는 전달이 된 리퀘스트에 대해 HttpServletRequest, HttpServletResponse 객체를 생성하고, 사용자가 요청한 URL 분석하여 매핑되어야 서블릿을 찾는다.

컨테이너는 서블릿 service() 메소드를 호출하고, HTTP method 따라서 서블릿 적절한 메소드가 호출되며 요청이 처리되는 것이다.

 

3. Servlet 3.0 이전

앞에 설명한 전달과정을 좀더 간단히 축약하자면, 서블릿을 사용하는 애플리케이션의 데이터 흐름은 2단계로 나눌 있다.

그림에서처럼 1) Client에서 Servlet container간의 흐름, 다음 이어지는 2) 서블릿 컨테이너에서부터 서블릿간의 흐름이다. 포스팅의 전체 서블릿 3.0과 3.1에서의 변화 이전에, 그보다 앞단인 클라이언트와 서블릿 컨테이너 간의 변화가 먼저 있었다.

 

앞단의 가장 핵심적인 변화는 Connector 변화였다. 톰캣의 커넥터 클라이언트와 서블릿 컨테이너가 커뮤니케이션하기 위한 구성요소이다.

 

3-1. BIO Connector

  • blocking 방식
  • One Thread Per Connection 모델
  • Connection마다 Thread가 할당되어 유휴 Thread가 발생하여 자원의 낭비가 발생

기존 Servlet 2.5 구현하는 Tomcat 6까지 Default HTTP Connector BIO connecto를 사용했다. 그림을 서블릿 컨테이너에 좀더 집중하여 표현해보았다. 

서블릿 컨테이너에서는 소켓의 관리도 함께 해주는데, 소켓 커넥션을 처리할 스레드로 처리하게 된다.  스레드는 스레드 풀에 의해 관리가 되기 때문에 소켓과 연결된 요청을 프로세싱하고, 요청에 대한 리스폰즈를 돌려준 소켓 커넥션이 종료되면 풀로 다시 반환이 된다. 이렇게 되면 커넥션이 완전히 클로즈될 때까지 하나의 Thread 특정 Connection 계속 할당이 되어있다. 여기서 바로 blocking이 발생한다.

이러한 blocking 방식으로 커넥션마다 Thread 할당해서 사용하게 되면, 동시에 사용되는 Active Thread 개수가 동시 접속자수가 된다. 또한 이렇게 커넥션마다 할당해서 클로즈될 때까지 기다리게 되면, 모든 스레드가 활발히 사용되지 않고 대기하는 경우가 발생하면 낭비되는 시간이 많이 발생할 밖에 없다. 즉 스레드를 제대로 활용할 수가 없다. 따라서 이러한 문제점을 해결하기 위해 NIO Connector 등장하게 되었다.

 

3-2. NIO Connector

  • non-blocking 방식
  • One Thread Per Request 모델
  • BIO Connector 보다 Thread를 잘 공유할 수 있음

NIO Connector BIO Connector 달리 커넥션이 맺어지면 바로 새로운 스레드를 할당하지 않고, Poller라는 개념의 스레드에게 커넥션을 넘겨주게 된다. 폴러는 소켓 커넥션들을 캐시로 들고있다가, 해당 소켓에 데이터에 대한 처리가 가능한 순간에만 스레드를 할당하는 방식을 사용해서 스레드가 유휴 상태로 낭비되는 시간을 줄여준다.

“Thread per request”라고 불리는 모델은 고정된 개수의 스레드로 증가하는 유저 커넥션 수를 핸들링할 있다. 커넥션과 스레드 개수가 1 1 매핑이 아니기 때문에  스레드 개수 이상 커넥션을 유지할 있게 된다. 모델에서는 이전 BIO Connector보다 스레드 리소스를 잘 공유할 수 있게 된 것이다. 그래서 Tomcat 8버전부터 NIO default connector 되었고, 8.5x부터는 BIO 지원이 완전히 종료되었다.

 

이 NIO connector 등장과 함께 client 서블릿 컨테이너 communication non-blocking 

적용되었다. 하지만 다음 단인 서블릿 컨테이너에서 서블릿 간의 커뮤니케이션은 여전히 blocking이었다. 이렇게 되면 connection 의해 thread blocking되는 것은 막았지만, 결국 리퀘스트를 처리하는 서블릿을 호출할 Servlet 3.0이전까지 서블릿 단은 아직 동기, 블로킹 방식으로 동작했기 때문에 스레드가 다시금 블로킹되는 현상이 발생하는 것이다.

 

결국 동기 방식(syncronous)으로 실행되는 request processing이가 실제 응답이 나가기 전까지 스레드를 오래동안 실행시키게 되고, 서블릿 컨테이너의 스레드는 고갈되고 말 것이다.

thread 개수를 증가시켜 많은 동시 request 처리하면 되지않냐?라고 생각할 있지만, 컨텍스트 스위칭이나 장비 증설 추가적인 코스트가 발생한다. 따라서 애플리케이션이 높은 동시성을 요구하는 경우에 다른 접근 방법을 찾아야 필요가 생겼고, 이에 따라 Servlet 3.0, 3.1에서 Async Servlet과 Non-blocking I/O를 지원하게 된 것이다.

 

오늘은 왜 Servlet 3.0, 3.1에서 Async Servlet과 Non-blocking I/O를 지원하게 되었는지 그 배경에 대해서 살펴보았다. 다음 포스팅에서는 Servlet 3.0, 3.1에서의 변화를 위주로 다루어보겠다.