Spring에는 두 가지 웹 프레임워크가 있습니다.
- WebMVC : 전통적인 멀티 스레드 기반의 웹 프레임워크
- WebFlux : 리액티브 스택 기반의 웹 프레임워크
Spring WebMVC와 WebFlux는 공존할 수 없다고 생각했는데, Spring framework에서 제공하는 Http Client API로 RestTemplate 대신 WebClient를 사용하라고 권고하고 있어서 의문이 생겼습니다.
두 모듈의 공존이 가능한 것인가?
Spring 문서를 확인해보면 "애플리케이션은 하나 또는 다른 모듈을 사용하거나 경우에 따라 두 모듈을 모두 사용할 수 있습니다(예: 반응형 WebClient가 포함된 Spring MVC 컨트롤러)."라고 써져 있습니다.
실제로 spring-boot-starter-web, spring-boot-starter-webflux 의존성을 동시에 추가할 수 있습니다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
SpringBoot는 WebMVC와 WebFlux를 모두 사용할 수 있는 경우 SpringMVC를 자동으로 구성합니다.
- Spring MVC는 Netty에서 Run 할 수 없음
- 두 인프라가 동일한 작업(예: 정적 리소스 제공, 매핑 등)을 위해 경쟁하게 됨
- 동일한 컨테이너 내에서 두 런타임 모델을 혼합하는 것은 좋은 생각이 아니며 성능이 나쁘거나 전혀 작동하지 않을 가능성이 높음
그래서 일반적으로 두 가지 의존성이 존재하면 WebApplicationType이 SERVELT이 되므로 WebFluxAutoConfiguration은 활성화되지 않습니다. 만약 @EnableWebFlux 어노테이션으로 강제로 WebFlux를 활성화하더라도 WebFluxConfigurationSupport에서 애플리케이션 실행 시점에 에러를 던져 방지하고 있습니다.
WebClient를 사용하여 Multiple HTTP 호출에 최적화하고 Reactor 연산자를 사용하려는 경우 MVC의 어노테이션 @Controller를 사용하고 return type을 Mono나 Flux로 사용할 수 있습니다. (SpringBoot talk 참조)
WebClient를 최대한 활용하고 싶다면 코어에 논블로킹(내부적으로 netty를 사용하여 논블로킹 방식으로 요청을 관리함)이 가능한 WebFlux를 사용해야 합니다.
다만, 비동기 워크플로우를 관리하는 두 가지 방법이 다르기 때문에 퍼블리셔 기반 코드(Mono, Flux)와 MVC 비동기 스타일 (@Async, DeferredResult) 중 하나만 사용해야 합니다.
@Bean
public WebClient webClient() {
//참고: https://lasel.kr/archives/740
ConnectionProvider connectionProvider = ConnectionProvider.builder("web-client-pool")
.maxConnections(500) //유지할 Connection Pool의 수, max 값 많큼 미리 생성해 놓지 않고 필요할때마다 생성(최대 생성 가능한 수)
.pendingAcquireTimeout(Duration.ofSeconds(5)) //Connection Pool에서 사용할 수 있는 Connection 이 없을때 (모두 사용 중일때) Connection을 얻기 위해 대기하는 시간
.pendingAcquireMaxCount(-1) //커넥션 풀에서 커넥션을 가져오는 시도 횟수
// .evictInBackground(Duration.ofSeconds(5)) //백그라운드에서 만료된 connection을 제거하는 주기
.maxIdleTime(Duration.ofSeconds(55L)) //커넥션 풀에서 idle 상태의 커넥션을 유지하는 시간(주의: AWS ALB 타임아웃이 60초라서 60초보다 작은게 좋음)
.maxLifeTime(Duration.ofSeconds(55L)) //커넥션 풀에서 살아있을 수 있는 커넥션의 최대 수명시간
.build(); //keepAliveTimeout 보다 maxIdleTime 이 작은게 좋음(일반적으로 AWS나 nginx keeplive timeout이 60초인 경우 많음)
final int timeoutSeconds = 5;
//참고: https://yangbongsoo.tistory.com/30
HttpClient httpClient = HttpClient.create(connectionProvider)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.doOnConnected(connection ->
connection.addHandlerLast(new ReadTimeoutHandler(timeoutSeconds))
.addHandlerLast(new WriteTimeoutHandler(timeoutSeconds))
).responseTimeout(Duration.ofSeconds(timeoutSeconds + 1)); //idle 커넥션을 닫거나 맺는 시간을 고려하지 않은 순수 http 요청/응답 시간을 제한(connection timeout + 커넥션풀에서 얻는 시간 보다 무조건 커야함)
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
}
[Reference]
https://docs.spring.io/spring-boot/docs/current/reference/html/features.html
'spring > spring webFlux' 카테고리의 다른 글
[WebFlux] Mono에 담긴 List를 하나로 합치기 (0) | 2023.07.13 |
---|---|
[webFlux] SwitchIfEmpty가 항상 호출되는 이슈 해결 (Lazy Evaluation) (0) | 2022.12.20 |
[SpringBoot] Reactive RESTful Web Service 구축 실습 (0) | 2021.02.18 |
[Spring] Reactive Programming (1) - Reactor 3, WebFlux (0) | 2021.02.18 |
댓글