현대 애플리케이션은 사용자에게 빠르고 유연한 응답을 제공하기 위해 비동기 처리(Asynchronous Processing)를 필요로 합니다. Spring에서는 이러한 비동기 작업을 매우 쉽게 처리할 수 있는 여러 가지 방법을 제공합니다. 이번 글에서는 비동기 처리의 기본 개념, 주로 사용되는 분야, 구현 시 주의할 점, 그리고 Gradle 기반 프로젝트를 통해 비동기 처리 방법을 실전 예시와 함께 소개하겠습니다.
비동기 처리의 기본 개념
비동기 처리란 특정 작업을 요청한 후 그 작업이 완료될 때까지 기다리지 않고, 다른 작업을 동시에 처리할 수 있는 방식을 의미합니다. 전통적인 동기 처리 방식에서는 요청과 응답이 차례로 일어나지만, 비동기 처리에서는 작업이 완료되지 않아도 다른 작업을 처리할 수 있기 때문에 애플리케이션의 성능과 반응성을 크게 향상시킬 수 있습니다.
비동기 처리가 많이 사용되는 분야
- 파일 처리 및 I/O 작업
파일 읽기/쓰기, 네트워크 요청 등 시간이 오래 걸리는 작업에서 비동기 처리는 매우 유용합니다. 동기적으로 처리하면 해당 작업이 끝날 때까지 애플리케이션이 멈추지만, 비동기적으로 처리하면 동시에 다른 작업을 진행할 수 있습니다. - 멀티스레드 환경에서의 병렬 처리
CPU 연산이 많은 작업이나 데이터 처리량이 많은 배치 작업에서 비동기 처리를 활용하면 멀티스레드를 이용해 성능을 최적화할 수 있습니다. - 외부 API 호출
REST API나 SOAP API를 호출하는 작업에서는 비동기 처리를 통해 응답을 기다리지 않고 다음 작업을 처리할 수 있습니다. - 웹 소켓 및 실시간 데이터 스트리밍
실시간 통신이 필요한 애플리케이션(예: 채팅, 주식 시세 조회)에서 비동기 처리는 실시간 데이터를 효율적으로 처리하는 데 필수적입니다.
비동기 처리의 구현 방법
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() 메서드를 통해 결과를 반환받습니다.
비동기 처리 시 주의할 점
- 스레드 풀 관리
비동기 작업이 너무 많으면 스레드 풀이 포화 상태에 이를 수 있습니다. 기본적으로 Spring Boot는 제한된 스레드 풀을 사용하므로, 필요한 경우 적절한 스레드 풀 크기를 설정해야 합니다. TaskExecutor를 사용해 커스텀 스레드 풀을 설정할 수 있습니다. - 예외 처리
비동기 작업에서 발생하는 예외는 메인 스레드에서 바로 처리되지 않기 때문에, 예외 처리를 위한 별도의 메커니즘을 도입해야 합니다. CompletableFuture.exceptionally를 사용하여 예외 처리 로직을 구현할 수 있습니다. - 트랜잭션 관리
비동기 메서드는 별도의 스레드에서 실행되기 때문에, 트랜잭션 전파 문제가 발생할 수 있습니다. 비동기 작업에서도 트랜잭션이 필요하다면, 트랜잭션 전파 설정에 주의해야 합니다.
Spring에서 비동기 처리를 구현하면 애플리케이션 성능과 확장성을 크게 향상시킬 수 있습니다. @Async 어노테이션과 CompletableFuture를 활용한 비동기 처리 방식은 개발자들이 손쉽게 비동기 작업을 처리할 수 있는 강력한 도구입니다. 하지만, 스레드 관리와 예외 처리, 트랜잭션 관리에 신경을 써야 안정적인 비동기 처리를 구현할 수 있습니다.
'Spring' 카테고리의 다른 글
Spring Boot와 GraphQL로 만드는 차세대 API 서비스 – 쉽게 따라하는 가이드! (0) | 2024.09.06 |
---|---|
Redis Pub/Sub로 실시간 메시징 애플리케이션 만들기: 초간단 가이드 (0) | 2024.09.06 |
Spring Boot와 Redis: 초고속 데이터 처리를 위한 완벽한 조합 (0) | 2024.09.05 |
스프링에서 예외 처리를 마스터하는 5가지 방법: 안정적인 애플리케이션을 위한 필수 가이드 (0) | 2024.09.04 |
Spring Boot와 Keycloak으로 간단한 인증 시스템 구현하기: 실습으로 배우는 OAuth2와 SSO (0) | 2024.09.04 |