현대 애플리케이션은 대부분 웹 환경에서 동작하는 웹 애플리케이션이다. 그리고 스프링 MVC라는 복잡한 웹 애플리케이션을 쉽고 빠르게 개발할 수 있도록 도와주는 Java 웹 프레임워크가 있다.
Java 백엔드 개발자는 이러한 웹 애플리케이션을 개발할 때 대부분 스프링 MVC를 사용한다.
백엔드 웹 기술을 학습하기 어려운 이유
Java 백엔드 웹 기술은 매우 방대하고 공부할 분량도 무척 많다. 웹 기술을 학습하기 어려운 이유는 대략 3가지 때문이다.
- HTTP
- 모든 웹 기술은 HTTP를 기반으로 하고 있기 때문에, HTTP 기반 지식이 있어야 한다.
- 오래된 Java 백엔드 웹 개발
- 오랜 시간 동안 불편한 점들을 개선하고 발전하면서 너무 많은 것들이 자동화 및 추상화되었다.
- 따라서 과거의 어떤 문제 때문에 지금 이런 방식으로 개선되고 사용되는지, 과거의 문맥을 제대로 이해하고 사용하기가 어렵다.
- ‘왜?’가 없이 단순히 사용법 위주로만 사용을 하게 된다.
- 많은 기능을 제공하는 스프링 MVC
- 스프링 MVC는 실무 백엔드 개발에 필요한 모든 기능을 거의 다 제공한다.
- 즉, 그만큼 방대하고 학습할 내용도 많다.
하지만 Spring MVC Framework의 기본 구조는 굉장히 탄탄하게 설계되어 있어서, 지금까지 수많은 기능이 추가되어도 그 기본 구조는 변하지 않았다.
따라서 스프링 MVC의 기본 구조를 확실하게 이해하는 것이 중요하다.
웹 서버와 웹 애플리케이션 서버
웹은 HTTP 프로토콜을 기반으로 통신을 한다. 다음과 같이 거의 모든 형태의 데이터 전송이 가능하다.
- HTML, TEXT
- 이미지, 음성, 영상, 파일
- JSON, XML (API의 경우)
웹 서버란
- 웹 서버란 HTTP를 기반으로 동작하는 서버이다.
- 정적 리소스와 기타 부가 기능들을 제공한다.
- 정적 리소스란, 특정 디렉토리에 있는 파일들을 그대로 전달해주는 것을 말한다.
- 정적 HTML, CSS, JS, 이미지, 영상 등
- 대표적으로 NGINX, APACHE 등이 있다.
웹 애플리케이션 서버
- Web Application Server는 보통 WAS라고 부른다.
- 웹 서버와 마찬가지로 HTTP를 기반으로 동작하며, 웹 서버의 기능들을 대부분 포함하고 있다.
- 프로그램 코드를 실행해서 애플리케이션 로직을 수행할 수 있다.
- 사용자에 따라서 사용자의 이름을 보여주거나 다른 화면을 보여줄 수 있다.
- 즉, 동적인 HTML을 생성하거나 HTTP API(REST API)를 제공한다.
- Servlet, JSP, Spring MVC 등이 WAS에서 동작한다.
- 대표적으로 Tomcat, Jetty, Undertow 등이 있다.
웹 서버 VS 웹 애플리케이션 서버
- 단순하게는 웹 서버는 정적 리소스를 제공하는 것, WAS는 애플리케이션 로직까지 실행할 수 있는 것으로 이해하면 된다.
- 사실, 둘의 용어도 경계도 모호하다.
- 웹 서버도 프로그램을 실행하는 기능을 포함하기도 한다.
- 웹 애플리케이션 서버도 웹 서버의 기능을 제공한다.
- 자바는 서블릿 컨테이너 기능을 제공하면 WAS라고 한다.
- 하지만 서블릿 없이 자바 코드를 실행하는 서버 프레임워크도 있으므로 이 구분법도 애매하다.
- WAS는 애플리케이션 코드를 실행하는데 더 특화되어 있다고 보는 것이 좋다.
웹 시스템 구성
WAS만 사용할 경우
- WAS와 DB만을 사용하여 최소한의 시스템 구성이 가능하다.
- WAS는 정적 리소스도 제공할 수 있고, 애플리케이션 로직도 제공이 가능하기 때문이다.
단점
- WAS가 너무 많은 역할을 담당하고 있다.
- 가장 비싼 애플리케이션 로직이 정적 리소스 때문에 수행이 어려울 수 있다.
- 정적 리소스를 제공하는 것은 있는 파일을 보내주면 되지만, 애플리케이션 로직은 비즈니스 로직이 들어가기 때문에 훨씬 비싸다.
- WAS에 장애가 발생하면 웹 접속 자체가 안된다.
일반적인 구성
- 웹 서버와 WAS, DB를 모두 둔다.
- 웹 서버가 정적 리소스를 처리하고, 애플리케이션 로직과 같은 동적인 처리는 WAS에 요청을 위임한다.
장점
- WAS는 중요한 애플리케이션 로직 처리를 전담할 수 있다.
- 효율적으로 리소스를 관리할 수 있다.
- 정적 리소스가 많이 사용되면 웹 서버를 증설한다.
- 애플리케이션 리소스가 많이 사용되면 WAS를 증설한다.
참고
- CDN이라는 정적 리소스를 캐시할 수 있는 중간 서버를 놓기도 한다.
- API로 데이터만 제공하는 API 서버일 경우, 굳이 웹 서버를 두지 않아도 괜찮다.
서블릿
회원가입을 하도록 HTTP 요청을 한다고 가정하자. 그렇다면 웹 애플리케이션 서버에서는 아래 항목을 직접 구현해야 한다.
- 서버 TCP/IP 연결 대기
- 소켓 연결
- HTTP 요청 메시지를 파싱해서 읽기
- HTTP 메소드 방식과 URL 확인
- Content-Type 확인
- 데이터를 사용할 수 있도록 HTTP 메시지 바디의 내용을 파싱
- 저장 프로세스 실행
- 비즈니스 로직 실행
- HTTP 응답 메시지 생성 시작
- HTTP 시작 라인 생성
- Header 생성
- 메시지 바디에 HTML 생성해서 입력
- TCP/IP에 응답 전달
- 소켓 종료
이 과정에서 실질적으로 비즈니스 로직은 5번 과정 뿐이다. 그런데 그러기 위해서 비즈니스 로직 실행 전후 단계가 너무 많다.
매번 이렇게 개발하고 있다면 비효율적이므로, 등장한 것이 Servlet이다.
Servlet
- 비즈니스 로직(위의 5번 과정)을 제외한 모든 과정을 지원해준다.
- 서블릿을 지원하는 WAS들이 이 기능들을 해준다.
특징
클라이언트에서 서버로 요청이 왔을 때, 서블릿 코드에 있는 service 메소드가 실행된다.
@WebServlet(name = "helloServlet", urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest reqeust, HttpServletResponse response) {
}
}
urlPatterns
의 URL이 호출되면 서블릿 코드가 실행된다.
- HTTP 요청 정보를 편리하게 사용할 수 있는 HttpServletRequest 객체
- HTTP 응답 정보를 편리하게 제공할 수 있는 HttpServletResponse 객체
- 이처럼 개발자는 서블릿을 통해 HTTP 스펙을 매우 편리하게 사용할 수 있다.
동작 과정
- 웹 브라우저에서
localhost:8080/hello
로 요청을 한다.
- WAS 서버에서는 HTTP 요청 메시지를 기반으로 HttpServletRequest 객체와 HttpServletResponse 객체를 생성한다.
- 서블릿 컨테이너는 이렇게 생성한 두 객체를 helloServlet에 파라미터로 넘겨 실행한다.
- 실행이 끝나고 리턴되면, WAS는 HttpServletResponse 객체를 기반으로 HTTP 응답 메시지를 만든다.
- 웹 브라우저에 응답 메시지를 전달한다.
즉, HTTP 요청 시 다음과 같이 흘러간다.
- WAS는 Request, Response 객체를 새로 만들어서 서블릿 객체를 호출
- 개발자는 Request 객체에서 HTTP 요청 정보를 편리하게 꺼내서 사용
- 개발자는 Response 객체에 HTTP 응답 정보를 편리하게 입력
- WAS는 Response 객체에 담겨있는 내용으로 HTTP 응답 정보를 생성
서블릿 컨테이너
- Tomcat처럼 서블릿을 지원하는 WAS를 서블릿 컨테이너라고 한다.
- 서블릿 컨테이너는 다음과 같은 일을 한다.
- 서블릿 객체를 생성, 초기화, 호출, 종료
- 서블릿 객체의 생명주기 관리
- 서블릿 객체는 싱글톤으로 관리한다.
- 고객의 요청이 올 때마다 계속 객체를 생성하는 것은 비효율적이므로 최초 로딩 시점에 서블릿 객체를 미리 만들어 두고 재활용한다.
- 따라서 모든 고객 요청은 동일한 서블릿 객체 인스턴스에 접근한다.
- 서블릿 컨테이너가 종료되면 함께 종료된다.
- 공유 변수를 사용할 시 주의해야 한다.
- JSP도 서블릿으로 변환되어서 사용한다.
- 동시 요청을 위한 멀티 쓰레드 처리를 지원한다.
동시 요청 - 멀티 쓰레드
- TCP/IP 커넥션 연결
- WAS에서 서블릿 객체 호출 → Thread가 한다.
Thread
- Thread는 애플리케이션 코드를 하나하나 순차적으로 실행한다.
- Thread가 없다면 자바 애플리케이션 실행이 불가능하다.
- Thread는 한 번에 하나의 코드 라인만 수행한다.
- 동시 처리가 필요하면 Thread를 추가로 생성해주어야 한다.
Thread가 하나일 경우
단일 요청 시
- HTTP 요청을 한다.
- WAS는 Thread를 할당한다.
- Thread는 코드를 실행한다.
- HTTP 응답을 하고 나면 Thread는 휴식을 한다.
다중 요청 시
- HTTP 요청이 1건 들어온다. (요청 1)
- WAS는 Thread를 할당한다.
- Thread는 요청 1을 처리하는데, 어떠한 이유로 처리가 지연되고 있다.
- HTTP 요청이 1건 더 들어온다. (요청 2)
- 요청 2는 Thread를 기다려야 한다.
- 이렇게 기다리다가 Timeout 등이 발생하면서 요청 1과 2 모두 오류가 날 수 있다.
요청마다 Thread를 생성할 경우
다중 요청에서 다음과 같이 동작한다.
- HTTP 요청이 1건 들어온다. (요청 1)
- WAS는 Thread를 할당한다.
- Thread는 요청 1을 처리하는데, 어떠한 이유로 처리가 지연되고 있다.
- HTTP 요청이 1건 더 들어온다. (요청 2)
- 요청 2는 새로운 Thread를 할당한다.
장점
- 동시 요청을 처리할 수 있다.
- 리소스(CPU, 메모리)가 허용할 때까지 처리 가능
- 하나의 Thread가 지연되어도 나머지 Thread는 정상 동작한다.
단점
- Thread의 생성 비용은 매우 비싸다.
- 고객의 요청이 올 때마다 Thread를 생성하면 응답 속도가 늦어진다.
- Thread는 Context-swtiching 비용이 발생한다.
- CPU의 코어는 Thread의 여러 개를 번갈아 실행하는데, 이를 컨텍스트 스위칭이라고 한다.
- Thread가 많아지면 이 컨텍스트 스위칭 비용이 점점 커진다.
- Thread 생성에 제한이 없다.
- 고객 요청이 너무 많이 오면 CPU와 메모리 임계점을 넘어서 서버가 죽을 수 있다.
Thread Pool
요청마다 Thread를 생성할 때 발생하는 단점을 해결하기 위해 WAS는 내부에 Thread Pool을 사용한다.
- 필요한 Thread를 Thread Pool에 보관하고 관리한다.
- Thread Pool에 생성 가능한 Thread의 최대치를 관리한다.
- Tomcat의 기본 설정은 최대 200개이다.
- Thread Pool은 다음과 같이 사용한다.
- Thread가 필요하면 Thread Pool에서 이미 생성되어 있는 Thread를 꺼내서 사용한다.
- Thread의 사용을 종료하면 Thread Pool에 해당 Thread를 반납한다.
- 최대 Thread가 모두 사용 중이어서 Thread Pool에 Thread가 없을 경우, 기다리는 요청은 거절하거나 특정 숫자만큼 대기하도록 설정할 수 있다.
동작 과정
- WAS의 Thread Pool에 미리 Thread를 만들어 둔다.
- HTTP 요청이 1건 들어온다. (요청 1)
- WAS는 Thread Pool에서 Thread를 꺼내 요청 1에게 할당한다.
- Thread는 요청 1을 처리하는데, 어떠한 이유로 처리가 지연되고 있다.
- HTTP 요청이 1건 더 들어온다. (요청 2)
- WAS는 Thread Pool에서 Thread를 꺼내 요청 2에게 할당한다.
- 요청이 끝나면 Thread를 Thread Pool에 반납한다.
장점
- Thread가 미리 생성되어 있으므로 Thread를 생성하고 종료하는 비용(CPU)이 절약되고, 응답 시간이 빠르다.
- 생성 가능한 Thread의 최대치가 있으므로 너무 많은 요청이 들어와도 기존 요청은 안전하게 처리할 수 있다.
성능 튜닝 관점에서
- WAS의 주요 튜닝 포인트는 Max Thread(최대 쓰레드) 수이다.
- 이 값이 너무 낮을 경우
- 동시 요청이 많으면, 서버 리소스는 여유롭지만 클라이언트는 금방 응답 지연될 수 있다.
- 이 값이 너무 높을 경우
- 동시 요청이 많으면, CPU와 메모리 리소스 임계점 초과로 서버가 다운될 수 있다.
- 따라서 적정값을 찾는 것이 중요하다.
- 적정값은 애플리케이션 로직의 복잡도, CPU, 메모리, IO 리소스 상황에 따라 모두 다르다.
- 따라서 최대한 실제 서비스와 유사하게 성능 테스트를 시도해야 한다.
- 성능 테스트 툴로는 아파치 ab, 제이미터, nGrinder 등이 있다.
정리
- 멀티 쓰레드에 대한 부분은 WAS가 처리해준다.
- 개발자가 멀티 쓰레드 관련 코드를 신경쓰지 않아도 된다.
- 멀티 쓰레드 환경이므로 싱글톤 객체(서블릿, 스프링 빈 등)는 주의해서 사용해야 한다.
HTML, HTTP API, CSR, SSR
정적 리소스
- 주로 웹 브라우저들이 요청을 한다.
- 웹 서버는 고정된 HTML 파일, CSS, JS, 이미지, 영상 등을 제공한다.
- 특정 폴더 아래에 이미 생성되어 있는 리소스 파일 제공
HTML 페이지
- WAS는 동적으로 필요한 HTML 파일을 생성해서 전달한다.
- 예를 들어서, 주문 내역 페이지를 보려고 한다면 주문 정보로 조회한 데이터로 HTML을 동적으로 생성하여 웹 브라우저에 제공한다.
- 웹 브라우저는 HTML을 받아서 해석 후 사용자에게 보여준다.
HTTP API
- HTML이 아니라 데이터를 전달한다.
- 주로 JSON 형식을 사용한다.
- 데이터만 주고 받으므로, UI 화면이 필요하면 클라이언트가 별도로 처리해야 한다.
- 다양한 시스템에서 호출한다.
활용 상황
- 앱 클라이언트 to 서버
- 앱에 UI 컴포넌트가 있기 때문에 데이터만 전달 받는다.
- 앱 클라이언트로는 아이폰, 안드로이드, PC 앱 등이 있다.
- 웹 클라이언트 to 서버
- 웹 브라우저에서 자바스크립트에서 ajax 등의 API를 통해 서버에 있는 API를 호출할 수 있다.
- 이때 자바스크립트에서 필요한 데이터만 받아서 HTML을 동적으로 만들어서 뿌린다.
- React, Vue.js와 같은 웹 클라이언트도 있다.
- 서버 to 서버
- 서버 간의 통신에는 데이터만 주고받으면 되므로 HTTP API를 사용한다.
- 예를 들어, 주문 서버와 결제 서버가 서로 데이터를 주고 받아야 할 경우가 있다.
- 기업 안에서 여러 서비스들이 잘게 쪼개져 있을 때(MSA) HTTP API를 통해서 많이 통신한다.
SSR과 CSR
서버 사이드 렌더링 (SSR)
- 서버에서 (동적으로) HTML 최종 결과를 만들어서 웹 브라우저에 전달한다.
- 웹 브라우저는 HTML을 보여주기만 하면 된다.
- 주로 정적인 화면에 사용한다.
- 관련 기술
클라이언트 사이드 렌더링 (CSR)
- 웹 브라우저에서 자바스크립트를 사용해 HTML 결과를 동적으로 생성해서 적용한다.
- 웹 브라우저에서 서버로 HTML 요청을 한다.
- 서버에서는 빈 HTML과 자바스크립트 링크를 전달한다.
- 웹 브라우저에서 자바스크립트를 요청한다.
- 서버에서는 클라이언트 로직과 HTML 렌더링 코드가 들어있는 자바스크립트를 전달한다.
- 웹 브라우저에서 HTML API로 데이터를 요청한다.
- 서버가 JSON 형식으로 데이터를 전달한다.
- 웹 브라우저는 자바스크립트로 HTML 결과를 렌더링한다.
- 주로 동적인 화면에 사용하며, 웹 환경을 마치 앱처럼 필요한 부분만 변경할 수 있다.
- 대표적인 예로는 구글 지도, Gmail, 구글 캘린더 등이 있다.
- 관련 기술
CSR과 SSR의 경계
- React, Vue.js를 CSR+SSR 동시에 지원하는 웹 프레임워크도 있다.
- SSR을 사용하더라도 자바스크립트를 사용하여 화면 일부를 동적으로 변경할 수 있다.
자바 백엔드 웹 기술 역사
과거의 기술
- 1997년, TCP/IP 연결과 멀티 쓰레드 등의 고민을 해결하기 위해 Servlet 기술이 등장한다.
- 그러나, Java 코드로 짜야 하기 때문에 HTML을 동적으로 생성하는 것이 굉장히 어렵다는 문제점이 있다.
- 1999년, Servlet의 문제를 해결하기 위해서 JSP가 등장한다.
- JSP는 최종적으로 서블릿으로 변한다.
- HTML 생성은 편리하지만, 비즈니스 로직까지 포함하면서 너무 많은 역할을 담당한다는 문제가 생겼다.
- 서블릿과 JSP를 조합하여 MVC 패턴을 사용
- MVC 패턴은 모델, 뷰, 컨트롤러로 역할을 나누어 개발한다.
- 핵심은 비즈니스 로직과 화면을 렌더링하는 부분을 분리하여, 관심사를 나누는 것이다.
- 2000년대 초부터 MVC 프레임워크의 등장
- MVC 패턴 자동화, 복잡한 웹 기술을 편리하게 사용할 수 있는 다양한 기능 지원
- 스트럿츠, 웹워크, 스프링 MVC
현재의 기술
- 애노테이션 기반의 스프링 MVC의 등장
@Controller
- MVC 프레임워크는 스프링 프레임워크와 사용하려면 통합에 대한 고민이 필요했다.
- 스프링 MVC는 스프링이 제공하기 때문에 통합에 대한 고민이 필요 없다.
- 스프링 부트의 등장
- 과거에는 서버에 WAS를 직접 설치하고 WAR 파일을 만들어서 설치한 WAS에 배포했다.
- 스프링 부트는 빌드 결과(Jar)에 WAS 서버를 포함함으로써 빌드와 배포를 단순화했다.
최신 기술
- 스프링 웹 기술이 다음 두 가지로 분화를 한다.
- Web Servlet - Spring MVC
- Web Reactive - Spring WebFlux
Spring WebFlux
- 완전한 비동기 non-blocking 처리
- 최소 Thread로 최대 성능
- Thread의 개수를 CPU의 코어(혹은 +1)에 딱 맞춤으로써 Thread를 계속 돌아갈 수 있도록 한다.
- Thread Context-switching 비용이 거의 들지 않으므로 효율화된다.
- 함수형 스타일로 개발한다.
- 서블릿 기술을 사용하지 않는다.
다만, 다음과 같은 단점이 있다.
- 웹 플럭스는 기술적 난이도가 매우 높다.
- 아직 RDB 지원이 부족하다.
- 일반 MVC의 Thread 모델도 충분히 빠르다.
- 실무에서 아직 많이 사용하지 않는다.
자바 뷰 템플릿 역사
View Template이란
- HTML을 편리하게 사용하는 뷰 기능을 말한다.
- 주로 HTML을 백엔드에서 동적으로 생성하는 것을 말한다.
View Template의 종류
- JSP
- 프리마커(Freemarker), 벨로시티(Velocity)
- JSP의 속도 문제를 해결했다.
- 다양한 기능을 제공한다.
- HTML 파일을 열었을 때 JSP 코드가 다 보인다.
- 타임리프(Thymeleaf)
- 내추럴 템플릿: HTML의 모양을 유지하면서 뷰 템플릿 기능을 적용할 수 있다.
- 스프링 MVC와 강력한 기능 통합
- 최선의 선택, 단 성능은 프리마커, 벨로시티가 더 빠름
Reference
인프런 강의
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 | 김영한 - 인프런