Programming

Spring WebFlux: 비동기 논블로킹 API 개발

ipxy 2025. 3. 18. 11:03
728x90

Spring WebFlux는 비동기 논블로킹(Asynchronous Non-Blocking) 방식으로 동작하는 스프링 웹 프레임워크입니다.
기존 Spring MVC와 달리 Reactive Streams 기반의 Reactor 라이브러리를 사용하여 높은 동시성을 처리할 수 있는 API를 만들 수 있습니다.

Spring Boot의 공식 비동기 웹 프레임워크
Reactor 기반의 논블로킹 I/O 처리
적은 리소스로 높은 동시성 처리 가능
마이크로서비스 및 서버리스 환경에 최적화


1️⃣ Spring WebFlux vs Spring MVC 비교

특징 Spring MVC (Blocking) Spring WebFlux (Non-Blocking)

동작 방식 동기(Blocking) 방식 비동기(Non-Blocking) 방식
스레드 모델 요청마다 새로운 스레드 할당 이벤트 루프 기반 (Netty)
사용 라이브러리 Servlet API, Tomcat Reactor, Netty, Undertow
장점 직관적인 코드, 개발 용이 높은 동시성 처리, 적은 리소스
단점 많은 스레드 사용, 리소스 낭비 러닝 커브 있음, Debugging 어려움
적합한 환경 전통적인 웹 애플리케이션 실시간 데이터 처리, 마이크로서비스

2️⃣ Spring WebFlux 설정

🛠 프로젝트 의존성 추가 (pom.xml)

<dependencies>
    <!-- Spring WebFlux -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>

    <!-- Lombok (선택 사항) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>
</dependencies>

3️⃣ 기본적인 WebFlux API 구현

🔹 기본 REST API (논블로킹)

@RestController
@RequestMapping("/api")
public class WebFluxController {

    @GetMapping("/hello")
    public Mono<String> hello() {
        return Mono.just("Hello, WebFlux!");
    }

    @GetMapping("/numbers")
    public Flux<Integer> getNumbers() {
        return Flux.range(1, 10).delayElements(Duration.ofMillis(500));
    }
}

✅ Mono<T> → 단일 값 반환 (ex. HTTP 1건 요청 처리)
✅ Flux<T> → 여러 개의 값 스트리밍 (ex. 실시간 데이터 처리)


🔹 비동기 서비스 & Repository

🛠 데이터 모델

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String id;
    private String name;
}

🛠 비동기 Repository (ReactiveCrudRepository 사용)

@Repository
public interface UserRepository extends ReactiveCrudRepository<User, String> {
    Flux<User> findByName(String name);
}

🛠 비동기 Service

@Service
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public Mono<User> getUserById(String id) {
        return userRepository.findById(id);
    }

    public Flux<User> getUsersByName(String name) {
        return userRepository.findByName(name);
    }
}

🛠 컨트롤러에서 비동기 데이터 처리

@RestController
@RequestMapping("/users")
public class UserController {
    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/{id}")
    public Mono<User> getUser(@PathVariable String id) {
        return userService.getUserById(id);
    }

    @GetMapping("/name/{name}")
    public Flux<User> getUsersByName(@PathVariable String name) {
        return userService.getUsersByName(name);
    }
}

4️⃣ Streaming 데이터 처리 (Server-Sent Events)

WebFlux는 서버에서 클라이언트로 데이터를 스트리밍하는 SSE(Server-Sent Events) 를 지원합니다.

🔹 SSE 예제 (서버에서 실시간 데이터 푸시)

@RestController
@RequestMapping("/stream")
public class StreamController {

    @GetMapping(value = "/time", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> streamTime() {
        return Flux.interval(Duration.ofSeconds(1))
                   .map(tick -> "Current Time: " + Instant.now());
    }
}

✅ MediaType.TEXT_EVENT_STREAM_VALUE → 클라이언트가 실시간 데이터를 받을 수 있도록 SSE 스트림 설정
✅ Flux.interval(Duration.ofSeconds(1)) → 1초마다 새로운 데이터 생성

💡 📌 클라이언트에서 수신하는 방법 (JavaScript)

const eventSource = new EventSource('/stream/time');
eventSource.onmessage = (event) => {
    console.log("Received: ", event.data);
};

5️⃣ WebClient: 비동기 HTTP 호출

Spring WebFlux에서는 RestTemplate 대신 WebClient를 사용하여 비동기 HTTP 요청을 처리할 수 있습니다.

🔹 WebClient 설정

@Bean
public WebClient webClient() {
    return WebClient.builder().baseUrl("http://localhost:8080").build();
}

🔹 비동기 API 호출 예제

@Service
public class ApiService {
    private final WebClient webClient;

    public ApiService(WebClient webClient) {
        this.webClient = webClient;
    }

    public Mono<String> getDataFromExternalApi() {
        return webClient.get()
                        .uri("/api/hello")
                        .retrieve()
                        .bodyToMono(String.class);
    }
}

6️⃣ 성능 최적화 및 운영

🔹 Netty 사용 (Tomcat 대신)

Spring WebFlux는 기본적으로 Netty를 사용하여 높은 성능을 제공합니다.
Netty를 명시적으로 설정하려면 application.properties에서 다음과 같이 설정합니다.

server.port=8080
spring.main.web-application-type=reactive

기본적으로 Netty 사용 (Tomcat 제거 가능)
Undertow로 변경 가능 (spring-boot-starter-undertow 추가)


🔹 WebFlux와 R2DBC (비동기 데이터베이스)

JPA 대신 R2DBC (Reactive Relational Database Connectivity) 를 사용하면 비동기 방식으로 데이터베이스를 처리할 수 있습니다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
    <groupId>io.r2dbc</groupId>
    <artifactId>r2dbc-postgresql</artifactId>
</dependency>

JPA 대신 R2DBC 사용하여 논블로킹 데이터 처리
MySQL, PostgreSQL 등 다양한 DB 지원


🔥 결론: Spring WebFlux가 적합한 경우

비동기 논블로킹 API가 필요한 경우
대량의 요청을 동시에 처리해야 할 때
실시간 데이터 스트리밍 (SSE, WebSocket)이 필요한 경우
마이크로서비스 및 클라우드 네이티브 환경에서 사용

Spring WebFlux는 높은 동시성과 확장성을 제공하여 쿠버네티스, 서버리스, 마이크로서비스 환경에서 강력한 API를 구축할 수 있습니다! 🚀

728x90