본문 바로가기

Spring Framework/Spring Boot

[Spring Boot] 리액티브 프로그래밍이란?

개요

실전 스프링부트 책을 읽으며 

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가지 핵심 인터페이스를 정의합니다:

  1. Publisher: 데이터를 발행
  2. Subscriber: 데이터를 구독
  3. Subscription: 구독 관계 관리
  4. 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도 다룰 수 있는 날이 오기를 기대해야겠네요!