기록과 정리

RestTemplate vs WebClient 비교 본문

IT/Spring

RestTemplate vs WebClient 비교

zepetto 2022. 7. 14. 10:56

 

- 소스는 아래 주소에서 보실수 있습니다. 

https://github.com/zepetto7065/WebClientTest

 

GitHub - zepetto7065/WebClientTest: RestTemplate + WebClient Test 및 비교

RestTemplate + WebClient Test 및 비교. Contribute to zepetto7065/WebClientTest development by creating an account on GitHub.

github.com

Spring Webflux

  • Web on Reactive Stack Spring Framework 5.x 부터 지원되는 Reactive Programming Framework
  • 적은 수의 스레드로 동시성을 처리하고 , 논 블로킹 , Functional Programming

 

 

Web on Reactive Stack

The original web framework included in the Spring Framework, Spring Web MVC, was purpose-built for the Servlet API and Servlet containers. The reactive-stack web framework, Spring WebFlux, was added later in version 5.0. It is fully non-blocking, supports

docs.spring.io

Thread Pool

  • 서버의 스레드 풀에 가득차게 되면, 클라이언트는 Queue에 가득 쌓인다.
  • 멀티스레드 방식에서 스레드 변경시 context switching 비용 발생
  • Thread를 늘리는 것에도 한계 발생에 따라 비동기 방식의 프로그래밍 필요 발생

Overview

  • Spring 5에 소개된 reactice web client
  • web request를 수행하기 위한 엔트리 포인트를 나타내는 인터페이스
  • RestTemplate(Blcking) 과 달리 Non-Blocking 방식

RestTemplate

  • Blocking I/O 기반의 Synchronous API
  • 프로젝트에 spring-web 모듈이 있다면 RestTemplateBuilder를 빈으로 등록해준다.

WebClient

  • Non-Blocking I/O 기반의 Asynchronous API
  • RestTemplate 과 달리 Single Thread + Non-Blocking
    • 각 이벤트들은 Event Loop 내 Job으로 등록
  • 사용시에 spring-boot-starter-webflux 의존성 추가

 

RestTemplate vs WebClient 응답시간 비교

2개의 핸들러에 대해 RestTemplate과 WebClient 요청을 하여 응답시간을 비교해본다.

다른 스레드 sleep 시간을 가지는 핸들러
RestClientBuilder를 만들고

아까 만들어놓은 hello와 world로 RestTemplate 요청을 보내보도록 하자.

5+3 기대 결과인 8초의 시간이 걸렸다.
동일하게 아래에 webClientBuilder를 생성
mono객체 생성후, subscribe

여기서 주의할 것은 자바 메서드 내에서는 Blocking 방식으로 동작한다. 첫번째 helloMono 객체를 만들고 subscribe를 해당 핸들러에 요청을 하고 ( 요청을 하면 subscribe 에 대한 건 끝이난다 , '끝이난다' 라는건 해당 메서드에 대한 역할은 다했다는것 )  요청에 대한 처리가 callBack이 되면 (async) , 내부 로직 (non-blocking) 이 실행된다. 

그러한 이유로 메서드 내부 Main Thread는 계속 진행이되어 0.1s 의 RunningTime을 가지고 , Thread Sleep이 3초인 world가 늦게 호출했음에 불구하고 먼저 callback 함수인 anonymous function을 실행하는 것을 볼 수 있다.

 

 

느낀점

 

그럼 언제 사용해야할까?

가령 , 어떤 서비스 로직이 성공 실패 유무에 상관없이 메세지를 보내야하는 상황이라면 이러한 webclient 방식을 통해 다른 API 서버로의 요청을 하면 resource를 줄이는 방안이 될 듯 하다.

 

서비스마다 로직이 다르기 때문에 동기&비동기&blocking&non-blocking 상황에 어떻게 실행이 될지 생각해보고 쓰면 조금 더 좋은 성능을 가져올 수 있을 듯하다.

 

람다 캡처링

해당 소스를 보면 조금 놓친 점이 있는데(?) , WebClientRunner에서 worldStart 또는 monoStart는 람다 밖에서 선언된 '지역변수'임에 불구하고 사용이 가능하다.

보통 람다는 쓰레드끼리 공유가 가능한 인스턴스 변수만을 공유한다. ( 인스턴스 변수는 heap 에 저장됨 ), 지역변수는 다른 스레드에서 사용시에 값을 복사하여 사용하기 때문에 데이터 정합성을 보장하지 못한다. 

각각의 다른 스레드 사용중인 webClientRunner

( WebClientRunner는 Thread-14, worldMono.subscribe는 Thread-15,  helloMono.subscribe는 Thread-16)

그런데 왜 지역변수로 선언된 worldStart와 monoStart가 람다안에서 사용이 가능했을까?

worldStart를 람다 재정의 하는 순간 , 자바 컴파일러는 worldStart가 지역변수로 인식하는듯하다. 즉 재정의하기 전까지는 한번도 변하지 않했으니 전역변수라고 heap에 저장하는것 같다 ( 이것까지는 내 예상.. )

 

따라서.....따라서....'final' 의 중요성을 느낀다. 재정의를 할 생각이 없고 막아야한다면 반드시 명시적으로 final을 선언해주는것도 좋은 방법인 것 같다.