1. 웹 서버(Web Server), 웹 애플리케이션 서버(WAS - Web Application Server)
브라우저에서 URL을 치면 인터넷을 통해 서버에 접근하고, 서버에서는 HTML을 만들어 클라이언트에 보낸다. 클라이언트와 서버가 요청과 응답을 할 때 모두 HTTP라는 프로토콜을 기반으로 동작한다. 모든 것을 HTTP 메시지에 담아 전송한다는 것이다.
웹 서버(Web Server)
웹 서버(Web Server)는 HTTP를 기반으로 동작하는 서버로, 정적 리소스를 제공하고 기타 부가 기능을 갖고 있다. 정적 리소스란 정적(파일) HTML, CSS, JS, 이미지, 영상 등이 포함되며, 특정 사용자마다 다르게 보여줄 수가 없고 말 그대로 정적인 파일이라고 볼 수 있다. 대표적으로 Nginx와 Apache라는 웹 서버가 있다.
웹 애플리케이션 서버(WAS - Web Application Server)
웹 애플리케이션 서버(Web Application Server)는 HTTP를 기반으로 동작하는 서버로, 웹 서버의 기능(정적 리소스 제공 등)을 대부분 포함한다. 웹 서버와는 다르게 프로그램 코드를 실행해서 애플리케이션 로직을 수행한다. HTTP 요청이 오면 사용자에 따라 다양한 리소스로 응답할 수 있다. 예를 들어 동적 HTML, HTTP API(JSON) 등을 제공할 수 있다. Servlet, JSP, Spring MVC 같은 것들이 WAS에서 동작하게 된다. 대표적으로 Tomcat, Jetty, Undertow 등이 있다.
웹 서버, 웹 애플리케이션 서버(WAS)의 차이
두 용어의 경계가 모호하다고 볼 수 있다. 웹 서버도 프로그램을 실행하는 기능을 포함하기도 하며, WAS도 웹 서버의 기능(정적 리소스)을 제공하기도 하기 때문이다. 자바에서는 보통 Servlet 컨테이너 기능을 제공하면 WAS라고 한다. 그런데 Servlet 없이 자바 코드를 실행하는 서버 프레임워크도 있다.
정리해 보면 WAS는 애플리케이션 코드를 실행하는데 더 특화되어 있다는 것만 생각하면 된다.
웹 시스템 구성 - WEB, WAS, DB
실제 시스템을 구성해야 하는 경우, WAS로 정적 리소스와 애플리케이션 로직 모두 제공할 수 있기 때문에 WAS와 DB만으로 시스템을 구성할 수 있다.
- 그러나 이렇게 시스템을 구성하면 WAS가 너무 많은 역할을 담당해 서버 과부하 위험이 생기거나 가장 비싼 애플리케이션 로직이 상대적으로 값이 싼 정적 리소스 때문에 수행이 어려울 수 있다. 심지어 WAS에 장애가 생기면 오류 화면조차 보여줄 수가 없게 된다.
위에 설명한 문제를 해결하기 위해 WEB, WAS, DB를 모두 사용해 웹 시스템을 구성한다. 보통 웹 서버를 WAS 앞에 두고 정적 리소스를 처리하게 하며, 애플리케이션 로직 같은 동적인 처리가 필요한 경우 웹 서버에서 WAS로 요청을 위임해 해결한다.
- 이런 구성을 사용하면 웹 서버와 WAS의 역할 분담을 통해 WAS는 중요한 애플리케이션 로직 처리에만 전담할 수 있다는 장점이 있다.
- 또, 시스템 리소스를 효율적으로 쓸 수 있다는 장점이 있다. 정적 리소스가 많이 사용되면 웹 서버를 증설하면 되고, 애플리케이션 리소스가 많이 사용되면 WAS를 증설하면 된다.
웹 서버는 단순한 파일 제공의 역할만 주로 하기 때문에 죽을 확률이 낮다. 그러나 WAS는 애플리케이션 로직이 동작하기 때문에 잘 죽는다. 개발자가 로직을 잘못 짜거나 DB가 죽는 등 여러 가지 이유로 죽을 수 있다. 그래서 만약 WAS나 DB에 장애가 발생한다면, 웹 서버에서 오류 화면 HTML을 보여주도록 구성하게 된다.
- 요즘엔 CDN이라는 정적 리소스를 캐시할 수 있는 중간 서버를 놓기도 한다.
- WAS에서 화면을 제공하는 게 아니고 API(데이터)만 제공한다면 WAS만 구축해도 괜찮다.
2. 서블릿(Servlet)
클라이언트가 서버로 요청을 보내려고 하면 웹 브라우저는 요청 HTTP 메시지를 생성해 서버로 전송한다. 이때 개발자가 WAS를 직접 구현해야 한다고 가정한다면, 서버에서 클라이언트가 원하는 데이터를 주기 위해 처리해야 하는 업무는 아래와 같다. 이 모든 과정을 거쳐야 클라이언트에서 서버의 HTTP 응답 메시지를 받을 수 있다.
모든 개발자가 이런 귀찮은 과정을 처리하기엔 무리가 있기 때문에 [의미 있는 비즈니스 로직]을 제외한 모든 일을 지원해 주는 Servlet을 지원하는 WAS를 사용한다.
서블릿(Servlet) - 특징
아래 예시를 살펴보면, urlPatterns(/hello)의 URL이 호출되면 서블릿 코드가 실행된다. https://localhost:8080/hello를 입력하면 service 메서드가 호출되는 것이다. 서블릿(Servlet) 덕분에 개발자는 HTTP 스펙을 편리하게 사용할 수 있다. 따라서 기본적인 HTTP 스펙은 대부분 알고 있는 것이 좋다.
- HttpServletRequest로 HTTP 요청 정보를 편리하게 사용할 수 있다.
- 예를 들어 위에 요청 HTTP 메시지의 데이터로 들어온 username과 age를 request.getParameter() 같은 메서드로 쉽게 얻을 수 있다.
- HttpServletResponse로 HTTP 응답 정보를 편리하게 제공할 수 있다.
- response에 write를 할 때, HTTP 스펙에 어떻게 나가는 지를 대략적으로 알고 있어야 한다.
@WebServlet(name = "helloServlet", urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) {
// 애플리케이션 로직
}
}
서블릿(Servlet) - HTTP 요청, 응답 흐름
아래 그림을 보면서 HTTP 요청과 응답 흐름을 살펴볼 수 있다.
- 우선, HTTP 요청 시 WAS는 Request, Response 객체를 새로 만들어서 서블릿 객체를 호출한다. 그러면 서블릿 컨테이너에 서블릿 객체가 올라가게 되고, 만들어둔 Request와 Response 객체를 파라미터로 넘겨 실행하게 된다.
- 개발자는 애플리케이션 로직에 따라 Request 객체에서 HTTP 요청 정보를 편리하게 꺼내서 사용하고, Response 객체에 HTTP 응답 정보를 편리하게 입력한다.
- 이후 서블릿 객체가 종료되면 WAS는 Response 객체에 담겨있는 내용으로 HTTP 응답 정보를 생성해 웹 브라우저에 반환한다.
서블릿(Servlet) 컨테이너
톰캣(Tomcat)처럼 서블릿을 지원하는 WAS를 서블릿 컨테이너라고 한다. 서블릿 컨테이너는 서블릿 객체를 생성하고 초기화하며, 호출, 종료하는 생명주기를 관리한다.
고객의 요청이 올 때마다 계속해서 객체를 생성하는 것은 비효율적이기 때문에 서블릿 객체는 싱글톤으로 관리된다.
- 최초 로딩 시점에 서블릿 객체를 미리 만들어두고 재활용할 수 있고, 모든 고객 요청은 동일한 서블릿 객체 인스턴스에 접근하게 된다.
- 컨테이너에 하나만 존재하기 때문에 공유 변수 사용에 주의해야 한다. 정보가 덮어 씌워지는 등의 문제가 발생할 수 있다.
- 서블릿 컨테이너 종료 시 서블릿 객체도 모두 함께 종료된다.
싱글톤에 대한 추가 설명은 아래 링크를 참고하면 좋다.
이외에도 JSP도 서블릿으로 변환되어 사용되기도 하고, 서블릿은 동시 요청을 위한 멀티 스레드 처리를 지원한다는 장점이 있다.
- 멀티 스레드 덕분에 고객의 요청이 100, 1000건이 들어오든 동시에 처리할 수 있게 된다.
3. 동시 요청 - 멀티 쓰레드
백엔드 개발자에게 가장 중요한 CS 개념이라고 할 수 있다. 트래픽이 몰리는 상황 등을 해결하기 위해 알아야 할 필수 개념이다.
쓰레드(Thread)
WAS에 요청이 들어오면 WAS에서 TCP 연결을 한 뒤 서블릿 객체를 호출하고 애플리케이션 로직을 실행해 처리한 후 응답한다. 이때 쓰레드(Thread)가 서블릿 객체를 호출한다.
쓰레드는 한 번에 하나의 코드 라인만 수행할 수 있어서 애플리케이션 코드를 하나하나 순차적으로 실행한다. 예를 들어 자바 메인 메서드를 처음 실행하면 main이라는 이름의 쓰레드가 실행된다. 쓰레드가 없다면 자바 애플리케이션을 실행할 수 없게 된다. 여러 요청이 한 번에 들어와서 동시 처리가 필요하다면 쓰레드를 추가로 생성하면 된다.
예를 들어 쓰레드가 하나뿐인 상황에서 쓰레드 하나만 사용하는 단일 요청이 들어온다면, 쉬고 있는 쓰레드를 서블릿 객체를 호출하는 데에 할당하면 된다.
그러나 쓰레드가 하나뿐인 상황에서 다중 요청이 들어온다면, 쓰레드가 이미 요청을 처리하고 있는데 또 다른 요청이 들어오면 모든 처리가 지연될 수 있다는 문제가 생긴다. 이런 문제를 해결하기 위해 WAS에선 두 가지 방법을 사용한다.
다중 요청 - 요청할 때마다 쓰레드 생성하기
다중 요청에 대비하기 위해 요청이 들어올 때마다 쓰레드를 생성하는 방법이 있다. 간단하고 최고의 방법인 것 같지만 당연히 장단점이 존재한다.
장점
- 만든 쓰레드 수만큼 동시 요청을 처리할 수 있다.
- 리소스(CPU, 메모리 등)가 허용할 때까지 모든 요청을 처리할 수 있다. (Resource Utilization ↑)
- 하나의 쓰레드가 지연돼도 나머지 쓰레드는 상관없이 정상적으로 작동한다.
단점
- 쓰레드 자체의 생성 비용이 매우 비싸다.
- 고객의 요청이 올 때마다 쓰레드를 생성하면 응답 속도가 늦어진다.
- 쓰레드는 컨텍스트 스위칭(Context Switching) 비용이 발생한다.
- CPU가 여러 일을 동시에 처리하는 것처럼 보이지만 실제로는 아주 짧은 시간마다 할 일을 바꿔가며 처리하고 있다. 이때 CPU가 할 일을 바꾸는 것을 Context Switching라고 한다. 한 쓰레드가 하나의 일을 맡아 처리하기 때문에 쓰레드를 많이 생성하면 Context Switching도 자주 발생하고 비용도 증가하게 된다.
- 쓰레드 생성 수에 제약이 없기 때문에 고객의 요청이 너무 많이 발생하게 되면 CPU나 메모리 임계점을 넘어 서버가 죽을 수도 있다.
다중 요청 - 쓰레드 풀
요청할 때마다 쓰레드를 생성하는 방식은 비용이 많이 들기 때문에 주로 쓰레드 풀을 이용한다.
쓰레드를 필요한만큼 미리 생성해서 쓰레드 풀에 보관하고 관리하는 방법이다. 쓰레드 풀에 생성 가능한 쓰레드의 최대치 또한 관리하며, 톰캣은 최대 200개의 쓰레드를 기본으로 설정해 둔다.
쓰레드가 필요하면 이미 생성되어 있는 쓰레드를 쓰레드 풀에서 꺼내 사용하고, 사용이 끝나면 쓰레드 풀에 해당 쓰레드를 반납하면 된다. 만약 최대 쓰레드가 모두 사용 중이어서 쓰레드 풀에 남은 쓰레드가 없다면, 추가적으로 들어오는 요청은 거절하거나 특정 숫자만큼 대기하도록 설정할 수 있다.
장점
- 쓰레드가 미리 생성돼 있으므로, 쓰레드를 생성하고 종료하는 비용이 절약되고 응답 시간이 빠르다.
- 생성 가능한 쓰레드 수에 최대치를 정해놓으므로, 너무 많은 요청이 들어와도 기존 요청은 안전하게 처리할 수 있다.
실무 팁
- WAS의 주요 튜닝 포인트는 최대 쓰레드(Max Thread) 수이다.
- 이 값을 너무 낮게 설정하면 동시 요청이 많은 경우,
- 서버 리소스는 여유롭지만 클라이언트는 금방 응답이 지연되고 CPU Utilization이 극히 떨어질 수 있다.
- 이 값을 너무 높게 설정하면 동시 요청이 많은 경우,
- CPU나 메모리 리소스 임계점을 초과해 서버가 다운될 수 있다.
- 이 값을 너무 낮게 설정하면 동시 요청이 많은 경우,
- 장애 발생 시 클라우드라면 일단 서버부터 늘린 뒤 이후에 튜닝을 하고, 클라우드가 아니라면 열심히 튜닝해야 한다.
쓰레드 풀의 쓰레드 최대치 적정 숫자는 애플리케이션 로직의 복잡도, CPU, 메모리, IO 리소스 상황에 따라 모두 다르기 때문에 개발 후 성능 테스트를 통해 직접 알아봐야 한다. 이때, 최대한 실제 서비스와 유사하게 성능 테스트를 시도해야 하며, 자주 쓰는 성능 테스트 툴로는 Apache ab, Jmeter, nGrinder 등이 있다.
WAS의 멀티 쓰레드 지원
정리하자면, 멀티 쓰레드에 대한 부분은 WAS가 처리하기 때문에 개발자는 멀티 쓰레드와 관련된 코드를 직접 신경쓰지 않아도 된다. 따라서 개발자는 그냥 싱글 쓰레드 프로그래밍을 하듯이 편리하게 소스 코드를 개발하면 된다. 대신 멀티 쓰레드 환경이므로 싱글톤 객체(서블릿, 스프링 빈)는 주의해서 사용해야 한다는 것을 잊지 말아야 한다.
4. HTML, HTTP API, CSR, SSR
정적 리소스
정적 리소스를 제공하는 경우 보통 고정된 HTML 파일이나 CSS, JS, 이미지, 영상 등을 제공한다. 예를 들어 /hello.html을 요청하면 웹 서버에서 이미 생성된 리소스 파일을 웹 브라우저에게 제공하게 된다.
동적으로 제공되는 HTML 페이지
HTML 페이지를 제공하는 경우 동적으로 필요한 HTML 파일을 생성해서 전달한다. /orders.html을 요청하면 WAS에서 DB에서 주문 정보를 조회하고 동적으로 HTML을 생성한다. 이때 JSP나 타임리프를 통해 프로그램 코드를 넣어 HTML 코드를 동적으로 생성하고 웹 브라우저에게 제공한다. 이후 웹 브라우저는 받은 HTML을 해석(렌더링)해서 클라이언트에게 보여준다.
HTTP API
HTTP API를 제공하는 경우 단순하게 HTML을 전달하는 게 아니라 데이터를 전달한다. 데이터 자체를 전달하기 때문에 웹 브라우저가 HTML을 렌더링할 필요가 없다. 주로 JSON 형식을 사용하며 다양한 시스템에서 호출하는 경우가 많다.
HTTP API는 주로 3가지 상황에서 사용된다. 우선 데이터만 주고받기 때문에 UI 화면이 필요하면 데이터를 받은 클라이언트에서 별도로 처리해야 한다. 앱/웹 개발자와 협업을 하는 경우 UI는 이미 구현돼 있고 서버에 데이터만 요청하는 경우가 많다. WAS에선 요청이 들어온 데이터를 제공하기만 하면 된다. 또는 서버끼리 통신을 해야 되는 경우에도 데이터만 주고받으면 되기 때문에 HTTP API를 사용한다.
- UI 클라이언트 접점
- 앱 클라이언트(iOS, Android, PC 앱)
- 웹 브라우저에서 JS를 통한 HTTP API 호출
- React, Vue.js 같은 웹 클라이언트
- 서버 to 서버
- 주문 서버 → 결제 서버
- 기업 간 데이터 통신
CSR(Client Side Rendering)
HTML 결과를 JS를 사용해 웹 브라우저에서 동적으로 생성해 적용한다. 주로 동적인 화면에 사용하며, 웹 환경을 마치 앱처럼 필요한 부분 부분만 변경할 수 있다. 예시로는 구글 지도나 Gmail, 구글 캘린더 등이 있다. 웹 프론트엔드 개발자가 React나 Vue.js를 CSR 기술로 사용한다.
- React, Vue.js를 사용하더라 CSR과 SSR을 동시에 지원하는 웹 프레임워크도 있다.
SSR(Server Side Rendering)
서버에서 최종 HTML을 생성해서 클라이언트에 전달하고, 웹 브라우저는 전달받은 HTML을 보여주기만 한다. 주로 정적인 화면에 사용하며 백엔드 개발자가 JSP나 타임리프 등을 SSR 기술로 사용한다.
- SSR을 사용하더라도 JS를 사용해 화면 일부를 동적으로 변경하는 것도 가능하다.
백엔드 개발자 입장에서 UI 기술의 어디까지 알아야 할까?
백엔드 개발자는 SSR 기술 학습이 필수라고 생각하면 된다. 간단한 admin 화면 정도는 JSP나 타임리프를 통해 만들어 둘 수 있기 때문에 미리 학습해 두면 프로젝트를 진행할 때 도움이 될 수 있다. React나 Vue.js 등 웹 프론트엔드 기술 학습은 옵션으로 봐도 된다. 둘 다 깊이 있게 잘하기엔 오랜 시간이 필요하기 때문에 우선은 서버, DB, 인프라 등 수많은 백엔드 기술에 집중하는 게 좋다. 백엔드도 웹 프론트엔드가 어떻게 돌아가는지 알아야 하기 때문에 이런 관점에서 미리 공부해 두는 것도 나쁘지 않다.
5. 자바 백엔드 웹 기술 역사
과거 기술
서블릿 - 1997
- Java 코드로 짜야하기 때문에 동적으로 HTML 생성하기가 어려웠다.
JSP - 1999
- HTML 생성은 편리하지만, 비즈니스 로직까지 너무 많은 역할을 담당하게 되기 때문에 유지보수가 어려웠다.
서블릿 + JSP = MVC 패턴 사용
- Model, View, Controller로 역할을 나누어 개발하는 것이다. 비즈니스 로직과 화면 렌더링 부분으로 관심사를 분리하기 때문에 유지 보수가 쉬워졌다.
다양한 MVC 프레임워크 등장 - 2000년 초 ~ 2010년 초
- MVC 패턴이 자동화가 되면서 복잡한 웹 기술을 편리하게 사용할 수 있는 다양한 기능을 지원했다.
- 예시: 스트럿츠(Struts), 웹워크, 스프링 MVC(과거 버전) → 스트럿츠와 스프링 MVC를 조합해 사용했다.
현재 사용 기술
어노테이션 기반의 스프링 MVC 등장
- @Controller 등 →MVC 프레임워크 등장 마무리
- 스프링 프레임워크가 제공하는 MVC이기 때문에 통합에 대한 고민도 없애주고, 어노테이션 기반이기 때문에 깔끔하고 유연하게 코드를 작성할 수 있다는 게 장점이었다.
스프링 부트의 등장
- 개발자들이 불편해하던 것들(복잡한 설정, 서버 설치 등)을 스프링 부트가 대부분 자동화(서버 내장 등)했다.
- 과거에는 서버에 직접 WAS를 설치하고 소스는 War 파일을 만들어서 설치한 WAS에 배포해야 했지만, 스프링 부트는 빌드 결과(Jar)를 WAS 서버에 포함시켜 빌드 배포가 굉장히 단순해졌다.
최신 기술 - 스프링 웹 기술의 분화
Web Servlet - Spring MVC
- 서블릿 위에 Spring MVC를 올려서 동작한다. 앞에서 설명한 HttpSurvletRequest나 HttpServletResponse, 멀티 쓰레드 등의 기술을 다 사용할 수 있다.
Web Reactive - Spring WebFlux
- 특징
- 완전한 비동기 non-blocking 처리를 하며, 최소 쓰레드로 최대 성능을 낼 수 있다. (쓰레드 Context Switching 비용이 효율화됐다.)
- 예를 들어 CPU 코어가 4개가 있다고 하면, 보통 쓰레드 개수를 CPU 코어 개수에 맞추기 때문에 Context Switching 비용이 거의 들지 않게 된다.
- 함수형 스타일로 개발하기 때문에 동시 처리 코드가 효율화된다.
- 서블릿 기술을 사용하지 않는다.
- 완전한 비동기 non-blocking 처리를 하며, 최소 쓰레드로 최대 성능을 낼 수 있다. (쓰레드 Context Switching 비용이 효율화됐다.)
- 아직 실무에선 많이 사용하지 않는 이유
- 웹 플럭스는 기술적 난이도가 매우 높고, 아직은 RDB 지원이 부족해 함께 쓰기가 어려워 Redis나 Elastic search, Dynamodb 등 다른 기술을 사용해야 한다.
- 일반 MVC의 쓰레드 모델도 충분히 빠르기 때문에 동시에 복잡한 API를 호출해야 되고 성능이 매우 중요한 경우가 아니라면 실무에서 아직까진 사용하기 어렵다.
자바 뷰 템플릿 역사 - HTML을 동적으로 편리하게 생성하는 뷰 기능
JSP
- 속도가 느리고, 기능이 부족하다는 단점이 있다.
프리마커(Freemarker), 벨로시티(Velocity)
- 속도 문제를 해결했고, 다양한 기능을 제공한다.
타임리프(Thymeleaf)
- 내추럴 템플릿: HTML의 모양을 유지하면서 뷰 템플릿 기능을 적용할 수 있다.
- 스프링 MVC와 강력한 기능 통합을 제공한다.
- 최선의 선택이라고 볼 수 있지만, 성능은 프리마커나 벨로시티가 더 빠르다.