원본 글: https://www.baeldung.com/spring-mvc-async-vs-webflux
이 글은 Spring MVC에서 제공되는 @Async와 Spring WebFlux를 비교해봅니다.
1. Implementation Scenario
간단한 API들을 포함하고 있는 웹 애플리케이션을 제작하고, 쓰레드들을 blocking, non-blocking방식을 통해서 비교해봅니다.
하나의 엔드포인트를 만들고 String 형태의 값을 반환할겁니다. 여기서 핵심은 요청이 들어올때 Filter에서 200ms정도 딜레이를 줄거고 Controller에서 500ms의 딜레이를 주고 String을 반환할겁니다.
테스트툴은 Apache ab라는 툴을 사용할 것이고, 이를 통해서 여러 요청을 보내볼겁니다.
2. Spring MVC Async
Spring 3.0에서 등장한 @Async애노테이션의 목표는 애플리케이션이 별도의 스레드로 로드가 많은 작업을 실행할 수 있도록 하는겁니다.
이 애노테이션을 단 함수를 호출하는 호출자는 원하면 기다릴 수 있기에 void형태로 함수가 반환되면 안되며, Future
나 CompletableFuture
같은 클래스로 반환되어야합니다.
Spring 3.2에서는 웹 계층에서도 @Async를 사용할 수 있게 패키지가 추가되었습니다. 따라서 @Controller
나 @RestController
와 같이 사용할 수 있게 됩니다.
사용자가 요청을 보내면 요청은 필터를 거쳐 DispatcherServlet
에 도달하게 됩니다.
그 후 다음과 같이 비동기처리가 됩니다.
- 서블릿이 요청을 AsyncWebRequest#startAsync()로 비동기 모드로 설정함.
- WebAsyncManager에게 요청 처리를 위임하고, 서블릿과 필터는 응답을 커밋하지 않음.
- 요청이 필터 체인을 정방향으로 통과한 후, 역방향으로 다시 거쳐감.
- WebAsyncManager가 ExecutorService를 사용해 별도의 스레드에서 작업 실행.
- 작업이 완료되면 DispatcherServlet이 최종 응답을 클라이언트에게 반환.
다음은 예제 애플리케이션을 만들어봅니다.
먼저, @EnableAsync
를 Spring Boot에 추가해 비동기로 동작할 수 있음을 알립니다.
@SpringBootApplication
@EnableAsync
public class AsyncVsWebFluxApp {
public static void main(String[] args) {
SpringApplication.run(AsyncVsWebFluxApp.class, args);
}
}
다음은 AsyncFilter라는 클래스를 둬서 200ms의 딜레이를 주고 요청을 넘기도록 합니다.
@Component
public class AsyncFilter implements Filter {
...
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
// sleep for 200ms
filterChain.doFilter(servletRequest, servletResponse);
}
}
마지막으로 /async_result라는 엔드포인트를 만듭니다.
@RestController
public class AsyncController {
@GetMapping("/async_result")
@Async
public CompletableFuture getResultAsyc(HttpServletRequest request) throws InterruptedException {
// sleep for 500 ms
Thread.sleep(500);
return CompletableFuture.completedFuture("Result is ready!");
}
}
@Async를 통하여 이 함수는 메인스레드가 처리하지않고 별도 쓰레드풀에서 처리하게 됩니다.
이렇게 구성한 애플리케이션을 테스트툴을 가지고 결과를 확인해봅니다.
원본 글에서는 JConsole을 이용한 쓰레드 모니터링을 진행하는데 저는 설치하지 않아서 Apache 테스트만 진행하였습니다. 비교를 쉽게하기 위해 밑에 내용을 추가하겠습니다.
4. Spring WebFlux
Spring 5.0에서는 WebFlux라는 비동기 방식의 reactive web을 구현하기 위한 기술이 출시되었습니다. reactor API를 기반으로 하며, Reactive Stream의 강력한 구현체 중 하나입니다.
SpringWebFlux는 reactive 백프레셔라는 것과 Servlet3.1의 논-블록킹 방식이 지원됩니다. 모든 웹서버가 동일한 방식으로 쓰레드가 관리되지는 않지만 WebFlux의 논-블록킹방식과 reactive 백프레셔가 지원하는한 정상 동작합니다.
Spring WebFlux는 Mono, Flux라는 리액티브 타입과 여러 연산자들을 지원하여 선언적으로 로직을 구성할 수 있습니다. 뿐만 아니라 함수형기반의 엔드포인트 형성도 가능합니다.
5. Spring WebFlux Implementation
WebFlux로 동일한 시나리오를 제작해봅니다.
@SpringBootApplication
public class SpringWebfluxAsyncDiffTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringWebfluxAsyncDiffTestApplication.class, args);
}
}
MVC와 다르게 @EnableAsync
어노테이션이 필요 없습니다.
다음은 필터를 제작합니다.
@Component
public class WebFluxFilter implements WebFilter {
@Override
public Mono filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
return Mono
.delay(Duration.ofMillis(200))
.then(
webFilterChain.filter(serverWebExchange)
);
}
}
마지막으로 컨트롤러를 제작합니다.
@RestController
public class WebFluxController {
@GetMapping("/flux_result")
public Mono getResult(ServerHttpRequest request) {
return Mono.defer(() -> Mono.just("Result is ready!"))
.delaySubscription(Duration.ofMillis(500));
}
}
이제 결과를 비교해봅니다.
먼저 제가 로컬에서 실행했을때 MVC 속도 처리량 입니다.
Time taken for tests: 101.656 seconds
Complete requests: 1600
Failed requests: 0
요청을 모두 처리하는데 101초가 걸렸습니다.
다음은 WebFlux 속도 및 처리량입니다.
Time taken for tests: 29.317 seconds
Complete requests: 1600
Failed requests: 0
MVC대비 속도가 1/3이나 감축된것을 확인할 수 있습니다. 또한 원본글에서 쓰레드 갯수를 비교했을때도 WebFlux가 쓰레드 사용갯수가 적었습니다.
그만큼 쓰레드 효율이 늘었다고 볼 수 있습니다.
6. What's the Difference?
GPT가 본문의 내용을 잘 정리해줘서 가져왔습니다.
- Spring Async와 Spring WebFlux의 기본 차이점
- Spring Async는 Servlet 3.0 규격을 지원하며, 차단적인 I/O 모델을 사용합니다. 즉, 클라이언트와의 통신 중에 블로킹이 발생할 수 있습니다. 이는 특히 느린 클라이언트에서 성능 문제를 일으킬 수 있습니다.
- Spring WebFlux는 Servlet 3.1+ 규격을 지원하고, 비동기(non-blocking) I/O 모델을 사용합니다. 이로 인해 클라이언트와의 통신이 블로킹되지 않고 비동기적으로 처리됩니다.
- 요청 본문 처리 차이
- Spring Async에서는 요청 본문(request body)이나 요청 파트를 읽을 때 블로킹 방식으로 처리됩니다. 즉, 요청이 들어올 때까지 기다리는 방식이기 때문에 성능에 제한이 있을 수 있습니다.
- 반면, Spring WebFlux는 비동기(non-blocking) 방식으로 요청 본문을 읽고 처리합니다. 이 덕분에 더 효율적인 리소스 사용과 성능을 발휘할 수 있습니다.
- Filter와 Servlet 처리 차이
- Spring Async에서는 Filter와 Servlet이 동기적으로 작동합니다. 즉, 요청을 처리하는 동안 다른 작업을 처리하지 못하고 기다려야 합니다.
- 반면, Spring WebFlux는 전체 비동기 통신을 지원합니다. 이로 인해 요청과 응답을 더 효율적으로 처리할 수 있습니다.
- 서버 호환성
- Spring Async는 Servlet 3.0+을 지원하는 서버에서만 동작할 수 있습니다. 반면, Spring WebFlux는 Netty, Undertow와 같은 다양한 서버에서도 호환됩니다. 즉, 더 넓은 범위의 서버와 호환성을 가지고 있습니다.
- 리액티브 백프레셔(Backpressure) 지원
- Spring WebFlux는 리액티브 백프레셔를 지원합니다. 즉, 빠른 데이터 생산자(예: 클라이언트가 빠르게 데이터를 보내는 경우)에 대해 어떻게 반응할지 제어할 수 있는 기능을 제공합니다. 이는 Spring MVC Async보다 더 정교한 제어가 가능합니다.
- 함수형 스타일의 코드 및 선언적 API
- Spring WebFlux는 Reactor API를 바탕으로 함수형 스타일의 코드와 선언적 API를 제공하며, 코드가 더 직관적이고 명확하게 구성됩니다.
- 어떤 기술을 선택할까?
- Spring WebFlux는 비동기적, 리액티브 특성을 제공하여 스케일링과 가용성에서 이점이 있습니다. 이는 고가용성 및 탄력성을 필요로 하는 시스템에 적합합니다.
- 하지만 Spring Async 또는 Spring MVC는 특정 프로젝트에서는 더 적합할 수 있습니다. 예를 들어, 동기 방식으로 충분히 처리할 수 있는 경우에는 Spring MVC나 Spring Async가 더 적합할 수 있습니다.
- Spring Async는 Spring MVC의 동기 구현보다 성능이 더 우수하며, Spring WebFlux는 리액티브 특성 덕분에 더 높은 가용성과 유연성을 제공합니다.
7. 결론
Spring MVC에서 제공하는 @Async
와 WebFlux를 비교하였습니다.
그림으로 처리 흐름을 좀 더 확인할 수 있으면 좋았을 것 같은데 이 글로도 어느정도 두 기능의 차이점을 이해할 수 있게되었습니다.
예제는 간단하지만 비동기 코드를 작성하면 할수록 '내가 작성한 코드가 정말 비동기식으로 돌아가나?'라는 의문이 생기는 것 같습니다.
'Baeldung번역&공부 > Spring-Reactive' 카테고리의 다른 글
WebFlux-Retry(Guide to Retry in Spring WebFlux) (0) | 2025.02.28 |
---|---|
Mono와 Flux의 차이점(Difference Between Flux and Mono) (0) | 2025.02.27 |
WbFlux에서 404Status를 반환하는방법(How to Return 404 with Spring WebFlux) (0) | 2025.02.25 |
Webflux에서 Error를 다루는방법(Handling Errors in Spring WebFlux) (0) | 2025.02.24 |
Spring WebFlux - 정적 컨텐츠들 (0) | 2025.02.18 |