티스토리 뷰

웹 서버(Web Server), 웹 어플리케이션 서버(WAS)

네트워크 환경에서 모든 것은 Http 프로토콜 기반으로 데이터(메시지)를 전달하게 된다. 서버 간에 데이터를 주고받을 때도 대부분 Http를 사용을 하는데 웹 서버들이 어떤 방식으로 Http를 사용하는지 알아보고자 한다.

 

Web Server

Http 기반으로 동작하며 정적 리소스를 제공한다.

정적 리소스란 Html, Css, Js, 이미지 등과 같이 정적인 파일을 제공하는 역할을 하며 NGINX, APACHE등과 같은 웹 서버들이 있다.

Web Application Server

흔히 WAS로 축약해서 말하는 웹 어플리케이션 서버이다.

웹서버와 마찬가지로 Http 기반으로 동작을 하지만 프로그램 코드를 실행해서 어플리케이션 로직을 수행한다는 차이점이 있다. 즉, 단순히 정적인 파일만 옮겨주는 웹서버와는 달리 우리가 자바코드를 만들어서 비즈니스 로직을 만들어 사용하는 것을 WAS에서 수행한다는 것이다.

 

둘의 차이점

웹 서버는 정적 리소스, WAS는 어플리케이션 로직

하지만 웹 서버도 프로그램을 실행하는 기능을 포함하기도 하고, WAS도 웹서버의 기능을 제공하기 때문에 구분짓기에는 모호한 감이 있다. WAS는 결국 어플리케이션 로직을 실행하는데 더 특화되어 사용하는 서버로 자바의 경우 서블릿 컨테이너의 기능을 제공하면 대부분 WAS라 칭한다.

 

웹 시스템 구성

그럼 WAS가 웹서버의 기능을 제공해주니까 정적 리소스든 어플리케이션 로직이든 전부 WAS에 띄우면 되겠네? 라고 생각하면 오산이다.


지금처럼 WAS에 정적 리소스와 어플리케이션을 전부 담아서 기능을 하게되면 WAS가 너무 많은 역할을 담당하기 때문에 서버 과부하가 걸리고 만약 서버 오류가 났을 때 정적 리소스마저 전송을 못하기때문에 오류페이지도 띄우지 못하는 경우가 발생한다. 그리고 어플리케이션 로직은 가장 중요하고 가장 비싼 자원이기 때문에 정적 리소스가 이를 방해해서도 안된다.

따라서 역할에 따라 기능을 분할하는 것이 가장 이상적인 웹 시스템이므로 다음과 같이 웹 서버가 정적 리소스를 제공하고 WAS가 어플리케이션 로직을 담당하는 이상적인 웹 시스템을 구성할 수있다.

역할별로 분할했을 때 어플리케이션 로직을 방해하지도 않고, 어플리케이션 로직이 오류가 났더라고 하더라도 정적 리소스는 웹 서버를 통해 전송되기 때문에 오류페이지도 띄울 수 있다. 그리고 중요한 것이 효율적으로 서버를 증성할 수 있다.


둘의 역할을 분할해두면 정적 리소스가 많이 사용되면 웹서버만, 어플리케이션 리소스가 많이 사용되면 WAS만 따로 증설을 하여서 좀 더 효율적인 서버 증설이 가능해진다.

 

서블릿(Servlet)

앞서 WAS는 Http를 기반으로 동작한다고 했었다. 그럼 구체적으로 WAS가 어떤 식으로 돌아가는지 클라이언트가 Post를 전송해서 저장해야한다고 가정하고 직접 WAS 내부 동작을 보자.


Http 요청 하나를 위해서 WAS가 동작해야할 것들이 너무 많다. 비즈니스 로직이 의미가 있고 중요한데 나머지까지 짜기에는 시간이 오래 걸려보인다. 그래서 개발자가 비즈니스 로직만 짤 수 있도록 도움을 주는 기술이 Servlet이다.

  • 클라이언트의 요청을 받고 내부 클래스에서 요청을 동작하여 결과를 반환한다.
  • 흔히 알고있는 MVC 패턴에서 Controller에 해당한다.
  • HttpServletRequest 객체를 이용하여 Http 요청 정보를 쉽게 알 수 있다.
  • HttpServletResponse 객체를 이용하여 Http 응답 정보를 쉽게 보낼 수 있다.

즉 비즈니스 로직을 제외한 일련이 과정들을 Servlet을 통해서 도움을 받아 개발자는 비즈니스 로직에만 집중할 수 있게 되는 것이다.

동작 구조를 알아보면,


  1. Http Request가 WAS로 들어온다.
  2. WAS가 Http Request를 기반으로 Request와 Response 객체를 생성하여 서블릿 객체(helloServlet)를 호출한다.
  3. 이때 개발자는 Request 객체에서 Http Request의 요약된 요청 정보를 꺼내어 사용한다.
  4. 그 후 결과물을 Response객체에 담는다.
  5. WAS는 Response 객체에 담긴 내용으로 클라이언트를 통해 Http Response를 전송한다.
  6. 응답이 끝나면 Request와 Response 객체를 소멸시킨다.

 

서블릿을 통해서 개발자가 비즈니스 로직만 걱정하면 된다는 것을 알았는데 그럼 서블릿 컨테이너는 뭘까?

지금까지 서블릿을 사용해서 Http 응답 정보를 처리하는 WAS들을 전부 서블릿 컨테이너라고 한다. 흔히 사용하는 Tomcat이 서블릿 컨테이너인 셈이다. 이런 서블릿 컨테이너들이 앞서 동작구조에서 보았다시피 서플릿 객체를 생성하고, 초기화, 호출, 종료하는 등 서블릿의 생명주기를 관리하는데 이때 중요한 사실이 있다.

서블릿 객체는 싱글톤으로 관리한다.

일단 우리가 하나의 웹서비스를 만들때 클라이언트로부터 무수히 많은 요청을 받게될 것이고 이때마다 계속 객체를 만들어서 생성하는 것은 매우 비효율적이다. 그래서 최초의 로딩 시점에 우리는 서블릿 객체를 만들어두고 재활용하는 방식으로 즉, 싱글톤으로 관리한다. 객체를 하나만 만들어 놓고 생성된 객체를 어디서든 참조할 수 있도록 만들어 두었다는 것이다. 따라서 모든 고객의 요청은 동일한 서블릿 객체 인스턴스에 접근을 하게 된다.

사용이 끝나도 객체를 지우지 않고 계속 재활용해서 사용하며 서블릿 컨테이너가 종료되면 그때 같이 종료를 한다.

그럼 여기서 의문사항이 생긴다. 하나의 객체만 두고 사용하면 여러 요청이 왔을 때 어떻게 처리하나요? 는 멀티 쓰레드를 지원하여 처리한다.

 

동시 요청 - 멀티 쓰레드

멀티 쓰레드를 알기 앞서 쓰레드에 대해서 알아보자면

실행 중인 프로그램 내에서 실제로 작업을 수행하는 주체

(= 어플리케이션 로직을 하나하나 순차적으로 진행하는 것)

자바에서 메인 메소드를 실행하면 main이라는 이름의 쓰레드가 실행이 되고 이 쓰레드는 한번에 하나의 코드 라인만 수행할 수 있다. 하지만 단일쓰레드, 즉 쓰레드 하나로 시스템을 구성하면 다음과 같은 문제가 발생한다.


만약 요청1이 먼저와서 쓰레드가 sevlet 객체를 호출받아 처리 중인데 처리지연이 생겼다고 하자. 근데 알다시피 많은 서비스들은 동시접속자가 많아 지연되는 동안 요청2가 생기기마련이다. 따라서 요청2는 아무것도해보지도 못하고 대기를 하게 되는 상황이 발생한다.

이때 우리는 요청마다 쓰레드를 새로 생성하여 다른 요청이 들어와도 그때그때 쓰레드를 부여하는 방식의 해결방법을 생각해낸다.

말한 것처럼 동시 요청이 들어와도 처리할 수 있고, CPU, 메모리가 허용되는 한에서는 계속해서 생성하여 처리할 수 있다. 하지만 단점이 있기마련이다.

  • 쓰레드의 생성 비용은 비싸다 > 요청 때마다 생성하면 응답속도가 느려진다.
  • 쓰레드는 컨텍스트 스위칭 비용이 발생한다.
  • 쓰레드 생성에 제한이 없으면 요청 수 만큼 계속 생성되고 메모리 허용범위가 넘어서면 서버가 죽는다.

여기서 컨텍스트 스위칭 비용이란 CPU에서의 코어만큼 쓰레드가 돌아가는데 코어 한개를 두고 보았을 때 쓰레드가 두 개면 코어 하나가 둘을 순차적으로 수행을하고 이때 다음 쓰레드로 옮길때의 비용을 말한다.

 

쓰레드 풀

그래서 우리는 쓰레드를 미리 만들어서 모아둔 쓰레드 풀을 이용한다. 이전에는 쓰레드를 요청때마다 생성하고, 사용한 후 쓰레드를 죽이는 방식을 사용했기 때문에 위와 같은 문제점들이 발생했었다. 따라서 쓰레드 풀이라는 곳에 쓰레드를 모아놓고 쓰레드 풀에서 쓰레드를 받아 사용하고 반환하는 방식으로 사용하게 된다.


이처럼 쓰레드 풀을 사용하였을 때 장점은 쓰레드 생성에 제한을 걸어두었다는 것이다. 200개의 쓰레드를 모두 사용한 시점이라면 새로 들어오는 요청일 지라도 대기를 시키거나, 연결자체를 끊어 연속되는 요청에 대해서 보완할 수 있는 것이다. 따라서 미리 쓰레드가 서버 실행과 함께 생성이 되기 때문에 새로 생성하거나 종료하는 비용이 절약되어 응답 시간이 빠르고, 기존 요청에 대해서 안전하게 처리할 수 있다.

우리가 주로 사용하는 Tomcat은 쓰레드 풀의 크기를 최대 200개로 default해두었고 물론 변경이 가능하다.

server.tomcat.max-threads = 0 # number of threads in protocol handler

결국 WAS에서의 주요 튜닝 포인트는 이 기본 default로 되어있는 최대 쓰레드 개수를 어떻게 조정하는 가이다.

쓰레드 수를 낮게 설정하면 동시 요청이 많을 때 서버 리소스는 여유롭지만 응답 지연이 발생하고, 너무 높으면 응답은 빠르지만 CPU, 메모리 초과로 서버가 다운될 수 있다.

뭐든 "적절하게" 어플리케이션 로직의 복잡도, CPU, 메모리, IO 리소스 상황에 맞추어서 실제 서비스와 유사하게 성능테스트를 진행하여 쓰레드 개수를 조정해야한다.(툴 : 아파치 ab, 제이미터, nGinder)

 

WAS에서의 멀티 쓰레드 지원의 핵심은 결국 WAS가 알아서 멀티 쓰레드를 처리해주고, 개발자는 멀티 쓰레드 관련된 코드에 신경쓰지 않아도 된다는 것이다. 즉, 하나의 요청에 대한 쓰레드(싱글 쓰레드) 프로그래밍하듯이 편하게 소스 코드를 개발하면된다.

단, 싱글톤 객체(서블릿, 빈)은 주의해서 사용하자.

 

HTML, HTTP API

우리는 정적 리소스로서 고정된 HTML 파일, CSS, Js, 이미지, 영상 등을 웹 브라우저를 통해 제공한다. 이때 WAS가 끼면?


데이터 베이스를 사용하는 등 어플리케이션 로직으로 얻을 수 없이 정적인 화면만 제공하던 웹 브라우저에 WAS가 어플리케이션 로직을 통해 동적인 HTML 결과물을 보여줄 수 있다.

물론, 게임과 같은 사이트에서 많이 볼 수 있듯이 JSON의 형태로 API만을 제공해줄 수 있기도 한다.


이런 식으로 HTTP API를 이용하면 활용폭이 넓어진다. 가령 스마트폰 앱을 생각해보면 웹페이지를 굳이 만들지 않아도 스마트폰의 앱에서 데이터를 받아서 화면에 띄워주는 등과 같이 데이터만 주고받는 역할로 WAS가 사용될 수도 있고 WAS 서버끼리도 서로서로 통신시킬 수 있다. (결제 서버와 주문 서버를 따로 두는 것처럼) JSON이라는 통일된 데이터 포멧을 통해 서버와 클라이언트, 서버와 서버와의 통신을 진행할 수 있게 된다.

 

SSR, CSR

주로 프론트 개발을 할 때 들을 수 있는 용어들이다.

 

SSR, 서버 사이드 렌더링

서버에서 만들어서 렌더링하는 것이다. 프론트에서는 요청만하면 서버에서는 HTML에 모든 결과를 담아 페이지를 브라우저에 뿌리기만 하면 끝이다. 정적인 화면에는 대부분 SSR을 사용하며 흔히 들어봤을 법한 JSP, Thymeleaf가 이에 해당한다.

 

CSR, 클라이언트 사이드 렌더링

서버 사이드 렌더링의 경우 클라이언트가 결과를 받고 그 페이지에서 무언가 동작을 하려면 새로운 페이지를 또 한번 요청해야한다. 이때, 계속 페이지를 요청받아서 정적인 페이지를 새로고침하는 것이 아니라 JS를 사용해서 웹 브라우저를 동적으로 사용하는것이 CSR이다.

구글 지도를 봐보자. 구글 위성 지도에서 확대를 하면 할 수록 세부적인 것들이 계속 입력된다. 근데 주소는 바뀌지않는다. 즉, HTML 페이지는 처음에 로드받고 웹에서 필요한 데이터들을 JS를 통해 정적으로 계속 받아내는 과정을 거친다는 것이다.

 

결국 두 렌더링 모두 각각의 장단점이 있다. SSR은 지속적인 요청에 대해서 잦은 응답을 하게 될 경우 서버에 부담이 생길 수 있고, CSR은 정적으로 사용하여야하기 때문에 관련된 JS를 모두 받아와야하기 때문에 초기 로딩이 SSR 대비해서 느리다.

활용방안에 대해서 각각의 차이점이 있지만 백엔드 개발자로서는 정적인 페이지를(ex 어드민 페이지) 관리해야할 때도 많고 프론트간의 통신에 대해서도 어느정도 알아야 한다고 생각하기 때문에 SSR까지는 필수적인 기술스택이라고 생각든다.

 

 

* 김영한님의 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술을 듣고 기록한 학습 자료입니다.

댓글
최근에 올라온 글
Total
Today
Yesterday