[Spring] 리액티브 프로그래밍을 들어가며

2025. 10. 12. 23:46·Framework & Library/Spring

우리는 HTTP, HTTPS 프로토콜을 통해 클라이언트와 서버 간 통신을 진행한다.

우리가 흔히 많이 사용하는 웹, 앱 등에서는 대부분 HTTP, HTTPS 프로토콜 기반으로 데이터를 전달하게 된다. 서버들이 어떤 방식을 통해 데이터를 전달해주는지 알아보자.

서버는 대표적으로 웹 서버와 웹 어플리케이션 서버가 존재한다.

Web Server

흔히 우리가 많이 보는 웹 화면을 표시해주는 서버이며, 정적 리소스를 제공해준다.

여기서 정적 리소스란 이미지, HTML, CSS, JavaScript 등 과 같은 정적인 파일을 제공하는 역할을 해주고, 흔히 우리가 쓰는 크롬 등 웹 브라우저에서는 해당 파일을 랜더링하여 우리가 볼 수 있는 화면으로 만들어 주는 역할을 한다.

웹 서버에는 Nginx, Apache 등이 존재한다.

Web Application Server

웹 어플리케이션 서버는 웹 서버의 역할도 진행할 수 있지만, 들어온 요청에 맞게 동적으로 만들어진 컨텐츠를 제공해 준다.

여기서 동적은 서버 내 알고리즘, 비즈니스 로직, DB 조회 등 요청에 따라 컨텐츠를 제공하는 것에 있어 동적을 의미한다.

웹 어플리케이션은 축약하여 WAS라 불리며, Tomcat, Jetty, JBoss 등이 존재한다.

웹 어플리케이션 아키텍처
([https://medium.com/@chrisjune_13837/web-웹서버-앱서버-was-app이란-692909a0d363](https://medium.com/@chrisjune_13837/web-%EC%9B%B9%EC%84%9C%EB%B2%84-%EC%95%B1%EC%84%9C%EB%B2%84-was-app%EC%9D%B4%EB%9E%80-692909a0d363))

웹 어플리케이션 아키텍처
(https://medium.com/@chrisjune_13837/web-웹서버-앱서버-was-app이란-692909a0d363)

이 밖에도 데이터를 저장해두는 데이터베이스나 프록시, 메일 등 다양한 서버가 존재한다.

여러 서버의 상호 작용에 의해 우리가 사용하는 웹이나 앱 등에서의 원할한 사용이 가능해졌다.

그렇다면 한 가지 생각이 들 수도 있다.

아까 WAS가 웹 서버 역할을 할 수도 있다고 하였는데, 하나의 서버가 여러가지의 역할을 가질 순 없는걸까?

그럼, WAS 하나로 모든 역할을 대신하면..?

WAS는 웹 서버의 기능도 수행할 수 있기에, 단일 서버로 구성해도 기술적으로 문제는 없다.

하지만 여러 이유가 존재하는데.

1. 기능 분리로 인한 서버 부하 방지

  • WAS는 DB 조회나 비즈니스 로직 처리 등 무거운 작업을 수행하기 때문에, 단순 정적 컨텐츠 요청은 Web Server가 처리하는 것이 훨씬 효율적이다.
  • 모든 요청을 WAS가 처리하게 되면 서버 부하가 커지고, 동적 컨텐츠 처리 속도 저하로 이어질 수 있다.

2. 보안 강화를 위한 물리적 분리

  • SSL 암복호화와 같은 보안 관련 처리를 Web Server에서 수행함으로써, WAS를 보안 위협으로부터 보호할 수 있다.

3. 로드 밸런싱을 통한 안정성 확보

  • 여러 대의 WAS를 Web Server를 통해 연결함으로써 부하 분산(load balancing) 및 장애 대응(fail over, fail back) 이 가능하다.
  • 특정 WAS에 장애가 발생하면, Web Server는 자동으로 다른 WAS로 요청을 분산시켜 무중단 서비스를 실현할 수 있다.

4. 다양한 언어/플랫폼의 웹 애플리케이션 서비스 가능

  • Web Server를 통해 다른 언어들과 애플리케이션을 동시에 운영할 수 있다.
  • 예를 들어, 정적 리소스를 제공하는 Web Server를 두고, Node.js 기반의 API 서버는 RESTful API를 제공하며, Flutter로 개발된 모바일 앱에서는 해당 API를 호출하여 동적으로 데이터를 주고받는 구조가 가능하다.

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

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

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

image.png

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

서블릿(Servlet)

앞서 WAS는 HTTP와 HTTPS 를 기반으로 동작한다고 했었다. 그럼 구체적으로 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이라는 통일된 데이터 포멧을 통해 서버와 클라이언트, 서버와 서버와의 통신을 진행할 수 있게 된다.

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

[스프링 MVC 1편] 웹 애플리케이션의 이해

'Framework & Library > Spring' 카테고리의 다른 글

[Spring] Spring Profile을 활용한 환경 구축  (1) 2026.01.18
[Spring] Spring Security 겉핥기  (0) 2025.12.06
[Spring] 리액티브에서의 Scheduler와 Context 그리고 Testing  (0) 2025.10.20
[Spring] Backpressure와 Sinks 이해하기  (0) 2025.10.20
[Spring] 리액티브 시스템? 리액티브 프로그래밍?  (0) 2025.10.17
'Framework & Library/Spring' 카테고리의 다른 글
  • [Spring] Spring Security 겉핥기
  • [Spring] 리액티브에서의 Scheduler와 Context 그리고 Testing
  • [Spring] Backpressure와 Sinks 이해하기
  • [Spring] 리액티브 시스템? 리액티브 프로그래밍?
ryuwon
ryuwon
여러 개발 정보 끄적이고 있습니닷..
  • ryuwon
    이름 없는 블로그
    ryuwon
  • 글쓰기 관리
  • 전체
    오늘
    어제
    • 분류 전체보기 (34)
      • Series (0)
      • Programming (1)
        • Java (1)
        • C (0)
        • Swift (0)
      • Framework & Library (8)
        • Spring (6)
        • Spring Boot (2)
      • Data & ORM (0)
        • RDBMS (0)
        • NoSQL (0)
        • ORM (0)
      • Infra & DevOps (1)
        • Cloud (0)
        • DevOps (1)
        • Infra (0)
      • Knowledge (4)
        • 자료구조 (1)
        • 알고리즘 (3)
        • 운영체제 (2)
        • 네트워크 (1)
        • 아키텍쳐 및 디자인 패턴 (0)
        • 개발지식 (3)
      • Testing (0)
      • Security & System (0)
      • Project (5)
      • Writing (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Spring Profile
    네트워크
    OCI
    프로젝트
    찐빵
    K3S
  • 최근 댓글

  • hELLO· Designed By정상우.v4.10.5
ryuwon
[Spring] 리액티브 프로그래밍을 들어가며
상단으로

티스토리툴바