728x90
요즘 내 속을 썩이는 부분 중 하나가 바로 WebClient 내장 Threads인데..
실무에서 WebClient 내장 Thread를 사용하는게 절대로 안좋다는 의미는 아니지만
내가 담당하는 서비스의 경우 어플리케이션 내 리소스 낭비를 하면 안되기 때문에 요즘 많이 공부를 하게 되었는데
이 참에 한번 Spring WebClient 내장 Thread와 커스텀 방법을 한번 적어보려고 한다.
1. WebClient 내장 Thread 기본 동작
/**
* HTTP 요청과 응답 처리는 Netty의 이벤트 루프 스레드에서 실행
*/
WebClient webClient = WebClient.create("https://sg-moomin.tistory.com");
Mono<String> result = webClient.get()
.uri("/contents")
.retrieve()
.bodyToMono(String.class);
result.subscribe(System.out::println);
- Reactor Netty를 기반으로 동작하며, 기본적으로 Netty의 이벤트 루프 스레드 풀에서 네트워크 I/O를 처리
- 스레드 수는 보통 시스템의 CPU 코어 수에 따라 결정
- CPU Core * 2배
2. WebClient 커스텀 Thread 설정 방법
WebClient 내장 Thread를 사용해도 무관하지만 특별한 상황에 따라 별도 Thread를 사용해야할 경우
아래의 2가지 방법으로 처리가 가능
2.1 clientConnector
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.LoopResources;
import org.springframework.web.reactive.function.client.WebClient;
HttpClient httpClient = HttpClient.create()
.runOn(LoopResources.create("sg-moomin-threads", 4, true)); // 4개의 커스텀 스레드 설정
WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.baseUrl("https://sg-moomin.tistory.com")
.build();
Mono<String> result = webClient.get()
.uri("/contents")
.retrieve()
.bodyToMono(String.class);
result.subscribe(System.out::println);
- clientConnector 사용하여 Netty의 이벤트 루프를 커스터마이징 가능
- 고정된 스레드 수를 가진 커스텀 이벤트 루프를 설정
2.1.1 clientConnector 특징
- Netty의 기본 이벤트 루프 대신 커스텀 스레드 풀이 네트워크 I/O를 처리
- 내장 스레드는 사용되지 않고, 지정된 4개의 스레드가 모든 요청을 담당.
- 애플리케이션의 워크로드에 따라 스레드 수를 조정할 수 있어 유연성이 높음
2.2 WebClient subscribeOn과 publishOn으로 스레드 제어
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
WebClient webClient = WebClient.create("https://sg-moomin.tistory.com");
Mono<String> result = webClient.get()
.uri("/contents")
.retrieve()
.bodyToMono(String.class)
.subscribeOn(Schedulers.boundedElastic()) // 구독을 별도 스레드에서
.publishOn(Schedulers.parallel()); // 다운스트림 작업을 별도 스레드에서
result.subscribe(System.out::println);
- subscribeOn과 publishOn을 사용하면 요청 처리 흐름의 스레드를 변경할 수 있음
- 네트워크 I/O 자체는 여전히 Netty 스레드에서 처리되지만, 구독 및 다운스트림 작업을 별도 스레드에서 실행
2.2.1 subscribeOn과 publishOn 특징
- subscribeOn: 요청의 구독과 초기 실행이 boundedElastic 스레드 풀에서 처리
- publishOn: 응답 데이터 처리 등 다운스트림 작업이 parallel 스레드 풀에서 실행
- 내장 스레드 사용 여부: 네트워크 I/O는 여전히 Netty 이벤트 루프에서 처리되므로, 내장 스레드가 완전히 배제된다고볼 수 없음
3. 내장 Thread vs 커스텀 Thread
설정 방법 | 내장 스레드 사용 여부 | 적합한 상황 |
기본 설정 | 사용 | 간단한 애플리케이션, 기본 성능으로 충분할 때 |
clientConnector | 사용 안 함 | 네트워크 I/O 스레드를 세밀히 제어하고 싶을 때 |
subscribeOn/publishOn | 사용 | 연산 작업을 별도 스레드로 분리하고 싶을 때 |
- 성능 최적화: 높은 트래픽 환경에서는 clientConnector로 커스텀 스레드 풀을 설정하는 게 유리
- 작업 분리: CPU 집약적 작업(예: 데이터 변환)이 많다면 publishOn으로 다운스트림을 별도 스레드로 옮기는 게 좋음
4. 마치며
WebClient의 스레드 설정은 애플리케이션 요구사항에 따라 유연하게 조정할 수 있고 기본 내장 스레드만으로 충분할 수도 있지만, clientConnector로 네트워크 레벨을 제어하거나 subscribeOn/publishOn으로 연산 스레드를 분리하면 더 효율적인 처리가 가능하다는걸 알게 되면서 실무에서는 clientConnector를 활용하고 있다.
- 그 이유로는 k8s 환경에서 cpu core가 높을 경우 불필요한 Thread를 생성하기 때문에 위와 같이 처리함
다만, 커스텀 Thread가 무조건적으로 정답은 아니라는점 꼭 알았으면 한다.
728x90
'Programing > Java & Spring' 카테고리의 다른 글
세무민의 코딩일기 : Spring WebClient doOnError와 onErrorResume 차이 (0) | 2025.03.26 |
---|---|
세무민의 코딩일기 : Spring WebClient에 대해 알아보자 (1) | 2025.03.10 |
[Spring] QueryDsl, java17 Q파일 생성 안되는 이슈 해결 (0) | 2025.01.24 |
[JPA&QueryDSL] QueryDsl이란 무엇인가? (4) | 2024.11.24 |
[Spring] 정의되지 않은 요청 파라미터에 대한 유효성 검증 및 Jackson 과 @EnableWebMvc의 관계성 (2) | 2024.11.12 |