숑숑이의 개발일기
article thumbnail

웹 서버, 웹 애플리케이션 서버

웹은 모두 HTTP를 기반으로 통신한다. HTTP 메시지에는 거의 모든 형태의 데이터 전송이 가능하다. 서버간 데이터를 주고 받을 때에도 대부분 HTTP를 사용한다.

  • HTML, TEXT
  • IMAGE, 음성, 영상, 파일
  • JSON, XML (API)

 

웹 서버(Web Server)

웹 서버는 인터넷을 기반으로 클라이언트에게 웹 서비스를 제공하는 컴퓨터이다. 클라이언트로부터 HTTP 요청을 받아 정적인 컨텐츠(HTML, CSS, JS, 이미지, 영상 등)를 제공한다. 이때 클라이언트는 HTML을 응답받고 서버는 HTTP에 대한 데이터를 만들어 응답한다. 대표적으로 NGINXAPACHE가 존재한다.

 

웹 애플리케이션 서버(WAS - Web Application Server)

웹 서버와 다르게 정적인 컨텐츠 뿐만아니라 동적인 컨텐츠를 제공한다. 또한 프로그램 코드를 실행해 애플리케이션 로직을 수행할 수 있다. 예시로 Tomcat, Jetty, Undertow가 존재한다.

  • 동적 HTML, HTTP API(JSON)
  • 서블릿, JSP, Spring MVC

 

웹 시스템 구성

실제 웹 시스템을 구성한다고 가정했을때 가장 간단하게 WAS+DB를 통해 시스템을 구성할 수 있다. WAS가 웹 서버의 기능인 정적 리소스를 제공할 수 있기 때문이다. 그러나 해당 시스템 구성은 아래의 단점을 가지고 있다.

  • WAS가 너무 많은 역할을 담당하여, 서버 과부하 우려가 있음
  • 가장 비싼 애플리케이션 로직이 정적 리소스 때문에 수행이 어려울 수 있음
  • WAS 장애시 오류 화면도 노출 불가능함

그래서 일반적인 웹 시스템 구성으로는 WS+WAS+DB가 있다. 이렇게 웹 시스템을 구성할 경우 정적리소스는 웹 서버가 처리하여 WAS는 애플리케이션 로직을 전담할 수 있다. 또한 웹서버에서 동적인 처리가 필요한 경우 WAS에 요청을 위임한다. 다시말해, 웹 서버와 WAS가 맡은 업무에 집중할 수 있다는 것이다.

 

또, 효율적인 리소스 관리가 가능하다. 정적 리소스 많이 사용시 WEB 서버를 증설하고, 애플리케이션 리소스가 많이 사용된다면 WAS를 증설하면 된다.

 

다른 장점으로는 정적 리소스만 제공하는 웹 서버는 쉽게 죽지 않는다. 그러나 애플리케이션 로직이 동작하는 WAS는 잘 죽으므로 중간에 WAS, DB 장애시 WEB 서버가 오류 화면을 제공할 수 있다.

인프런 김영한님 : 웹 MVC

 

서블릿(Servlet)

서블릿은 동적 웹 페이지를 만들 때 사용되는 자바 기반의 웹 애플리케이션 프로그래밍 기술이다. 서블릿은 웹 요청과 흐름을 간단한 메서드 호출만으로 체계적으로 다룰 수 있게 해준다.

 

예를들어 아래의 코드가 있다고 가정한다.

인프런 김영한님 : 웹 MVC

 

여기서 username은 kim, age는 20으로 폼을 전송했다고 생각해보자. 그럼 아래의 요청 http 메시지를 만들것이다.

인프런 김영한님 : 웹 MVC

만일 우리가 웹 애플리케이션 서버를 직접 다 구현해야 한다면 위의 http 메시지를 통해 아래의 과정을 거쳐야 한다.

  1. 서버 TCP/IP 연결 대기, 소켓 연결
  2. HTTP 요청 메시지를 파싱해서 읽기
  3. POST 방식, /save URL 인지
  4. Content-Type 확인
  5. HTTP 메시지 바디 내용 파싱 : username, age 데이터를 사용할 수 있게 파싱
  6. 저장 프로세스 실행
  7. 비즈니스 로직 실행 : 데이터베이스에 저장 요청
  8. HTTP 응답 메시지 생성 시작
    1. HTTP 시작 라인 생성
    2. Header 생성
    3. 메시지 바디에 HTML 생성에서 입력
  9. TCP/IP에 응답 전달, 소켓 종료

개발자가 직접 위의 과정을 모두 구현한다면 정작 우리가 구현해야할 비즈니스 로직에 집중할 수 없다. 그래서 7번을 제외한 모든 과정을 Servlet이지원해준다.

 

예시코드는 아래와 같다.

package hello.hellospring.controller;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(name = "helloServlet", urlPatterns = "/")
public class ApisServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse res) {
        // 애플리케이션 로직
    }
}


서블릿의 특징

  • 클라이언트의 요청(Request)에 대해 동적으로 작동하는 웹 어플리케이션 컴포넌트
  • MVC패턴에서 컨트롤러로 이용됨
  • 서블릿 컨테이너에서 실행됨
  • 보안 기능을 적용하기 쉬움

 

서블릿 컨테이너

톰캣처럼 서블릿을 지원하는 WAS를 서블릿 컨테이너라고 한다. 서블릿 컨테이너는 서블릿 객체의 생명주기를 관리한다.(생성, 초기화, 호출, 종료) 생성된 서블릿 객체는 최초 로딩 시점에 서블릿 객체를 미리 만들어두고 재활용하며. 싱글톤으로 관리된다.

  • 모든 고객 요청은 동일한 서블릿 객체 인스턴스에 접근
  • 공유 변수 사용에 특히나 주의
  • 서블릿 컨테이너 종료시 함께 종료
  • JSP도 서블릿으로 변환 되어서 사용
  • 멀티쓰레드 지원 및 관리

 

서블릿의 동작 과정

  1. HTTP 요청 메시지가 서블릿 컨테이너에 전송된다.
  2. 서블릿 컨테이너는 HTTP 요청 메시지를 파싱한 후 request, response 객체를 생성한다.
  3. 컨테이너는 설정파일(web.xml, @WebServlet)을 기반으로, 요청 URL과 매칭된 서블릿 객체를 찾는다.
  4. 해당 서블릿의 최초 요청이었을 경우, 서블릿 객체를 생성하고 init() 호출 한 뒤에 스레드를 생성, service() 메서드를 호출한다.
  5. 컨테이너는 response 객체에 담긴 데이터로 HTTP 응답 메시지를 생성하고 웹 브라우저로 전송한다.
  6. 서블릿 컨테이너는 distory() 메서드를 실행하여 request, response 객체를 소멸시킨다.

 

동시요청 - 멀티 쓰레드

쓰레드는 프로세스 내에서 독립적으로 실행되는 가장 작은 단위의 작업을 의미한다. 자바 메인 메서드를 처음 실행시에 main이라는 이름의 쓰레드가 실행된다. 쓰레드는 한번에 하나의 코드 라인만 수행하며 동시 처리가 필요하면 쓰레드를 추가로 생성한다.

 

하나의 쓰레드가 있다고 가정하고, 다중 요청이 들어왔다 가정한다.

만일 1번 요청을 처리하다 지연되는 경우, 2번 요청은 계속 지연되어 타임 아웃으로 오류가 발생할 수 있다.

 

그렇다면 이를 해결하기 위해서 요청 마다 쓰레드를 생성하는 것은 어떨까?

위의 과정은 아래의 장단점이 존재한다.

장점

  • 동시 요청을 처리할 수 있다.
  • 리소스(CPU, 메모리)가 허용할 때 까지 처리가능
  • 하나의 쓰레드가 지연 되어도, 나머지 쓰레드는 정상 동작

단점

  • 쓰레드 생성 비용은 매우 비싸다.
    • 요청이 올 때 마다 쓰레드를 생성한다면, 응답 속도가 늦어진다.
  • 쓰레드는 컨텍스트 스위칭 비용이 발생한다.
  • 쓰레드 생성에 제한이 없다 : 요청이 너무 많은 경우 메모리 임계점을 넘어가 서버가 죽을 수 있다.

요청마다 쓰레드를 생성하는 방법의 보완, 쓰레드 풀

쓰레드 풀

쓰레드 풀은 미리 일정 개수의 쓰레드를 생성하여 관리하는 기법이다. 쓰레드 풀에 생성 가능한 쓰레드의 최대치를 관리한다. 톰캣에서는 최대 200개가 기본 설정이다. (변경가능)

 

쓰레드 풀 사용시 쓰레드 생성 및 삭제에 따른 오버헤드를 줄일 수 있고, 특정 시점에 동시에 처리할 수 있는 작업의 개수를 제한할 수 있다. 이를 통해 시스템의 자원을 효율적으로 관리하고 성능을 향상시킬 수 있다.

 

사용시에는

  • 쓰레드 필요시, 이미 생성되어 있는 쓰레드를 쓰레드 풀에서 꺼내 사용
  • 사용 종료시, 쓰레드 풀에 해당 쓰레드를 반납
  • 최대 쓰레드가 모두 사용중이라 쓰레드 풀에 쓰레드가 없다면 => 요청 거절 혹은 대기

장점

  • 쓰레드가 미리 생성되어 있어 쓰레드를 생성하고 종료하는 비용이 절약, 응답시간이 빠르다
  • 생성가능한 쓰레드 개수의 제한으로 많은 요청이 들어와도 기존 요청을 안전하게 처리할 수 있다.

 

실무에서의 WAS 튜닝

  • WAS의 주요 튜닝 포인트는 최대 쓰레드(max thread) 수이다.
  • 값을 너무 낮게 설정 : 동시 요청이 많은 경우, 서버 리소스는 여유, 클라이언트는 금방 응답 지연
  • 값은 너무 높게 설정 : 동시 요청이 많으면, CPU, 메모리 리소스 임계점 초과로 서버 다운
  • 장애 발생시
    • 클라우드라면 => 서버를 늘리고, 이후 튜닝
    • 아니라면 => 열심히 튜닝

고로 동시요청이 많을때는 CPU 사용량이 50%는 되어야 정상적인 것이다.

 

그렇다면 쓰레드 풀의 적정 숫자는 어떻게 찾을까?

쓰레드 풀의 적정 숫자는 애플리케이션 로직의 복잡도, CPU, 메모리, IO 리소스 상황에 따라 모두 다르다. 고로 최대한 실제 서비스와 유사하게 성능 테스트를 시도한다. 툴로는 아파치 ab, 제이미터, nGrinder가 있다.

 

WAS의 멀티 쓰레드 지원

멀티 쓰레드에 대한 부분은 WAS가 처리한다. 고로 개발자는 멀티 쓰레드 관련 코드를 신경쓰지 않아도 된다. (= 마치 싱글 쓰레드 프로그래밍을 하듯이 편리하게 소스 코드를 개발하면 된다.) 그러나 멀티 쓰레드 환경이므로 싱글톤 객체(서블릿, 스프링 빈)는 주의해서 사용하도록 한다.

 

 

HTML, HTTP API, CSR, SSR

 

정적 리소스

  • 고정된 HTML 파일, CSS, JS, 이미지, 영상 등 제공

HTML 페이지

  • 동적으로 필요한 HTML 파일을 생성해서 전달
  • 웹 브라우저는 HTML을 해석한다

 

HTTP API

  • HTML이 아닌 데이터를 전달한다. 주로 JSON 형식을 사용하며, 다양한 시스템에서 호출한다.
  • 데이터만 주고받고 UI 화면이 필요하면, 클라이언트가 별도 처리
  • 앱 클라이언트 to 서버, 웹 클라이언트 to 서버, 서버 to 서버

UI 클라이언트에서의 접점은?

  • 앱 클라이언트(아이폰, 안드로이드, PC 앱)
  • 웹 브라우저에서 자바스크립트를 통한 HTTP API 호출
  • React, Vue.js와 같은 웹 클라이언트

서버 to 서버의 예시

  • 주문서버 -> 결제 서버
  • 기업간 데이터 통신시

 

CSR(클라이언트 사이드 렌더링)

  • HTML 결과를 자바스크립트를 사용하여 웹 브라우저에 동적으로 생성해서 적용한다
  • 주로 동적인 화면에 사용하며, 웹 환경을 앱처럼 필요한 부분부분을 변경할 수 있다.
  • ex) 구글 지도, Gmail, 구글 캘린더
  • 관련기술: React, Vue.js

 

SSR(서버 사이드 렌더링)

  • 최종 생성 HTML을 서버에서 만들어 웹 브라우저에 전달
  • 주로 정적인 화면에 사용한다.
  • SSR을 사용하더라도, 자바스크립트를 사용해 화면 일부를 동적으로 변경할 수 있음.
  • 관련기술: JSP, 타임리프

 

위 글은 김영한 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술의 일부 내용을 정리한 것입니다.
profile

숑숑이의 개발일기

@숑숑-

풀스택 개발자 준비중입니다