Skip to content

Latest commit

 

History

History
77 lines (48 loc) · 11.5 KB

basic.md

File metadata and controls

77 lines (48 loc) · 11.5 KB

서블릿 컨테이너

웹 애플리케이션 서버의 역할

  • 웹 애플리케이션 서버는 클라이언트와 서버 간의 소켓 통신에 필요한 TCP/IP 연결 관리와 HTTP 프로토콜 해석 등의 네트워크 기반 작업을 추상화해 일종의 실행 환경을 제공합니다.
  • 이런 실행 환경에서 웹 프로그램을 작성하는 프로그래머는 웹 애플리케이션 서버에서 제공하는 요청, 응답이라는 개념 위에서 구현을 시작합니다.
  • 따라서 웹 프로그래머는 TCP/IP 연결을 직접 생성하고 HTTP 프로토콜을 해석하는 과정을 생략해 웹을 쉽게 구현할 수 있습니다.

서블릿 컨테이너

  • 엄밀하게 말해 웹 애플리케이션 서버는 Java EE 명세를 만족시키는 Java 구현체를 의미하지만, 웹 프로그래밍을 위한 미들웨어라는 개념이 일반화되면서 자바 이외의 프로그래밍 언어로 작성한 서버도 비슷한 역할을 하면 웹 애플리케이션 서버라고 부릅니다.
  • 스프링 처럼 여러 경량 프레임워크가 인기를 얻고 널리사용 되고 있습니다. 경량 프레임워크도 Java EE 정의 중 웹 애플리케이션 기술 위에서 동작하는 것이 일반적이며, 이런 웹 애플리케이션 기술에 대한 구현체가 바로 서블릿 컨테이너 입니다.
  • 서블릿 컨테이너는 웹 애플리케이션 서버중에서 HTTP 요청을 받아 차리하는 기초 역할을 맡고 있습니다.
  • 대부분의 웹 프레임워크가 제공하는 기능은 서블릿 컨테이너 위에서 동작하는 서블릿, 필터, 이벤트 리스너 등을 적절히 조합해 구현한 것입니다. 따라서 웹 프레임워크로 작성한 웹 애플리케이션은 결국 서블릿 컨테이너 위에서 동작합니다.
  • 서블릿 컨테이너의 주요 목표는 서블릿을 동작시키는 것입니다.

HTTP 프로토콜의 간략한 소개

클라언트가 보낸 메시지는 HTTP 프로토콜에 실려 서블렛 컨테이너에 전송됩니다. 전성된 메시지를 서버 측에서 재구성 하려면 서블릿 컨테이너는 반드시 HTTP 프로토콜 해석 기계를 구현해야 합니다. 이는 HTTP 프로토콜을 이해 해야만 하는 이야기입니다.

적정 병렬 진행수

대부분의 서블릿 컨테이너는 서블릿을 동작시키는 스레드의 숫자를 설정할 수 있습니다. 서블릿을 처리하기 위해 스레드 풀이 내부적으로 유지하는 워커 스레드 숫자를 지정할 수 있으며 이 값을 어떻게 설정하느냐에 따라 서블릿 컨테이너 전체 처리량이 크게 영향을 받게 됩니다.

컴퓨터의 연산 처리 CPU가 수행합니다. 즉 CPU가 가진 코어의 갯수만큼 계산 작업이 동시에 처리할 수 있습니다. 이 말은 서블릿 컨테이너가 CPU 코어 수 만큼의 스레드를 사용한다면 한 개의 이상의 CPU 코어는 서블릿 컨테이너가 사용하지 못한다는 의미입니다. 따라서 최대 스레드 수 설정은 CPU 코어 수보다 작지 않아야합니다.

또한, 병렬 진행되어야 하는 스레드의 수가 CPU 코어 수보다 큰 경우, 모든 스레드가 동시에 CPU 코어를 사용할 수 없습니다. 다시 말해, 다수의 스레드가 소스의 CPU 코어를 이용하려면 특정 시간에 반드시 하나 이상의 스레드가 CPU 코어를 점유하지 않고 대기 상태에 있어야합니다. 그러므로 처리하는 일이 CPU 코어를 점유하지 않고 대기 상태에 있어야합니다. 그러므로 처리하는 일이 CPU 연산 능력에만 의존하는 경우 CPU 코어마다 하나의 스레드가 할당되어 설정하는 것이 최적입니다. 이렇게 설정하면 일을 처리하는 모든 스레드는 대기 상태로 빠지지 않고 작업을 계속할 수 있습니다.

그렇다면 결국 CPU 코어 수만 세면 최적의 스레드 수를 찾은 것일까요? 그렇지 않습니다. 앞선 내용 중 'CPU 연산 능력에만 의존하는 경우' 라는 조건에 유의해야합니다. 현실적으로 CPU 연산에만 의존하는 병렬처리는 찾기가 어렵습니다. 좀 더 정확히는 CPU 연산에 주로 의존하는 작업을 병렬처리하기 위해 재구현하면 원래 제약조건인 CPU 연산 외의 병렬처리 자체가 내재된 다른 제약 조건이 발생합니다.

CPU 코어 네 개인 시스템의 10만 팩토리얼 연산 작업

스레드수 걸린시간(ms)
1 46433
2 16903
4 7924
8 6746
16 6908

스레드의 수가 8개일 때 가장 연산이 빨랐습니다. 쓰레드가 4개일 때가 16개일때 보다 더 좋은 결과가 나온것에 유의하십시오. 예상과다른 결과가 나온 원인을 알아보겠습니다.

병렬화 기능하지 않은 영역의 존재

CPU 코어 수와 스레드 수를 일치시킬 때 최대 처리량을 얻을 것이라는 추측은 모든 CPU 코어를 최대한 동작시킬 방법이 있다는 가정하에 설득력이 있으며, 이런 병렬화가 가능하지 않은 영역에 존재하지는 경우 그 정당성이 상실합니다.

병렬처리로 인해 부가되는 추가 제약조건

서블릿 컨테이너의 최대 처리량에 대한 제약 조건이 CPU 처리량이 아니라 I/O 처리량 이라는 사실을 기억하세요. 그렇다면 가능한 동시에 많은 스레드를 사용해 I/O 처리량을 극대화하는 전략이 더 나은 선택이 될수 있을수 도 있지만 무작정 스레드의 수가 많을수록 처리량이 늘어나지는 않습니다. 스레드도 생성하고 유지하는 관리에 비용이 드는 자원이기 때문입니다.

컨텍스트 스위칭

물리적인 제약 외에도 스레드 수가 많아지면 그 자체가 성능 저하의 원인이 됩니다. 앞서 CPU 코어 수 이상의 스레드는 동시에 활성화 되지 못하고 대기 상태에 머물러 있다고 언급했습니다. 멀티태스킹을 지원하는 대부분의 현대적인 운영체제는 CPU 코어 수보다 많은 프로세스, 스레드들이 마치 동시에 수행되는 것처럼 보이게 합니다. 이를 시분할 기법을 사용하며 하나의 프로세스와 스레드가 일정 시간 CPU를 사용한 후에 다른 프로세스나 스레드에 사용을 양보한 후 대기하다가 자기 차례가 오면, 앞서 수행한 단계 다음부터 진행을 계속 할 수 있어야합니다. 따라서 양보의 시점에서 수행하던 일의 차례, 계산해 놓았던 중간 결과물 등의 일처리 때문에 문맥을 저장해 놓다가 다시 자신의 차례까 오면 저장했던 정보를 처리 문맥을 되돌리는 컨텍스트 스위칭(문맥교환)이 발생합니다.

쓰레드의 수가 많아지면 CPU 점유에 대한 경쟁 조건이 심화되므로 자연히 컨텍스트 스위칭이 그만큼 더 자주 발생합니다. 그런데 컨텍스트 스위칭은 병렬로 처리하는 것처럼 보이게 하는 데 소요 되는 비용이지 처리량을 높이는 것과는 무관합니다. 따라서 컨텍스트 스위칭의 발생을 억제할수록 성능이 개선됩니다. 이런 이유로 스레드의 개수는 특정한 한계가 분명히 있습니다.

서블릿 컨테이너와 그 위에 동작하는 서블릿의 조합으로 구성된 특정 사이트의 성능을 최대하화기 위해서 서블릿 컨테이너가 가져야하는 스레드의 수는 얼마인가? 그리고 그 답변은 해당 사이트에서 사용하는 서블릿의 특성에 따라 좌우되므로 실제 상황에 알맞게 설정 값을 적절히 변경해 가면서 최적의 값을 찾아내야 합니다.

BIO와 NIO의 비교

일반적인 프론트엔드 웹서비스 구성

서블릿 컨테이너는 외부와 HTTP로 통신하는 서빌릇을 관리하므로 웹 서비그 구성 요소 중 클라이언트와 가장 가까운 프론트엔드에 위치합니다.

하지만 실제로는 서블릿 컨테이너를 외부로 노출해 서비스하기보다는 Nginx, Apache, IIS와 같은 웹 서버를 앞에 세워 서블릿 컨테이너와 통신하는 구성이 조금더 일반적입니다. 이런 그조의 장점은 크게 보아 두가지입니다.

첫 번째, 이미지 및 HTP 파일 같은 동적으로 생성되지 않은 static 파일은 전문적인 웹 서버가 좀 더 효율적으로 처리할 수 있습니다. 왜냐하면, 개별 OS에 특화된 시스템 콜을 호출하는데 제약이 없는 네잍브 바이너리로 제공되는 웹서버이 비해 JVM이 추상화된 레벨로 한 번더 감싼 I/O를 사용해야 하는 서블릿 컨테이너는 성능상 제약이 분명히 있기 때문입니다.

두 번째, 동시에 동작 가능한 스레드의 수가 전체 웹 서비스의 단위 시간당 처리량을 제한합니다. Java 1.4가 java.nio를 제공하기 전까지 모든 I/O는 블로킹 모드로만 동작했습니다. 따라서 클라이언트가 접근하면 서블릿 컨테이너는 해당 클라이언트와 연결된 소켓을 처리하는 스레드를 할당해야 했습니다. 일반적으로 한 대의 기계가 연결할 수 있는 소켓의 수는 유지할 수 있는 스레드의 수보다 훨씬 큽니다. 스레드가 프로세스보다 공유하는 메모리가 크다 할지라도 필요한 리소스의 크기를 무시할수 없기 때문입니다.

따라서 일반적인 웹서비스 시스템은 서블릿 컨테이너 앞단에 OS 레벨에서 지원되는 non-blocking I/O를 사용해 동시 처리 능력을 높인 웹 서버를 세우고, 서블릿 컨테이너는 서블릿을 동작하는 데 집중시켜 전체 시스템의 처리량을 최대한 높입니다.

프론트엔드 서버로서의 서블릿 컨테이너

이제 대부분의 서블릿 컨테이너가 Non-blocking IO를 지원하므루, 굳이 웹서버를 앞에 세우지 않아될것 처럼보입니다. 하지만 지금까지도 서블릿 컨테이너를 클라이언트에 직접 노출하는 경우는 그렇게 많지 않습니다.

대표적인 이유 중 하나느 서블릿 처리 자체가 원래 동기적이란 것입니다. Non-blocking IO를 사용하려면 IO 대기를 최소화하여 CPU 사용량을 극대화 할 수 있지만, 이것이 가능해지려면 작업 동기화가 필요한 위치, 일반적으로는 IO 대기가 발생하는 지점을 경계로 나눌 수 있어야하고 각 나눤 작업이 동시에 병렬로 처리할 수 있어야합니다. 하지만 서블릿은 HttpServlet을 상속하여 doXXX메서드를 오버로딩하도록 구현하여, doXXX메서드는 HTTP 요청을 동기적으로 수생합니다. 따라서 Non-blocking IO의 장점을 살릴 수 있는 부분은 서블릿이 수행되거전에 HTTP 요청을 받아들여 프로토콜에 따라 파싱하고 서블릿 요청을 생성하는 커넥터.리스너 모듈이 주된 대상입니다. 하지만 doXXX메서드의 내에 존재하는 비즈니스 로직 실행은 앞서 말한바와 같이 동기적으로 수행하기 때문에 하나의 워커 스레드를 할당하여 처리할 수 밖에 없습니다.

다시말해 Non-blocking IO를 사용해서 많은 요청을 받아들여도 동시에 실행 할 수 있는 서블릿 인스턴스 수는 해당 기계의 가용 스레드 수를 초과할 수 없음으로 HTTP 요청을 처리하는 전체 단위 시간당 처리량의 관점에서 blocking IO를 사용하는 방식과 큰 차이가 없습니다.

출처