Web Server, WAS
WAS(Web Application Server)는 한마디로 정의되는 용어지만 한마디로 이해할 수 있을 만큼 쉬운 개념은 아니다. 이 포스팅에서는 WAS가 무엇인지, 왜 필요한지, 내부적으로 어떻게 요청을 처리하는지 알아볼 것이다. WAS를 이야기 하려면 Web Server를 빼놓을 수 없기에 Web Server와 함께 WAS를 풀어가도록 하겠다.
- Web Application : Web Application이란 말 그대로 웹 상에서 실행되는 어플리케이션이다. 어플리케이션이 실행되는 위치에 따라 Client-side, Server-side로 나뉜다.
- Web Server : Web Server는 클라이언트로부터 HTTP 요청을 받았을 때 정적인 컨텐츠를 제공하는 서버로 C언어로 작성되어 있다. 여기서 정적인 컨텐츠란 html, css, js, jpg, gif 등과 같이 항상 일정한 UI를 제공하는 파일을 말한다.
- WAS : WAS는 클라이언트로부터 HTTP 요청을 받았을 때 동적인 컨텐츠를 처리, 제공하기 위한 미들웨어로 Java로 작성되어 있다. 여기서 동적인 컨텐츠란 사용자의 입력에 따라 결과가 달라지는 컨텐츠를 말한다. WAS는 Web Server의 기능도 겸하고 있으며 내부적으로 Servlet Container를 가지고 있는 것이 가장 큰 특징이다.
- Middleware : 미들웨어란 2개의 포인트 사이에서 데이터를 주고 받을 수 있도록 매개체 역할을 하는 소프트웨어다.
- 3-tier Architecture : 3계층 구조란 특정 플랫폼을 3분할하여 물리적으로 구현한 것을 말한다. 여기서 특정 플랫폼이란 일반적으로 서버를 말한다. (layer = 논리적인 계층 구조, tier = 물리적 구조)
3-tier Architecture
[Web Server ↔ WAS ↔ DB Server]로 나누어 3계층으로 보는 구조다. 여기서 클라이언트는 계층에 포함시키지 않고 3 계층으로 보는데 클라이언트를 포함시켜 4-tier라고 부르는 경우도 있다. 일반적으로 웹 페이지가 위와 같이 구성된다.
[Client ↔ WAS ↔ DB Server]로 나누어 3계층으로 보는 구조로, Web Server가 별도로 존재하지 않고 WAS 가 Web Server의 역할을 같이 한다는 것이 특징이다. 웹 사이트에 접속할 때에는 URL을 통해 해당 사이트에 대한 html을 요청하고 다운받은 html을 브라우저에 표시하는 것인데 일반적인 모바일 어플리케이션의 경우 GUI가 이미 구성되어 있고 UI에 표시할 데이터만 요청한다. 이러한 경우 Web Server가 생략될 수 있고 3-tier에 클라이언트를 포함시킨 위와 같은 구성이 쓰인다고 볼 수 있다.
3-tier Architecture라고 해서 반드시 위와 같이 구성되는 것은 아니며 클라이언트 아래에 적어 놓은 웹이나 앱도 하나의 예시일 뿐이다. 제시한 구조 외에도 WAS가 Web Server, DB Server가 하는 일을 모두 할 수 있기 때문에 WAS만 사용하는 1-tier로 구성되기도 한다. 위에 나온 방식들은 서버를 구성하는 하나의 구조일 뿐이며 서버나 프로젝트 규모, 환경에 따라 얼마든지 구조가 달라질 수 있음을 인지하도록 하자.
위에서 알아본 내용을 정리하면 Web Server는 정적인 컨텐츠를 처리하는 서버, WAS는 동적인 컨텐츠를 처리하기 위한 미들웨어다. 또한 서버를 환경에 따라 분할하는 것을 n-tier Architecture 라고 하며 분할의 기준은 프로젝트 규모, 환경, 제공하는 서비스 등에 따라 얼마든지 달라질 수 있다.
서버를 분할하는 이유
앞서 언급했듯 WAS는 크게 Web Server, Container 기능을 수행한다. 이 외에도 트랜잭션, 보안, 트래픽 관리, DBCP, 사용자 관리 등의 기능을 제공한다. 나열한 기능들을 살펴 보면 WAS가 Web Server, DB Server의 기능을 모두 가지고 있는 것을 알 수 있는데 WAS만으로 서버를 구성할 수 있음에도 굳이 서버를 나누는 이유가 무엇일까?
서버를 분할했을 때 성능 측면에서 보면 처리해야 할 작업을 적절하게 나누어 처리량을 분산시키는 효과가 있다. 사용자에게 보여 줄 인터페이스에 대한 요청은 Web Server가, 비지니스 로직을 처리해야 하는 작업은 WAS가, 비지니스 로직 처리 중 DB 조작이 필요하면 DB Server에서 데이터를 읽고 쓰는 것으로 역할을 분산시킨 것이다. 이렇게 Web Server의 작업을 세분화하면 하나의 물리적인 서버에 트래픽이 집중되어 과부화가 걸리는 것을 방지할 수 있다. 그 외에도 속도, 보안, 성능 개선, 서버 장애 대처 등의 이유가 있다.
Container
WAS에 대해 검색하면 "WAS는 Web Container 혹은 Servlet Container 라고도 부른다." 라는 문장을 단골 멘트처럼 볼 수 있다. 두 개의 이명에 공통적으로 들어가 있는 Container는 위에서 WAS의 기능을 소개할 때 Web Server와 함께 등장했었다. Servlet Engines라고도 불리는 Servlet Container에 대해 알아보자.
- Container : 컨테이너란 JSP, Servlet과 같은 동적인 컨텐츠를 생성하고 관리하는 서버 사이드의 소프트웨어다. 클라이언트의 요청을 받으면 Thread Pool에서 wait 상태인 스레드를 깨우고 요청에 맞는 Servlet을 찾는다. 스레드가 서블릿을 통해 요청을 처리한 결과를 컨테이너에게 반환하면 그 결과를 클라이언트에게 반환한다. 이 과정에서 Servlet의 생성, 동작, 소멸 등 Servlet에 대한 전반적인 생명 주기를 관리한다.
- Servlet : WAS에서 동적인 컨텐츠를 처리하는 소프트웨어로 Java 언어로 작성된다. 서버가 구동된 뒤 특정 서블릿에 대한 요청이 발생할 때 비로서 메모리에 올라가며 스레드에 의해 동작한다.
- Servlet Life cycle : 서블릿은 [int → service → destroy] 의 생명주기를 갖는다. 서블릿의 생명주기는 생성부터 소멸까지 모두 컨테이너에 의해 관리된다.
- JSP(Java Server Page) : Servlet과 유사한 기술인 JSP는 사용자에게 보여질 View(페이지)를 작성할 때 사용되며 html 기반에서 스크립트 형태로 작성된다.
- JSP Container : JSP를 서블릿으로 변경할 때 사용되는 소프트웨어로, 서블릿으로 구현되어 있다.
- Thread Pool : 작업을 처리하기 위해서 특정 개수의 스레드를 생성해놓고 작업 큐에 들어오는 작업을 하나씩 할당받아 처리하는 것을 말한다. 스레드는 요청이 발생했을 때 컨테이너에 의해 실행되고 서블릿을 실행한다. Tomcat의 옛 버전(3.2 이전)에서는 작업이 발생할 때마다 스레드를 생성하고 서블릿 실행을 맡긴 뒤 작업이 끝나면 스레드를 소멸시켜주는 등 Thread에 대한 전반적인 관리가 필요했고 이는 많은 문제를 야기하였다. Thread Pool의 등장으로 스레드 관리가 훨씬 간단해졌다.
- Filter : 서블릿 컨테이너의 서블릿은 서버의 최종 자원이라고 할 수 있다. 여러 과정을 거쳐 마지막에 요청을 처리한 뒤 클라이언트에 결과를 반환하는 요청 처리 과정의 종점인 것이다. 필터는 순서상 서블릿 앞에 존재하며 데이터를 처리하기 전, 데이터를 처리한 후에 반드시 거치게 영역이다. 서블릿을 처리하기 전에 반드시 수행해야 하는 작업(사용자 인증, 인코딩 등)을 수행하게 된다.
알아본 내용을 간단하게 정리해보자. Container란 JSP, Servlet과 같이 동적인 컨텐츠를 생성 및 관리한다. 클라이언트로부터 요청이 발생하면 Thread Pool에서 대기 중인 스레드를 할당받는다. 요청을 처리하기 앞서 필터링을 수행하고 관련된 서블릿을 찾아 비지니스 로직을 처리, 다시 필터링을 거친 뒤 결과를 반환한다.
Servlet
- Servlet의 생명 주기는 각각 javax.servlet.Servlet 인터페이스의 init(), service(), destroy() 메서드와 같다.
Servlet (https://docs.oracle.com/javaee/7/api/javax/servlet/Servlet.html) - 'init'은 해당 서블릿에 대한 요청이 최초로 발생했을 때 단 한번만 수행된다. Class Loader로 서블릿 클래스를 로딩하여 인스턴스화하고 init() 메서드가 호출된다.
- 'service'는 해당 서블릿에 대한 요청이 발생했을 때 수행된다. 요청이 오면 service() 메서드를 수행하여 비지니스 로직을 처리하고 결과를 반환한다.
- 'destroy'는 서버의 종료 혹은 서블릿 컨테이너가 메모리에서 소멸될 때 수행된다.
- init()이 단 한 번만 수행되는 이유는 서블릿은 싱글톤으로 메모리에 상주하다가 서버가 종료될 때 같이 소멸하기 때문이다. 즉 init()은 생성자 역할을 하며 하나의 서블릿 객체는 JVM마다 단 하나 씩만 존재한다는 것이다.
- 서블릿 객체는 단 하나만 존재하는데 여러 개의 요청이 하나의 서블릿을 공통적으로 실행해야 할 경우 멀티 스레딩으로 요청을 처리한다.
- 서블릿은 최종적으로 javax.servlet.Servlet를 구현한 HttpServlet을 상속한 클래스로 구현된다. (HttpServlet 클래스는 Servlet 인터페이스를 상속받고 있다.)
- HttpServlet를 상속받은 클래스는 일반적으로 doGet, doPost 라는 메서드를 오버라이드하게 되는데 HTTP 프로토콜의 메서드 타입에 따라 달리 호출된다.
- doGet, doPost 메서드는 파라미터로 HttpServletRequest, HttpServletResponse 객체를 가지는데 각각 요청 정보, 응답 정보를 가지고 있다.
- WebSocket 어노테이션, 설정 파일(web.xml)에서 url을 매핑해줄 수 있다. 이 값은 서블릿을 식별하는 식별자가 될 것이다.
WAS 동작 흐름
WAS에서 동작을 그림으로 표현하였다. Web Server가 따로 존재하지 않고 WAS에서 Web Server 역할을 같이 한다고 가정하자. 클라이언트로부터 "http://localhost:8080/app/search?keyword=aws" 라는 요청을 받았고 "/search"는 keyword 값인 "aws" 해당하는 검색 결과를 반환해주는 서블릿이라고 가정한다.
먼저 요청받은 URL을 파싱해보자.
- localhost(IP or Domain) : URL에서 ip 혹은 도메인에 해당하는 영역이다. 클라이언트와 서버가 같은 네트워크 상에 있다면 localhost를 사용한다.
- 8080 : 서버가 사용하는 포트에 해당하는 영역이다. ip에 해당하는 컴퓨터를 찾아 간 후 8080 포트를 사용 중인 서버를 찾아간다.
- app : 웹 어플리케이션 이름에 해당하는 영역이다. app이라는 이름을 사용 중인 웹 어플리케이션을 찾아간다.
- /search : "/search"에 해당하는 서블릿을 찾아간다.
동작 과정
(빨간 화살표가 내부 흐름이며 그림의 번호는 화살표의 순서를 의미할 뿐 설명 순서와 상관없음.)
- 사용자가 웹 주소창에 "http://localhost:8080/app/search?keyword=aws" 요청.
- 클라이언트로부터 Request 발생.
- Web Server에 요청이 전달되었지만 동적 콘텐츠 요청이므로 요청을 패스.
- 작업 큐에 작업 적재.
- 스레드 풀에 대기 중인 스레드가 작업을 할당.
- 요청하는 서블릿 컨테이너에 접근, 필터링을 수행.
- 필터링 수행 후 "/search"와 매핑된 서블릿이 메모리에 있는지 확인.
- 서블릿이 메모리에 없기 때문에 Class Loader가 서블릿 클래스를 로딩.
- 서블릿 객체 인스턴스화. (서블릿 생명주기 - init)
- 비지니스 로직 처리 (서블릿 생명주기 - service)
- 비지니스 로직 처리 후 결과값을 반환하기 위해 필터링 수행 후 결괏값 반환.
- 작업이 끝난 스레드는 자원 반납.
포스팅에 관련된 질문 혹은 잘못된 내용, 오타 등이 있다면 댓글로 말씀해주시면 감사하겠습니다.
# 본 블로그는 불펌을 허용하지 않습니다.
'서버 > 톰캣(Tomcat)' 카테고리의 다른 글
[Tomcat/error] 톰캣 포트 변경하기 (0) | 2022.01.23 |
---|---|
[Server] 이클립스 인코딩 방식 UTF-8으로 설정하기 (0) | 2022.01.20 |
[Server] 아파치 톰캣(Apache Tomcat) 9 설치하기 (0) | 2022.01.19 |