web/SpringBoot

[WebClient] Block vs Subscribe vs Tuple

뽀리님 2023. 12. 4. 16:15

WebClient 를 적용하다보니 궁금한점이 생겨서 정리하는 블로깅

 

API URL

https://jsonplaceholder.typicode.com/todos

https://jsonplaceholder.typicode.com/comments

 

 API 2개를 각각 3가지 방식으로 다르게 호출하고 시간을 측정한 테스트 로직이다.

 

일단 소스부터 투척

@Test
public void webClient_block() {
    long start = System.currentTimeMillis();
    String monoResult1 = httpWebClient.httpRequestGet("/todos", null).block();
    String monoResult2 = httpWebClient.httpRequestGet("/comments", null).block();
    long end = System.currentTimeMillis();
    System.out.println("block Elapsed Time: " + (end - start) + "ms");
}

@Test
public void webClient_nonBlock_await() throws InterruptedException {
    long start = System.currentTimeMillis();
    CountDownLatch cdl = new CountDownLatch(2);
    JSONObject jsonObject1 = new JSONObject();
    JSONObject jsonObject2 = new JSONObject();
    httpWebClient.httpRequestGet("/todos", null)
            .doOnSuccess(e -> {
                jsonObject1.put("total_result", e);
                cdl.countDown(); // 성공적으로 완료되었을 때만 countDown
            })
            .doOnError(error -> {
                // 에러 처리
                System.out.println("Error:"+error.getMessage());
                cdl.countDown(); // 에러 발생 시에도 countDown
            })
            .subscribe();

    httpWebClient.httpRequestGet("/comments", null)
            .doOnSuccess(e -> {
                jsonObject2.put("total_result", e);
                cdl.countDown(); // 성공적으로 완료되었을 때만 countDown
            })
            .doOnError(error -> {
                // 에러 처리
                System.out.println("Error:"+error.getMessage());
                cdl.countDown(); // 에러 발생 시에도 countDown
            })
            .subscribe();
    cdl.await();
    long end = System.currentTimeMillis();
    System.out.println("await Elapsed Time: " + (end - start) + "ms");
}

@Test
public void webClient_nonBlock_tuple() {
    long start = System.currentTimeMillis();
    Mono<String> result1 = httpWebClient.httpRequestGet("/todos", null);
    result1.subscribeOn(Schedulers.boundedElastic());

    Mono<String> result2 = httpWebClient.httpRequestGet("/comments", null);
    result2.subscribeOn(Schedulers.boundedElastic());

    Tuple2<String,String> tuple = Mono.zip(result1,result2).block();
    long end = System.currentTimeMillis();
    System.out.println("tuple Elapsed Time: " + (end - start) + "ms");
}

 

 

 

 

1. 블로킹

webClient_block () : 차례대로 호출한 결과

 

 

2. 논블로킹

webClient_nonBlock_await - CountDownLatch를 이용하여 호출

 

CountDownLatch는 언제 쓸까? 

쓰레드를 N개 실행했을 때, 일정 개수의 쓰레드가 모두 끝날 때 까지 기다려야지만 다음으로 진행할 수 있거나 다른 쓰레드를 실행시킬 수 있는 경우 사용한다. 예를들어서 리스트에 어떤 자료구조가 있고, 각 자료구조를 병렬로 처리한 후 배치(batch)로 데이터베이스를 업데이트 한다거나 다른 시스템으로 push하는 경우가 있다.

CountDownLatch의 어떤점이 이를 가능하게 하는가?

CountDownLatch 초기화 정수값 count 넣어준다. 쓰레드는 마지막에서 countDown() 메서드를 불러준다. 그러면 초기화 넣어준 정수값이 하나 내려간다. 쓰레드는 마지막에서 자신이 실행완료했음을 countDown 메서드로 알려준다. 쓰레드들이 끝나기를 기다리는 입장에서는 await()메서드를 불러준다. 그러면 현재 메서드가 실행중이 메인쓰레드는 더이상 진행하지않고 CountDownLatch count 0 때까지 기다린다. 0이라는 정수값이 게이트(Latch) 역할을 한다. 카운트다운이 되면 게이트(latch) 열리는 것이다.

 

즉 한마디로 말해 동시성을 제어하기위해 쓰는거라고 보면된다. 갯수를 정해놓고 그 갯수가  끝날때까지  await. 기다린다.

주로, 이벤트성이나 선착순 프로모션으로 n명이 정해져있는 상태에서 동시성을 체크할때 쓰이는듯 하다.

 

webClient_nonBlock_tuple - Tuple 를 이용하여 호출

비동기 작업을 병렬로 처리하면서, Mono.zip 및 block을 사용하여 최종 결과를 동기적으로 가져온다.

각각의 Mono가 별도의 스레드에서 실행되므로, 병렬성을 활용할 수 있수있고, 동기적으로 결과가 필요한 경우 쓰면된다.

 

 

✔ 3개다 호출했을때 총 걸리는 호출시간은

block Elapsed Time: 1705ms
await Elapsed Time: 1312ms
tuple Elapsed Time: 1105ms

 

 

튜플로 병렬처리후 동기적으로 가져왔을때가 젤 빠르다!

상황에 맞춰서 쓰도록 하자.

 

 

 

 

참고

https://techblog.woowahan.com/10795/

https://findmypiece.tistory.com/276