[Spring In Action] 리액티브 시스템과 클라우드 네이티브 패턴: Eureka, Gateway, 그리고 배포의 진화

1. 문제 제기 (Introduction & Problem Statement)

  • 관찰 현상 또는 질문:
    모놀리식에서 마이크로서비스로 넘어오면서 인프라 레이어의 개념들이 혼재되기 시작한다. *"Nginx가 로드밸런싱을 해주는데 왜 Eureka라는 별도의 서버가 필요한가?", *"API Gateway가 있는데 Nginx는 또 왜 앞단에 두는가?" 와 같은 질문들은 '정적 인프라''동적 클라우드 환경'의 차이를 명확히 이해하지 못했기 때문에 발생한다. 또한, *"Flux와 Mono는 단순한 리스트 포장지(Wrapper)가 아닌가?"* 라는 의문은 리액티브 스트림의 본질인 '비동기 파이프라인'을 이해해야 해소된다.
  • 탐구 목표:
    본 아티클에서는 리액티브 프로그래밍의 데이터 흐름(Flux/Mono)을 시작으로, 프록시(Proxy)와 로드 밸런서의 계층별 차이(Nginx vs Eureka)를 명확히 정의한다. 나아가 서킷 브레이커와 모니터링 시스템, 그리고 배포 전략의 진화 과정을 통해 현대적인 클라우드 네이티브 아키텍처의 당위성을 분석한다.

2. 기술 분석 및 핵심 원리 (Technical Deep Dive)

2-1. 리액티브 스트림: Flux와 Mono는 단순한 래퍼(Wrapper)인가?

WebFlux에서 사용하는 FluxMono는 데이터를 담아두는 컬렉션(List)과는 근본적으로 다르다.

  • 동작 원리 (Subscriber 인터페이스):
    이들은 데이터를 메모리에 적재하는 그릇이 아니라, 데이터가 흘러가는 파이프라인의 설계도다.
    public interface Subscriber<T> {
      void onNext(T t);       // 데이터가 하나 도착할 때마다 실행 (이벤트 드리븐)
      void onError(Throwable t); // 에러 발생 시 처리
      void onComplete();      // 스트림 종료 신호
    }
    


* **분석**: `List<String>`은 100만 개의 데이터를 모두 메모리에 로드한 뒤 리턴하지만, `Flux<String>`은 100만 개가 DB에서 조회되는 족족(onNext) 하나씩 클라이언트로 흘려보낸다. 따라서 **적은 메모리로 대용량 트래픽**을 처리할 수 있다.
* **R2DBC의 필요성**: JPA(JDBC)는 Blocking 방식이므로, DB 응답을 기다리는 동안 쓰레드가 멈춘다. WebFlux의 이점을 살리려면 DB 드라이버 단계부터 비동기인 **R2DBC**를 사용해야 한다.

#### 2-2. 인프라의 미로: Proxy, Nginx, Eureka, Gateway 정리

가장 혼동하기 쉬운 네트워크 컴포넌트들의 역할과 위치를 정리한다.

**1. 프록시(Proxy)의 두 얼굴**

* **포워드 프록시 (Forward Proxy)**: 클라이언트 앞단에 위치. 클라이언트가 누군지 숨긴다. (예: 사내망 VPN, 우회 접속)
* **리버스 프록시 (Reverse Proxy)**: 서버 앞단에 위치. 서버가 누군지 숨긴다. (예: Nginx)

**2. 로드 밸런싱: Nginx vs Eureka (Client-side Discovery)**

* **Server-side LB (Nginx)**: 클라이언트는 Nginx 주소만 안다. Nginx가 뒤에 있는 서버 A, B 중 하나로 토스한다. (전통적인 방식)
* **Client-side LB (Eureka + Spring Cloud)**: 클라우드 환경에서는 서버 IP가 수시로 바뀐다(Elastic/Dynamic IP). 클라이언트(MSA 서비스)가 Eureka에게 "Service A 어디 있어?"라고 물어보고, 받은 리스트 중 하나를 골라 **직접** 요청을 보낸다.
* *Why Eureka?* Nginx 설정 파일(`nginx.conf`)에 IP를 하드코딩하면, 서버가 늘어날 때마다 Nginx를 재시작해야 한다. Eureka는 이를 실시간으로 감지한다.



**3. Nginx와 API Gateway의 공존**

* **Nginx (최전방)**: 정적 파일(HTML, CSS, Image) 처리, SSL 종료, DDoS 방어 등 하드웨어적인 부하를 처리한다.
* **Spring Cloud Gateway (MSA 대문)**: 인증/인가, 동적 라우팅(Eureka 연동), 서킷 브레이킹 등 비즈니스 로직에 가까운 라우팅을 담당한다.
* **흐름**: `Internet -> Nginx -> Spring Cloud Gateway -> Microservices`

#### 2-3. 회복탄력성(Resilience)과 모니터링

분산 시스템은 언제든 실패할 수 있다는 가정하에 설계된다.

**1. 서킷 브레이커 (Circuit Breaker)**

* **상태 머신**:
* **Closed (닫힘)**: 정상. 전기가 흐른다.
* **Open (열림)**: 에러가 임계치를 넘음. 요청을 즉시 차단(Fail Fast)하여 망가진 서버가 회복할 시간을 준다.
* **Half-Open (반열림)**: 간보기. 요청을 살짝 보내보고 성공하면 닫고, 실패하면 다시 연다.



**2. 모니터링 스택 (Observability)**

* **Actuator**: 애플리케이션의 상태 데이터를 수집.
* **Micrometer**: 수집한 데이터를 표준 포맷으로 변환 (어댑터 역할).
* **Prometheus**: 주기적으로 데이터를 긁어가서(Pull) DB에 저장.
* **Grafana**: 저장된 데이터를 대시보드로 시각화.

#### 2-4. 배포의 진화 (Deployment)

* **1세대 (WAR)**: 서버에 Tomcat을 먼저 깔고, 애플리케이션(WAR)을 넣어 돌렸다. (환경 설정 복잡)
* **2세대 (Fat JAR)**: Spring Boot가 Tomcat을 내장(Embedded). JAR 파일 하나만 있으면 어디서든 실행(`java -jar`) 가능.
* **3세대 (Container/Kubernetes)**: OS 환경까지 패키징(Docker Image). 이제는 '서버'가 아니라 '이미지'를 관리하며, 쿠버네티스가 이를 오케스트레이션한다.

---

### 3. 결론 및 고찰 (Conclusion & Takeaways)

* **핵심 요약:**
* **Flux/Mono**는 데이터 컨테이너가 아니라 **이벤트 기반의 데이터 파이프라인**이며, R2DBC와 결합해야 진정한 비동기 논블로킹이 완성된다.
* **Eureka**는 클라우드 환경에서 **수시로 변하는 IP 주소(Dynamic IP)를 관리**하기 위한 필수 요소이며, Nginx와 같은 정적 로드 밸런서의 한계를 보완한다.
* **Nginx와 Gateway**는 경쟁 관계가 아니라 상호 보완 관계다. (물리적 방어 vs 논리적 라우팅)
* 배포는 서버 의존성을 점점 제거하는 방향(WAR -> JAR -> Container)으로 발전해왔다.


* **기술적 통찰 및 나의 생각:**
이번 학습을 통해 *"인프라가 코드가 된다(Infrastructure as Code)"* 는 개념을 체감했다. 과거에는 시스템 엔지니어가 L4 스위치나 Nginx 설정을 통해 하드코딩하던 로드 밸런싱이, 이제는 Eureka와 Gateway를 통해 애플리케이션 레벨에서 소프트웨어적으로 처리된다. 특히 서킷 브레이커가 단순히 에러를 막는 것을 넘어, **'시스템 전체의 연쇄적인 붕괴(Cascading Failure)를 막고 회복 시간을 벌어준다'**는 점은 MSA 안정성의 핵심임을 깨달았다.
* **향후 과제:**
* 쿠버네티스 환경(K8s Service, Ingress)을 도입하면 Eureka와 Spring Cloud Gateway의 역할은 어떻게 대체되거나 축소되는가? (Service Mesh, Istio 학습 필요)
* Resilience4j의 서킷 브레이커 설정을 실제 운영 트래픽에 맞춰 튜닝하는 기준은 무엇인가?