1. 개요

일반적으로 애플리케이션에서 HTTP 요청을 할 때 이러한 호출을 순차적으로 실행합니다. 그러나 이러한 요청을 동시에 수행하려는 경우가 있습니다.

예를 들어, 여러 소스에서 데이터를 검색하거나 단순히 애플리케이션의 성능을 향상시키려는 경우에 이 작업을 수행할 수 있습니다.

이 빠른 사용방법(예제)에서는 Spring 반응형 WebClient 를 사용하여 병렬 서비스 호출을 수행하여 이를 수행하는 방법을 알아보기 위해 몇 가지 접근 방식을 살펴보겠습니다 .

2. 리액티브 프로그래밍 요약

간단히 요약하자면 WebClient 는 Spring 5에 도입되었으며 Spring Web Reactive 모듈의 일부로 포함되었습니다. HTTP 요청을 보내기 위한 반응형 비차단 인터페이스를 제공합니다 .

WebFlux를 사용한 반응형 프로그래밍에 대한 심층 사용방법(예제)는 훌륭한 Spring 5 WebFlux 사용방법(예제)를 확인하십시오 .

3. 간단한 사용자 서비스

예제 에서는 간단한 사용자 API를 사용할 것입니다. 이 API에는 ID를 매개변수로 사용하여 사용자를 검색하기 위한 getUser 메소드 하나를 노출하는 GET 메소드가 있습니다.

주어진 ID에 대한 사용자를 검색하기 위해 단일 호출을 수행하는 방법을 살펴보겠습니다.

WebClient webClient = WebClient.create("http://localhost:8080");
public Mono<User> getUser(int id) {
    LOG.info(String.format("Calling getUser(%d)", id));

    return webClient.get()
        .uri("/user/{id}", id)
        .retrieve()
        .bodyToMono(User.class);
}

다음 섹션에서는 이 메서드를 동시에 호출하는 방법을 알아봅니다.

4. 동시 WebClient 호출하기

이 섹션에서는 getUser 메소드를 동시에 호출하는 몇 가지 예를 볼 것 입니다. 또한 예제에서 게시자 구현 Flux Mono 를 모두 살펴보겠습니다 .

4.1. 동일한 서비스에 대한 다중 호출

이제 5명의 사용자에 대한 데이터를 동시에 가져오고 그 결과를 사용자 List으로 반환한다고 가정해 보겠습니다 .

public Flux fetchUsers(List userIds) {
    return Flux.fromIterable(userIds)
        .flatMap(this::getUser);
}

수행한 작업을 이해하기 위해 단계를 분해해 보겠습니다.

정적 fromIterable 메서드를 사용하여 userId List 에서 Flux를 생성하는 것으로 시작합니다 .

다음으로 flatMap 을 호출 하여 이전에 생성한 getUser 메서드를 실행합니다. 이 반응 연산자는 기본적으로 동시성 수준이 256입니다. 즉, 최대 256개의 getUser 호출을 동시에 실행합니다. 이 숫자는 flatMap 의 오버로드된 버전을 사용하여 메소드 매개변수를 통해 구성할 수 있습니다.

작업이 병렬로 발생하기 때문에 결과 순서를 알 수 없다는 점은 주목할 가치가 있습니다. 입력 순서를 유지해야 하는 경우 flatMapSequential 연산자를 대신 사용할 수 있습니다.

Spring WebClient는 내부적으로 비차단 HTTP 클라이언트를 사용하므로 사용자가 스케줄러를 정의할 필요가 없습니다. WebClient 는 호출을 예약하고 결과를 차단하지 않고 내부적으로 적절한 스레드에 게시합니다.

4.2. 동일한 유형을 반환하는 다른 서비스에 대한 다중 호출

이제 여러 서비스를 동시에 호출하는 방법을 살펴보겠습니다 .

이 예에서는 동일한 사용자 유형을 반환하는 다른 엔드포인트를 생성합니다.

public Mono<User> getOtherUser(int id) {
    return webClient.get()
        .uri("/otheruser/{id}", id)
        .retrieve()
        .bodyToMono(User.class);
}

이제 두 개 이상의 호출을 병렬로 수행하는 방법은 다음과 같습니다.

public Flux fetchUserAndOtherUser(int id) {
    return Flux.merge(getUser(id), getOtherUser(id));
}

이 예제의 주요 차이점은 fromIterable 메서드 대신 정적 메서드 병합 을 사용했다는 것 입니다. 병합 방법을 사용하여 둘 이상의 Flux 를 하나의 결과로 결합할 수 있습니다.

4.3. 다양한 서비스 다양한 유형에 대한 다중 호출

동일한 것을 반환하는 두 서비스가 있을 확률은 다소 낮습니다. 보다 일반적으로 우리는 다른 응답 유형을 제공하는 또 다른 서비스를 갖게 되며 우리의 목표는 두 개(또는 그 이상)의 응답을 병합하는 것입니다 .

Mono 클래스는 두 개 이상의 결과를 결합할 수 있는 정적 zip 메서드를 제공합니다.

public Mono fetchUserAndItem(int userId, int itemId) {
    Mono user = getUser(userId);
    Mono item = getItem(itemId);

    return Mono.zip(user, item, UserWithItem::new);
}

zip 메서드는 주어진 사용자 항목 Mono UserWithItem 유형 의 새로운  Mono 로 결합 합니다. 이것은 사용자와 항목을 래핑하는 간단한 POJO 개체입니다.

5. 테스트

이 섹션에서는 이미 본 코드를 테스트하는 방법, 특히 서비스 호출이 병렬로 발생하는지 확인하는 방법을 살펴보겠습니다.

이를 위해 Wiremock 을 사용하여 모의 서버를 만들고 fetchUsers 메서드를 테스트합니다.

@Test
public void givenClient_whenFetchingUsers_thenExecutionTimeIsLessThanDouble() {
        
    int requestsNumber = 5;
    int singleRequestTime = 1000;

    for (int i = 1; i <= requestsNumber; i++) {
        stubFor(get(urlEqualTo("/user/" + i)).willReturn(aResponse().withFixedDelay(singleRequestTime)
            .withStatus(200)
            .withHeader("Content-Type", "application/json")
            .withBody(String.format("{ \"id\": %d }", i))));
    }

    List<Integer> userIds = IntStream.rangeClosed(1, requestsNumber)
        .boxed()
        .collect(Collectors.toList());

    Client client = new Client("http://localhost:8089");

    long start = System.currentTimeMillis();
    List<User> users = client.fetchUsers(userIds).collectList().block();
    long end = System.currentTimeMillis();

    long totalExecutionTime = end - start;

    assertEquals("Unexpected number of users", requestsNumber, users.size());
    assertTrue("Execution time is too big", 2 * singleRequestTime > totalExecutionTime);
}

이 예에서 우리가 취한 접근 방식은 사용자 서비스를 조롱하고 모든 요청에 ​​1초 안에 응답하도록 하는 것입니다. 이제 WebClient 를 사용하여 5번 호출 하면 호출이 동시에 발생하므로 2초 이상 걸리지 않을 것이라고 가정할 수 있습니다 .

WebClient 를 테스트하기 위한 다른 기술에 대해 알아보려면 Spring에서 WebClient 모의하기 사용방법(예제)를 확인하세요 .

6. 결론

이 예제에서는 Spring 5 Reactive WebClient를 사용하여 HTTP 서비스 호출을 동시에 수행할 수 있는 몇 가지 방법을 살펴보았습니다.

먼저 동일한 서비스를 병렬로 호출하는 방법을 보여 주었습니다. 나중에 서로 다른 유형을 반환하는 두 서비스를 호출하는 방법의 예를 보았습니다. 그런 다음 모의 서버를 사용하여 이 코드를 테스트하는 방법을 보여주었습니다.

항상 그렇듯이 이 기사의 소스 코드는  GitHub에서 사용할 수 있습니다.

HTTPClient footer