SpringBoot WebClient

dragonappear·2022년 2월 21일
3

Spring Webflux

목록 보기
1/1


출처

제목: Spring WebClient 쉽게 이해하기
작성자: tistory"happycloud-lee"
작성자 수정일: 2021년 2월 20일
링크: https://happycloud-lee.tistory.com/220
작성: 2022년 2월 21일

제목: Spring WebClient를 적용해보자, spring webClient 사용 방법
작성자: tistory.com(doinge-coding)
작성자 수정일: 2021년 11월 9일
링크: https://doinge-coding.tistory.com/entry/spring-webClient%EC%82%AC%EC%9A%A9%EB%B0%A9%EB%B2%95
작성: 2022년 2월 22일


제목: Spring WebClient 사용#2 (MVC+ WebClient 구조)
작성자: tistory.com(icthuman)
작성자 수정일: 2021년 12월 17일
링크: https://icthuman.tistory.com/entry/Spring-WebClient-%EC%82%AC%EC%9A%A9-2-MVC-WebClient-%EA%B5%AC%EC%A1%B0
작성: 2022년 2월 22일

참고


1. Spring WebClient 이해

공식 Docs: https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-client

Spring WebClient란?

우리가 개발하는 어플리케이션들을 크게 2개로 나눠보면 요청자와 제공자라고 할 수 있다.요청자를 consumer 또는 subscriber라고 하고, 제공자를 producer 또는 provider라고 한다.요청자는 제공자에게 무언가를 요청할 때 제공자가 공개한 API를 이용한다.

요청 시 프로그램에서 가장 흔하게 사용하는 것이 Http Client이다.

  • Spring WebClient는 웹으로 API를 호출하기 위해 사용되는 Http Client 모듈 중 하나이다.
    • Java에서 가장 많이 사용하는 Http Client는 RestTemplate이다.

그럼 RestTemplateWebClient는 어떤 공통점과 차이가 있을까?

  • 공통점은 둘 다 HttpClient 모듈이다
  • 차이점은 통신방법이 RestTemplate은 Blocking 방식이고, WebClient는 Non-Blocking 방식이다.

참고
동기(Synchronous)와 비동기(Asynchronous), 그리고 블럭(Blocking)과 넌블럭(Non-blocking) https://musma.github.io/2019/04/17/blocking-and-synchronous.html

결과적으로, Spring WebClient가 필요한 이유는 요청자와 제공자 사이의 통신을 좀 더 효율적인 Non-Blocking 방식으로 하기 위해서이다.

동작원리

Spring Webclient의 동작 원리를 이해하기 위해서는 RestTemplate의 동작원리를 같이 이해해야 한다.

RestTemplate

RestTemplate은 Multi-Thread와 Blocking 방식을 사용한다.

  • Thread Pool은 요청자 어플리케이션 구동 시에 미리 만들어 놓는다

  • Request는 먼저 Queue에 쌓이고 가용한 쓰레드가 있으면 그 쓰레드에 할당되어 처리된다.

    • 즉 1요청 당 1스레드가 할당된다
  • 각 쓰레드에서는 Blocking 방식으로 처리되어 응답이 올때까지 그 스레드는 다른 요청에 할당 될 수 없다.

  • 아래는 RestTemplateConnection Pool에 Spring Bean으로 등록하기 위한 예제이다.

    • 요청 당 20개의 RestTemplate client를 만들고, 최대 50개까지 증가할 수 있도록 설정하였다.
@Configuration
public class RestTemplateConfig {
	public RestTemplate getRestTemplate(int defaultMaxPerRoute, int maxTotal) {
		PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
		connManager.setDefaultMaxPerRoute(defaultMaxPerRoute);
		connManager.setMaxTotal(maxTotal);

		HttpClient client = HttpClientBuilder.create().setConnectionManager(connManager).build();

		HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(client);
		factory.setConnectTimeout(3000);
		factory.setReadTimeout(3000);

		return new RestTemplate(factory);

	}

	@Bean
	public RestTemplate coffeeRestTemplate() {
		return getRestTemplate(20, 50);
	}
}
  • 요청을 처리할 쓰레드가 있으면 아무런 문제가 없지만, 쓰레드가 다 차는 경우 이후의 요청은 Queue에 대기하게 된다.

  • 대부분의 문제는 네트워킹이나 DB와의 통신에서 생기는데 이런 문제가 여러 쓰레드에서 발생하면 가용한 쓰레드수가 현저하게 줄어들게 되고, 결국 전체 서비스는 매우 느려지게 된다.

Spring WebClient

  • Spring WebClient 는 Single ThreadNon-Blocking 방식을 사용한다
    • Core 당 1개의 Thread를 이용한다


그림출처: https://luminousmen.com/post/asynchronous-programming-blocking-and-non-blocking

  • 각 요청은 Event Loop 내에 Job으로 등록이 된다.

  • Event Loop는 각 Job을 제공자에게 요청한 후, 결과를 기다리지 않고 다른 Job을 처리한다.

  • WebClient는 이렇게 이벤트에 반응형으로 동작하게 설계되었다.

    • 그래서 반응성, 탄력성, 가용성, 비동기성을 보장하는 Spring React 프레임워크를 사용한다.
    • 또한, React Web 프레임워크인 Spring WebFlux 에서 Http Client로 사용된다.

성능 비교


그림출처: https://alwayspr.tistory.com/44

  • 1000명까지는 비슷하지만 동시사용자가 늘수록 RestTemplate은 급격하게 느려지는것을 볼 수 있다.
  • Spring 커뮤니티에서는 RestTemplate을 이미 Depreciated시키고 WebClient를 사용할것을 강력히 권고하고 있다.

2. Spring WebClient 사용

가장 기본적인 WebClient 사용예제를 만들어보자

서버 생성

프로젝트 생성

application.yaml 작성

server.port: 5011
spring:
  application:
    name: webserver

API class 작성

@Slf4j
@RestController
public class WebserverController {
    @GetMapping("/webclient/{param}")
    public String testWebClient(
            @PathVariable String param,
            @RequestHeader HttpHeaders headers,
            @CookieValue(name = "httpclient-type", required=false, defaultValue="undefined") String httpClientType) {

        log.info(">>>> Cookie 'httpclient-type={}'", httpClientType);

        headers.forEach((key, value) -> {
            log.info(String.format(">>>>> Header '%s' => %s", key, value));
        });

        log.info("### Received: /webclient/" + param);

        String msg = param + " => Working successfully !!!";
        log.info("### Sent: " + msg);
        return msg;
    }
}

실행 및 테스트

http://localhost:5011/webclient/hi

WebClient 생성

프로젝트 생성

application.yaml 설정

server:
  port: ${service_port:5001}
spring:
  application:
    name: webclient

API 작성

@RestController
public class WebClientController {

    @GetMapping("/test")
    public Mono<String> doTest() {
        WebClient client = WebClient.create();
        return client.get()
                .uri("http://localhost:5011/webclient/test-create")
                .retrieve()
                .bodyToMono(String.class);
    }
}

실행 및 테스트
http://localhost:5001/test


3. 사용법

생성

WebClient client = WebClient.create();

Base Url 지정

아래 방법을 사용하여 각 업스트림 서비스에 대한 공통 WebClient를 만들수 있다.

@Bean
    public WebClient webClient() {
        return WebClient.create("localhost:5011");
    }

이러한 공통 인스턴스를 어디에서나 사용하여 기본 URL에서 특정 리소스 실행 가능

WebClient.ResponseSpec responseSpec = client.get()
                .uri("/users/" + userId)
                .retrieve();

builder 이용 방법

WebClient webClient = WebClient.builder()
                .baseUrl("localhost:5011")
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .build();

Get 요청

Post 요청

Patch 요청

Delete 요청

타임아웃 설정 방법


테스트

5011 포트

@Slf4j
@RestController
public class WebserverController {

    @GetMapping("/slow-service-strings")
    private List<String> getAllTweets() throws InterruptedException {
        Thread.sleep(2000L); // delay
        return Arrays.asList(
                "RestTemplate rules",
                "WebClient is better",
               "OK, both are useful");
    }
}

5001 포트

@Slf4j
@RestController
public class WebClientController {
    private static final String uri = "http://localhost:5011/slow-service-strings";

    @GetMapping("/strings-blocking")
    public List<String> getTweetsBlocking() {
        log.info("Starting BLOCKING Controller!");


        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<List<String>> response = restTemplate.exchange(
                uri, HttpMethod.GET, null,
                new ParameterizedTypeReference<List<String>>(){});

        List<String> result = response.getBody();
        result.forEach(s -> log.info(s));
        log.info("Exiting BLOCKING Controller!");
        return result;
    }

    @GetMapping(value = "/strings-non-blocking")
    public Flux<String> getTweetsNonBlocking() {

        log.info("Starting NON-BLOCKING Controller!");
        Flux<String> stringFlux = WebClient.create()
                .get()
                .uri(uri)
                .retrieve()
                .bodyToFlux(String.class);

        stringFlux.subscribe(s -> log.info(s));
        log.info("Exiting NON-BLOCKING Controller!");
        return stringFlux;
    }
}

결과

//Blocking
Starting BLOCKING Controller!
RestTemplate rules
WebClient is better
OK, both are useful
Exiting BLOCKING Controller!

//Non-blocking
Starting NON-BLOCKING Controller!
Exiting NON-BLOCKING Controller!
["RestTemplate rules","WebClient is better","OK, both are useful"]

0개의 댓글