본문 바로가기

Spring

Spring에서 비동기 처리 완전 정복: 성능 향상과 확장성을 위한 실전 가이드

728x90
반응형

현대 애플리케이션은 사용자에게 빠르고 유연한 응답을 제공하기 위해 비동기 처리(Asynchronous Processing)를 필요로 합니다. Spring에서는 이러한 비동기 작업을 매우 쉽게 처리할 수 있는 여러 가지 방법을 제공합니다. 이번 글에서는 비동기 처리의 기본 개념, 주로 사용되는 분야, 구현 시 주의할 점, 그리고 Gradle 기반 프로젝트를 통해 비동기 처리 방법을 실전 예시와 함께 소개하겠습니다.

비동기 처리의 기본 개념

비동기 처리란 특정 작업을 요청한 후 그 작업이 완료될 때까지 기다리지 않고, 다른 작업을 동시에 처리할 수 있는 방식을 의미합니다. 전통적인 동기 처리 방식에서는 요청과 응답이 차례로 일어나지만, 비동기 처리에서는 작업이 완료되지 않아도 다른 작업을 처리할 수 있기 때문에 애플리케이션의 성능과 반응성을 크게 향상시킬 수 있습니다.

비동기 처리가 많이 사용되는 분야

  1. 파일 처리 및 I/O 작업
    파일 읽기/쓰기, 네트워크 요청 등 시간이 오래 걸리는 작업에서 비동기 처리는 매우 유용합니다. 동기적으로 처리하면 해당 작업이 끝날 때까지 애플리케이션이 멈추지만, 비동기적으로 처리하면 동시에 다른 작업을 진행할 수 있습니다.
  2. 멀티스레드 환경에서의 병렬 처리
    CPU 연산이 많은 작업이나 데이터 처리량이 많은 배치 작업에서 비동기 처리를 활용하면 멀티스레드를 이용해 성능을 최적화할 수 있습니다.
  3. 외부 API 호출
    REST API나 SOAP API를 호출하는 작업에서는 비동기 처리를 통해 응답을 기다리지 않고 다음 작업을 처리할 수 있습니다.
  4. 웹 소켓 및 실시간 데이터 스트리밍
    실시간 통신이 필요한 애플리케이션(예: 채팅, 주식 시세 조회)에서 비동기 처리는 실시간 데이터를 효율적으로 처리하는 데 필수적입니다.
반응형

비동기 처리의 구현 방법

Spring에서는 비동기 처리를 위해 몇 가지 주요 방식을 제공합니다. 이번 섹션에서는 @Async 어노테이션을 통한 비동기 처리와 CompletableFuture를 이용한 방법을 설명하고, 간단한 Gradle 기반 프로젝트 예시를 통해 이들을 살펴보겠습니다.

1. @Async 어노테이션을 이용한 비동기 처리

@Async는 Spring에서 기본적으로 제공하는 비동기 처리를 위한 어노테이션입니다. 이 어노테이션을 사용하면 메서드를 비동기로 실행시킬 수 있습니다. 이를 통해 별도의 스레드 풀에서 작업을 처리하게 되어, 메인 스레드를 차단하지 않고 다른 작업을 병행할 수 있습니다.

1.1 Gradle 설정

먼저, Gradle 프로젝트에서 Spring의 비동기 기능을 사용하기 위해 build.gradle 파일에 필요한 의존성을 추가합니다.

plugins {
    id 'org.springframework.boot' version '3.1.2'
    id 'io.spring.dependency-management' version '1.1.0'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
    useJUnitPlatform()
}

1.2 @EnableAsync 설정

Spring에서 비동기 처리를 활성화하기 위해 @EnableAsync 어노테이션을 @Configuration 클래스에 추가해야 합니다.

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;

@Configuration
@EnableAsync
public class AsyncConfig {
}

1.3 비동기 메서드 구현

이제 비동기로 실행할 메서드를 구현해 보겠습니다. 메서드에 @Async 어노테이션을 붙이면, Spring은 이를 비동기로 처리합니다.

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class AsyncService {

    @Async
    public void asyncMethod() {
        System.out.println("비동기 작업 시작!");
        try {
            Thread.sleep(5000);  // 5초간 지연
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("비동기 작업 완료!");
    }
}

1.4 비동기 서비스 호출

Controller에서 비동기 메서드를 호출하여 비동기 처리가 제대로 동작하는지 확인할 수 있습니다.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AsyncController {

    @Autowired
    private AsyncService asyncService;

    @GetMapping("/async")
    public String callAsyncMethod() {
        asyncService.asyncMethod();
        return "비동기 메서드 호출됨!";
    }
}

위의 예시에서 /async 엔드포인트를 호출하면, asyncMethod는 비동기로 실행되며, 메인 스레드는 즉시 "비동기 메서드 호출됨!" 응답을 반환합니다.

2. CompletableFuture를 사용한 비동기 처리

CompletableFuture는 자바에서 제공하는 비동기 처리 메커니즘으로, 비동기 작업이 완료된 후 결과를 반환받을 수 있습니다. @Async 어노테이션과 함께 사용할 수 있으며, 복잡한 비동기 작업의 흐름을 제어할 때 유용합니다.

2.1 CompletableFuture 반환 메서드 구현

CompletableFuture를 반환하는 비동기 메서드를 만들어봅시다.

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.concurrent.CompletableFuture;

@Service
public class AsyncServiceWithFuture {

    @Async
    public CompletableFuture<String> asyncMethodWithReturn() {
        System.out.println("비동기 작업 시작 (결과 반환 포함)!");
        try {
            Thread.sleep(5000);  // 5초간 지연
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("비동기 작업 완료 (결과 반환)!");
        return CompletableFuture.completedFuture("비동기 처리 완료!");
    }
}

2.2 CompletableFuture 결과 처리

Controller에서 CompletableFuture를 반환받아 결과를 처리하는 예제를 살펴보겠습니다.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

@RestController
public class AsyncControllerWithFuture {

    @Autowired
    private AsyncServiceWithFuture asyncServiceWithFuture;

    @GetMapping("/async-future")
    public String callAsyncMethodWithFuture() throws ExecutionException, InterruptedException {
        CompletableFuture<String> futureResult = asyncServiceWithFuture.asyncMethodWithReturn();
        return "결과: " + futureResult.get();  // 결과가 준비될 때까지 대기
    }
}

위 코드에서는 /async-future 엔드포인트를 호출할 때, 비동기 작업이 완료된 후 CompletableFuture의 get() 메서드를 통해 결과를 반환받습니다.

728x90

비동기 처리 시 주의할 점

  1. 스레드 풀 관리
    비동기 작업이 너무 많으면 스레드 풀이 포화 상태에 이를 수 있습니다. 기본적으로 Spring Boot는 제한된 스레드 풀을 사용하므로, 필요한 경우 적절한 스레드 풀 크기를 설정해야 합니다. TaskExecutor를 사용해 커스텀 스레드 풀을 설정할 수 있습니다.
  2. 예외 처리
    비동기 작업에서 발생하는 예외는 메인 스레드에서 바로 처리되지 않기 때문에, 예외 처리를 위한 별도의 메커니즘을 도입해야 합니다. CompletableFuture.exceptionally를 사용하여 예외 처리 로직을 구현할 수 있습니다.
  3. 트랜잭션 관리
    비동기 메서드는 별도의 스레드에서 실행되기 때문에, 트랜잭션 전파 문제가 발생할 수 있습니다. 비동기 작업에서도 트랜잭션이 필요하다면, 트랜잭션 전파 설정에 주의해야 합니다.

Spring에서 비동기 처리를 구현하면 애플리케이션 성능과 확장성을 크게 향상시킬 수 있습니다. @Async 어노테이션과 CompletableFuture를 활용한 비동기 처리 방식은 개발자들이 손쉽게 비동기 작업을 처리할 수 있는 강력한 도구입니다. 하지만, 스레드 관리와 예외 처리, 트랜잭션 관리에 신경을 써야 안정적인 비동기 처리를 구현할 수 있습니다.

728x90
반응형