개요
실전 스프링부트 책을 읽으며
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(DemoApplication.class);
springApplication.setWebApplicationType(WebApplicationType.REACTIVE);
springApplication.run(args);
}
}
다음과 같이 웹 어플리케이션의 타입을 리액티브 방식으로 바꾸는 실습을 해보았다.
그런데, 웹 어플리케이션을 리액티브로 바꾼다는 건 무슨 뜻일까???
더 자세히 알아보도록 하자.
1. 리액티브 프로그래밍이란?
리액티브 프로그래밍은 데이터 스트림과 변화의 전파에 중점을 둔 프로그래밍 패러다임이다.
간단히 말해, 데이터가 변경되면 자동으로 관련된 모든 것들이 반응(react)하도록 하는 방식
📺 Netflix 시청 상황
기존 방식 (동기/블로킹):
- 영상 1분을 다운로드 완료 → 재생
- 다음 1분을 다운로드 완료 → 재생
- 매번 다운로드가 끝날 때까지 기다림
리액티브 방식 (비동기/논블로킹):
- 영상 데이터가 조금씩 스트리밍으로 도착
- 데이터가 도착하는 즉시 재생
- 기다리지 않고 연속적으로 처리
2. 왜 리액티브가 필요한가?
현대 애플리케이션의 특징
- 높은 동시성: 수천 명의 사용자가 동시 접속
- 다양한 데이터 소스: 데이터베이스, 외부 API, 메시지 큐 등
- 실시간 요구사항: 채팅, 알림, 라이브 스트리밍
- 클라우드 환경: 자원 효율성이 중요
기존 방식의 한계
전통적인 Servlet 방식
@RestController
public class TraditionalController {
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
// 1. 데이터베이스 조회 (100ms 대기)
User user = userRepository.findById(id);
// 2. 외부 API 호출 (200ms 대기)
UserProfile profile = externalApiService.getProfile(user.getEmail());
// 3. 결과 조합
user.setProfile(profile);
return user; // 총 300ms 동안 스레드가 블로킹됨
}
}
문제점:
- 하나의 요청마다 스레드 하나가 할당
- I/O 작업 동안 스레드가 아무것도 하지 못하고 대기
- 동시 요청이 많아지면 스레드 부족으로 성능 저하
Reactive 방식
@RestController
public class ReactiveController {
@GetMapping("/users/{id}")
public Mono<User> getUser(@PathVariable Long id) {
return userRepository.findById(id) // 논블로킹 데이터베이스 조회
.flatMap(user ->
externalApiService.getProfile(user.getEmail()) // 논블로킹 API 호출
.map(profile -> {
user.setProfile(profile);
return user;
})
);
// 스레드는 즉시 반환되어 다른 요청 처리 가능
}
}
3. 리액티브 스트림의 핵심 개념
리액티브 스트림이란?
비동기 스트림 처리를 위한 표준으로, 4가지 핵심 인터페이스를 정의합니다:
- Publisher: 데이터를 발행
- Subscriber: 데이터를 구독
- Subscription: 구독 관계 관리
- Processor: Publisher와 Subscriber 역할을 모두 수행
백프레셔(Backpressure)
생산자가 데이터를 너무 빨리 생성할 때,
소비자가 "잠깐, 처리할 시간을 좀 줘!" 라고 말할 수 있는 메커니즘
Reactor 라이브러리
Spring WebFlux는 Reactor 라이브러리를 사용하며, 두 가지 주요 타입을 제공합니다:
Mono: 0~1개의 데이터
// 사용자 한 명 조회
Mono<User> user = userRepository.findById(1L);
// 빈 Mono
Mono<String> empty = Mono.empty();
// 값이 있는 Mono
Mono<String> hello = Mono.just("Hello World");
Flux: 0~N개의 데이터
// 모든 사용자 조회
Flux<User> users = userRepository.findAll();
// 숫자 스트림
Flux<Integer> numbers = Flux.range(1, 10);
// 리스트를 Flux로
Flux<String> names = Flux.fromIterable(Arrays.asList("김철수", "이영희", "박민수"));
4. Spring Webflux 이해하기
WebFlux vs Spring MVC
| 구분 | Spring MVC | Spring WebFlux |
| 스택 | Servlet Stack | Reactive Stack |
| 서버 | Tomcat, Jetty | Netty, Undertow |
| 프로그래밍 모델 | 명령형 | 함수형/선언형 |
| 동시성 | 스레드 풀 기반 | 이벤트 루프 기반 |
| I/O | 블로킹 | 논블로킹 |
5. 언제 리액티브를 사용해야 할까?
적합한 경우
- I/O 집약적인 애플리케이션
- 높은 동시성이 필요한 경우
- 마이크로서비스 간 비동기 통신
- 실시간 스트리밍 데이터 처리
- 리소스가 제한된 환경
부적합한 경우
- CPU 집약적인 작업
- 간단한 CRUD 애플리케이션
- 팀의 리액티브 경험 부족
- 기존 블로킹 라이브러리와의 통합이 많은 경우
정리
- 높은 동시성과 I/O 집약적 상황에서 진가를 발휘
- 학습 곡선이 가파르므로 팀의 준비가 필요
- 점진적 도입을 통해 리스크 최소화
- 모니터링과 디버깅 도구에 대한 이해 필요
간단하게 WebFlux에 대해 알아보았습니다.
더 자세한 실습은 책의 후반부에서 나오니 그때 따로 포스트를 정리하도록 하겠습니다.
아직은 부족한 점이 많아 MVC 패턴만 사용하고 있지만,
언젠가 WebFlux도 다룰 수 있는 날이 오기를 기대해야겠네요!
'Spring Framework > Spring Boot' 카테고리의 다른 글
| [Spring Boot] 스프링 부트 이벤트 리스너 구현과 등록 (2) | 2025.07.17 |
|---|---|
| [Spring Boot] SpringApplication.run() 메서드 인자에 관해서 (0) | 2025.07.17 |
| [Spring Boot] 스프링 부트 애플리케이션 종료 (1) | 2025.07.02 |
| [Spring Boot] SpringApplication.run()을 호출하면 일어나는 일들 (0) | 2025.07.02 |
| [Spring Boot] Jar vs War 배포 방법 비교 (0) | 2025.01.08 |