다음과 같이 web, webflux 의존성을 추가한다.
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-web-services'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
서버를 띄우면 톰캣 서버가 뜬다.

스레드 덤프를 떠보면 http-nio-8080-*처럼 톰캣에서 사용되는 스레드의 이름이 관찰된다.

리액티브 코드 실행
리액티브 코드로 작성한 API를 동시에 50명의 유저가 호출하도록 하고 스레드 덤프를 떠본다.
@GetMapping("/react")
public Mono<List<Integer>> call(
) {
return Flux.range(0,1000000000)
.doOnNext(System.out::println)
.collectList();
}

스택 트레이스를 보면 흥미로운 부분을 관찰할 수 있다.
stacktrace:
...
org.springframework.web.servlet.mvc.method.annotation.ReactiveTypeHandler$DeferredResultSubscriber.connect(ReactiveTypeHandler.java:466)
org.springframework.web.servlet.mvc.method.annotation.ReactiveTypeHandler.handleValue(ReactiveTypeHandler.java:163)
org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler.handleReturnValue(ResponseBodyEmitterReturnValueHandler.java:154)
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:78)
...
handleReturnValue, 즉 컨트롤러 응답을 핸들링할 때 ReactiveTypeHanlder가 사용되고 있다.
이때 DeferredResultSubscriber를 시작으로 컨트롤러에 작성해둔 리액티브 코드를 실행한다.
Stack trace :
...
reactor.core.publisher.FluxRange$RangeSubscription.request(FluxRange.java:109)
reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.request(FluxOnAssembly.java:649)
reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.request(FluxPeekFuseable.java:144)
reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.request(FluxOnAssembly.java:649)
reactor.core.publisher.Operators$BaseFluxToMonoOperator.request(Operators.java:2067)
reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.request(FluxOnAssembly.java:649)
reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.request(FluxOnAssembly.java:649)
reactor.core.publisher.StrictSubscriber.onSubscribe(StrictSubscriber.java:77)
reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onSubscribe(FluxOnAssembly.java:633)
reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onSubscribe(FluxOnAssembly.java:633)
reactor.core.publisher.Operators$BaseFluxToMonoOperator.onSubscribe(Operators.java:2051)
reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onSubscribe(FluxOnAssembly.java:633)
reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onSubscribe(FluxPeekFuseable.java:178)
reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onSubscribe(FluxOnAssembly.java:633)
reactor.core.publisher.FluxRange.subscribe(FluxRange.java:69)
reactor.core.publisher.Mono.subscribe(Mono.java:4576)
// 컨트롤러 코드 실행(subscribe)
org.springframework.web.servlet.mvc.method.annotation.ReactiveTypeHandler$DeferredResultSubscriber.connect(ReactiveTypeHandler.java:466)
org.springframework.web.servlet.mvc.method.annotation.ReactiveTypeHandler.handleValue(ReactiveTypeHandler.java:163)
org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler.handleReturnValue(ResponseBodyEmitterReturnValueHandler.java:154)
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:78)
...
따라서 톰캣 스레드로 reactive code를 실행한다는 사실을 알 수 있다.
관찰된 바, 서블릿 컨테이너로 http request/response를 처리하기 때문에 여전히 1 thread per 1 request로 요청을 처리하게 된다. 스레드 덤프 결과를 보면 request를 보낸 users 수만큼 스레드(50개)가 생성되어있었다.
Webclient 사용 케이스 관찰
@GetMapping("/react")
public Mono<List<String>> call(
) {
WebClient webClient = WebClient.create("http://localhost:8080"); // base URL 설정
return Flux.range(0, 10000)
.flatMap(i ->
webClient.get()
.uri("/api/hello")
.retrieve()
.bodyToMono(String.class)
.doOnNext(value ->
System.out.println("API " + i + " 응답: " + value))
)
.collectList();
}

(tomcat thread가 200개가 된 건 로컬 API를 호출해서 그렇다.)
parallel 스레드가 관찰된다. 이는 리액티브에서 사용하는 스레드 이름으로, WebClient 요청 처리를 위해 스레드풀을 만들어 사용하고 있는 것으로 추측된다.
결론
WebMVC와 WebFlux를 함께 사용하는 것은 가능은 하다. WebFlux를 사용하는 여러 케이스를 분석해보진 못했지만, 적어도 WebClient를 쓸 때 WebFlux의 이벤트 루프 스레드는 논블로킹이지만 톰캣 스레드는 블로킹된다는 점, 여전히 1 request 당 1개의 스레드가 필요하다는 한계점을 관찰할 수 있었다.
'Spring' 카테고리의 다른 글
| @ResponseBody를 이용한 응답 과정 알아보기 (0) | 2024.09.13 |
|---|---|
| HttpServletResponse와 HttpOutputMessage (0) | 2024.09.11 |
| 서블릿에서 HTTP Response 보내기 (0) | 2024.08.31 |
| 영속성 관리(ORM 표준 기술 {3} ) (0) | 2023.07.06 |
| JPA(자바 ORM 표준 JPA 프로그래밍-기본편) { 2 } (0) | 2023.07.05 |