티스토리 뷰
WebClient란?
간략히 말해서, web request를 수행하는 entry point를 나타내는 인터페이스이다. HTTP/1.1 프로토콜로 동작하는 reactive하고 non-blocking client이다. 이 인터페이스는 DefaultWebClient 라는 클래스 하나로만 구현되어 있다.
Concurrency in Spring WebFlux
Reactive Programming의 탄생 배경
전형적인 웹 어플리케이션은 여러 복잡하고 상호작용하는 부분들로 구성되어 있습니다. 많은 상호작용들은 blocking 방식입니다. 예를 들어, 데이터를 fetch 나 update 하는 DB call 이 있습니다. 하지만 독립적이고 동시에 (병렬적으로도 가능한) 수행될 수 있는 것들도 있습니다.
예를 들어, 웹 서버에 두 개의 요청이 다른 스레드에 의해 핸들링될수 있습니다. multi-core 플랫폼 위에서라면, 전반적인 응답 시간 관점으로 볼 때 명백한 이점이 있습니다.
다음 동시성 모델은 thread-per-request model로 알려져 있습니다.

각 request에 하나의 스레드가 할당된다
하지만 스레드 기반 동시성은 싱글 스레드 내의 대부분의 상호작용은 여전히 blocking 방식이라는 사실은 다뤄지지 않고 있습니다. 게다가 자바에서 동시성을 얻을 때 사용하는 native thread들은 context swtich 와 같은 비용을 가집니다.
웹 어플리케이션은 점점 더 많은 리퀘스트를 받고, thread-per-request model은 기대에 못미치는 성능을 보여주기 시작합니다.
결론적으로, 우리가 필요한 것은 상대적으로 적은 수의 thread로 증가하는 리퀘스트를 다룰 수 있는 동시성 모델입니다. 이렇게 등장한 것이 reactive programing입니다.
Reactive programing의 동시성
reactive programing은 프로그램을 데이터 플로우와 propagation of change 를 구조화할 수 있게 해줍니다. 완전한 non-blocking 환경에서 더 좋은 자원 효율과 높은 동시성을 가질 수 있게 해줍니다.
reactive programing 은 동시성을 얻는 스레드 사용의 접근을 다르게 가져갑니다. 그래서 reactive programing 가 가져온 근본적인 차이는 **비동기성(asynchronicity)**에서 옵니다.

즉, 프로그램 플로우는 동기적 연산의 시퀀스에서 이벤트들의 비동기성 stream 으로 전환됩니다.
예를 들어, reactive model에서, data를 fetch 하는 동안 db로 부터 읽는 연산 호출을 block하지 않습니다. 호출은 즉시 호출한 자(publisher)에게 반환됩니다. subscriber(호출된 자)는 이벤트가 발생한 후에 그 이벤트를 처리합니다.
위의 그림과 같이, reactive programming은 이벤트가 생성되고 소비되는 것보다 비동기적 이벤트 스트림으로써 프로그램을 구조화했다는 것을 강조합니다.
Event Loop
어떻게 리액티브 프로그래밍이 적은 스레드로 높은 동시성을 갖는지 살펴보자.
서버의 리액티브 비동기적 프로그래밍 모델 중 하나가 event loop model이다.

위는 리액티브 비동기적 프로그래밍의 개념을 나타내는 이벤트 루프의 추상적인 디자인이다.
- 이벤트 루프는 싱글 스레드로 계속 돌아간다.
- 이벤트 루프는 순차적으로 이벤트 큐로부터 이벤트를 처리하고 ****플랫폼에 콜백이 등록된 후에 즉시 반환한다.
- 플랫폼은 DB call이나 외부 서비스와 같은 연산의 완료를 트리거해준다.
- 이벤트 루프는 연산 완료 노티에 콜백을 트리거해주고 원래 caller에게 결과를 보내준다.
이벤트 루프 모델은 Node.js, Netty 에 포함된 플랫폼 개수만큼 실행된다. 기존의 Apache HTTP server, Tomcat, JBoss 보다 더 확장성을 갖고 있다.
Reactive Programming with Spring Webflux
이제 Reactive Programming 에 대해 충분히 봤으니 Spring Webflux에 대해 살펴보자
Webflux는 스프링의 reactive-stack web framework이다.

위와 같이 스프링 기존의 웹 프레임워크와 매치될 수 있고 반드시 바꿔야 하는 건 아니다.
- Spring Webflux는 기존 annotation-based programming model with functional routing을 확장한 것이다.
- 런타임에 상호실행 가능하도록 Reactive Stream API에 HTTP 런타임을 기반으로 적용하였다.
- 톰캣과 같은 Servlet 3.1+ 컨테이너를 포함하여 더 다양하게 지원가능하다
- WebClient를 포함한다.
Threading Model in WebClient
Webclient는 Spring WebFlux에 있는 reactive HTTP client이다.
REST를 기반으로 하는 통신에 사용할 수 있고, end-to-end reactive하는 애플리케이션을 만들 수 있다
WebClient는 event loop model를 사용하여 동시성을 갖는다. 만약 Reactor Netty에서 WebClient가 실행된다면, 서버에서 사용하는 event loop를 공유한다.
그러나 Servlet 3.1+ 컨테이너에서는 다른 방식으로 동작한다.
Java NIO를 사용하는 Tomcat Architecture
Tomcat5는 그의 Connector 컴포넌트(주로 리퀘스트를 받는 역할) 내에서 NIO 를 지원한다.
다른 컴포넌트는 Container 컴포넌트로, 컨테이너 관리를 한다.
다음은 Connector 컴포넌트의 threading model이다.

Acceptor, Poller, Worker, Worker에게 할당되는 thread pool로 구성된다
- Right way to use Spring WebClient in multi-thread environment
- WebClient 의 ClientHttpConnector 로 HTTP resources (connections, caches, etc)를 관리할 수 있다
- WebClient는 immutable하다. thread-safe 하다위의 두 특징으로 보아, ClientHttpConnector 는 재활용되어야 한다. 이것은 커넥션 풀을 공유하기 때문이다. (성능에 큰 영향을 미칠 수 있다) 즉, 모든 WebClient 인스턴스를 WebClient.create() 호출로 얻는 게 좋다. Spring Boot가 WebClient.Builder bean을 주입받도록 해줄 것이다.
세 가지 구성방식이 있다.
- 빌더 단계에서 구성
WebClient baseClient = WebClient.create().baseUrl("<https://example.org>"); - 요청별로 구성
Mono<ClientResponse> response = baseClient.get().uri("/resource").header("token", "secret").exchange(); - 기존 인스턴스에서 새 클라이언트 인스턴스 생성
// metate() 는 builder state를 copy하여 새로운 것을 만든다 WebClient authClient = baseClient.mutate() .defaultHeaders(headers -> {headers.add("token", "secret");}) .build();
커넥션 풀링으로 인한 성능 향상은 원격 호스트에 의해 어떠한 API 호출이 갑자기 종료되었을 때 다른 API 호출에서 임의의 오류를 일으킬 것이라는 (기본 Reactor-netty)라이브러리의 가정으로 인해 크게 떨어질 수 있다. 어떤 경우에는 호출들이 모두 shared worker thread에서 만들어지기 때문에 어디서 에러가 발생했는지조차 알 수 없다.
따라서 제어할 수 없는 서버에서 외부 API를 호출하는 경우 WebClinet를 전혀 사용하지 않거나 pooling mechanism을 끈 상태에서 사용하는 게 좋다.
WebClient를 사용하지 않는다면 일반 HttpClient 또는 Apache Commons HttpClient를 사용할 수 있다.
** Creating a separate WebClient does not mean that WebClient will have a separate connection pool. Just look at the code for HttpClient.create - it calls HttpResources.get() to get the global resources. You could provide the pool settings manually but considering the errors that occur even with the default setup, I don't consider it worth the risk.
** 별도의 WebClient를 생성한다고 해서 별도의 커넥션 풀이 있는 건 아닙니다. HttpClient.create 코드를 보면, HttpResources.get()을 호출하여 전역 리소스를 가져온다. 풀 설정을 수동으로 제공할 수도 있지만 기본 설정에서도 발생하는 오류를 고려하면 그럴 가치가 없다고 생각한다.
- 참고
- https://newbedev.com/right-way-to-use-spring-webclient-in-multi-thread-environment
- https://www.baeldung.com/spring-webflux-concurrency
'Spring' 카테고리의 다른 글
| Webclient (0) | 2023.06.20 |
|---|---|
| Controller의 response 는 model 과 다른 필드를 가지거나 숨겨야 하는 경우가 있는데 어떻게 설계? (0) | 2021.12.22 |
| Spring Error Handling (0) | 2021.12.22 |
| Cacheable annotation (0) | 2021.12.22 |
| AOP (0) | 2021.12.22 |
- Total
- Today
- Yesterday
- effective-java
- sort algorithm
- 메모리 릭
- 코테 log
- 이펙티브자바
- Encoding
- fetchResults
- 암호화
- point
- ASCII
- Java
- WebClient
- annotation
- ruby
- SQL 전문가 가이드
- Git
- aws
- querydsl
- 사고..
- Lombok
- IntelliJ
- 실용주의
- gitignore
- Spring-Boot
- TroubleShooting
- DesignSystem
- Generic
- ActiveAdmin
- SHA
- 이벤트스토밍
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |