Java

[Http Client] RestTemplate에서 RestClient 적용으로 최적화

ride-dev 2024. 1. 19. 02:05
Http Client의 종류는 HttpURLConnection, Apache HttpClient, RestTemplate, OpenFeign, WebClient, RestClient 등으로 다양합니다.
각 HttpClient의 기본 구현에 대해 궁금하다면 아래 게시글을 참조해주세요.
[HTTP Client] 개요
[HTTP Client] 예제

- 2024-06-19 추가

프로젝트를 진행하던 중,

외부 api와 연결하는 것이 필요하여 Http Client 중 RestTemplate 을 적용했습니다.

최적화하지 않은 상태에서 시간을 측정해본 결과,

평균적으로 100밀리초가 소요되는 것을 확인했습니다.

이를 단축하기 위한 방법을 찾던 중,

RestTemplate의 소요 시간을 단축할 수 있는 Http Client인 WebClient와 RestClient를 발견했습니다.

 

RestTemplate을 사용한 프로젝트의 소요 시간이 상대적으로 긴 이유는,

프로젝트에 RestTemplate을 사용했고,

RestTemplate이 동기적인 방식으로만 동작하기 때문입니다.

그렇기 때문에 요청을 보낸 후 현재 스레드가 응답을 받을 때까지 block이 됩니다.

 

또한, 블로킹 I/O 기반 서버를 사용했기 때문입니다.

Tomcat과 같은 쓰레드 풀 기반 서버는 요청마다 새로운 스레드를 할당하기 때문에,
요청을 보내는 스레드는 응답이 끝날 때까지 block 상태가 됩니다.

 

 위 두 이유로 대기 시간이 증가한 것이었습니다.

 

 RestTemplate과 달리 WebClient와 RestClient는 비동기 방식을 지원하며,

WebClient와 RestClient는 기존의 RestTemplate기능을 사용할 수 있습니다.

응답을 기다리는 동안 다른 작업을 진행할 수 있으며, 여러 요청을 동시에 할 수 있습니다.

 

 단, 블로킹 I/O 기반 서버에서는 RestTemplate나 WebClient,

그리고 RestClient를 사용하든 크게 차이가 없습니다.

(많은 동시 요청을 처리할 경우에는 유의미한 차이를 보일 수 있습니다)

 

논블로킹 I/O 기반 서버에서

적은 수의 스레드로 이벤트 루프 방식으로 동작하므로,
하나의 스레드가 블로킹되면

다른 요청을 처리할 수 없어 전체 성능이 저하됩니다.

 

WebClient, RestClient를 사용하여 비동기 처리를 하면
큰 차이를 확인할 수 있습니다.

(예: WebFlux, Netty 기반 서버)

 

RestClient는 셋 중에서 가장 최신인 Http Client입니다.

RestClient는 WebClient와 비슷한 Api인 것과 더불어
RestTemplate객체를 매개변수로 받아,
RestTemplate의 인프라(메시지 컨버터, 인터셉터 등)를 사용할 수 있습니다.

/**
 * Obtain a {@code RestClient} builder based on the configuration of the
 * given {@code RestTemplate}. The returned builder is configured with the
 * template's
 * <ul>
 * <li>{@link RestTemplate#getRequestFactory() ClientHttpRequestFactory},</li>
 * <li>{@link RestTemplate#getMessageConverters() HttpMessageConverters},</li>
 * <li>{@link RestTemplate#getInterceptors() ClientHttpRequestInterceptors},</li>
 * <li>{@link RestTemplate#getClientHttpRequestInitializers() ClientHttpRequestInitializers},</li>
 * <li>{@link RestTemplate#getUriTemplateHandler() UriBuilderFactory}, and</li>
 * <li>{@linkplain RestTemplate#getErrorHandler() error handler}.</li>
 * </ul>
 * @param restTemplate the rest template to base the returned builder's
 * configuration on
 * @return a {@code RestClient} builder initialized with {@code restTemplate}'s
 * configuration
 */
static RestClient.Builder builder(RestTemplate restTemplate) {
    return new DefaultRestClientBuilder(restTemplate);
}

이미 RestTemplate을 구현한 상태에서, 비동기 처리 등의 기능을 구현하고자 하나,

WebClient 등의 HttpClient를 구현하기 복잡할 경우,

RestClient를 사용하는 것은 좋은 선택입니다.

저는 간단한 기능을 구현하기 때문에 RestTemplate을 이식하는 것이 아니라 RestClient를 구현했습니다.

출처: https://spring.io/blog/2023/07/13/new-in-spring-6-1-restclient
출처: https://spring.io/blog/2023/07/13/new-in-spring-6-1-restclient

RestClient와 WebClient의 차이는 의존성에 있습니다.

WebClient를 사용하려면 webflux dependency가 필요하지만,

RestClient 사용 시 webflux dependency를 필요로 하지 않습니다.

 

이를 구현해본 결과,

동일한 리소스에 대해 평균적으로 40밀리초가 소요되는 것을 확인했습니다.

 

시간을 단축할 수 있었던 것 뿐만 아니라 인터페이스를 활용하여

api호출을 보다 쉽게 사용할 수 있으며, 이해하기 쉽도록 할 수 있었습니다.

구현 과정은 아래와 같습니다.

1. 기본적인 사용법

builder().build() 또는 create() 로 스프링 빈을 정의합니다.

@Configuration
public class RestClientConfig {

    /**
     * RestClient 빈 정의 방식 1.
     * 이 메서드는 API와 통신하기 위해 RestClient 객체를 생성합니다.
     *
     * @return 설정된 RestClient 객체
     */
    @Bean
    public RestClient restClient() {
        return RestClient.builder()
                .build();
    }
        /**
     * RestClient 빈 정의 방식 2.
     * 이 메서드는 API와 통신하기 위해 RestClient 객체를 생성합니다.
     *
     * @return 설정된 RestClient 객체
     */
    @Bean
    public RestClient restClient() {
        return RestClient.create();
    }
}

RestClient 객체를 살펴보면 두 방식의 차이가 거의 없는 것을 볼 수 있습니다.

static RestClient.Builder builder() {
    return new DefaultRestClientBuilder();
}
static RestClient create() {
    return new DefaultRestClientBuilder().build();
}

 

 

2. HttpServiceProxyFactory(HttpInterface)

HttpInterface인 HttpServiceProxyFactory를 활용하여 RestClient를 OpenFeign처럼 선언형으로 사용할 수 있습니다.

 

먼저 CustomHttpInterface 인터페이스에 메서드를 정의하고

public interfaceCustomHttpInterface{
    @GetExchange("areaCode.do?out=json&code={apiKey}")
    String getSido(@PathVariable String apiKey);
}

API 스펙에 따라 반환 타입을 String이 아니라 DTO로 설정할 수도 있습니다.

 

이를 기반으로 HttpInterface Bean을 생성합니다.

@Bean
public CustomHttpInterface customHttpInterface() {
    RestClient client = RestClient.builder()
    // HttpClient에 필요로하는 설정 입력
      	    .baseUrl(BASE_URL)
            .build();
    RestClientAdapter adapter = RestClientAdapter.create(client);
    return HttpServiceProxyFactory
            .builderFor(adapter)
            .build()
            .createClient(CustomHttpInterface.class);
}

이후 HttpInterface인 HttpServiceProxyFactory에 연결합니다.

 

이제 이를 주입받아서 사용할 수 있습니다.

private final CustomHttpInterface httpInterface;

 

메서드를 매개변수와 함께 선언형으로 사용할 수 있습니다.

httpInterface.getSido(properties.getApiKey())

참조

https://spring.io/blog/2023/07/13/new-in-spring-6-1-restclient

 

New in Spring 6.1: RestClient

Spring Framework 6.1 M2 introduces the RestClient, a new synchronous HTTP client. As the name suggests, RestClient offers the fluent API of WebClient with the infrastructure of RestTemplate. Fourteen years ago, when RestTemplate was introduced in Spring Fr

spring.io

https://docs.spring.io/spring-framework/reference/integration/rest-clients.html#rest-restclient

 

REST Clients :: Spring Framework

WebClient is a non-blocking, reactive client to perform HTTP requests. It was introduced in 5.0 and offers an alternative to the RestTemplate, with support for synchronous, asynchronous, and streaming scenarios. WebClient supports the following: Non-blocki

docs.spring.io

728x90
반응형