Spring Webflux & WebClient

Solf·2024년 5월 18일

WEB

목록 보기
4/11

Spring에서 두 개의 프로젝트간 API 통신을 하려는 과정에서 Spring에서 제공하는 WebClient를 사용할 일이 생겨 정리해보려고 한다.

작동 방식과 디자인에 대해서 단계별로 알아보자.

(Non-)Blocking & (A)Synchronouse

일단 들어가기 전에 사전지식으로 동기와 비동기, 블로킹과 논블로킹 방식에 대해 알아야한다.

Blocking vs Non-Blocking은 다른 주체가 작업을 할때 제어권이 있는지 없는지로 판단한다.

  • Blocking : 자신의 작업을 진행하다 다른 작업을 호출하면 다른 작업이 끝날때까지 대기하는 방식(제어권도 함께 전달)
  • Non-Blocking : 자신을 작업하다 다른 작업을 호출하되 그것에 관련 없이 자신의 작업을 하는 방식(제어권이 여전히 자신에게 있음)

Sync vs Async는 호출된 함수의 종료를 신경쓰는 주체가 누구냐에 따라 갈린다.

  • Sync(동기) : 호출하는 함수가 작업 호출된 함수의 종료를 직접 확인하거나 return을 기다린다.
  • Async(비동기) : 호출된 함수가 작업 끝났음을 callback 함수로 전달한다.

보면 알겠지만 Sync-NonBlocking이나 Async-Blocking은 다소 비효율적으로 보인다. 폴링방식은 설계상 어쩔 수 없이 채택하기도 하지만, Async-Blocking은 쓸 이유가 없다.

Spring MVC vs Spring Webflux


스프링에서 위 두 스택중 하나를 선택하여 섞지 않고 적용해야 각 디자인이 가지는 장점을 확실하게 가져갈 수 있다.

Spring MVC

Spring MVC 경우 Multi-Thread + Blocking 방식을 사용한다.
클라이언트의 요청당 서버는 Thread pool에서 남는 Tread를 하나씩 할당하고 부족하면 큐에서 대기 시킨다.
이때 Tread의 개수와 컴퓨터 성능의 적절한 비율이 맞아야 높은 효율을 가질 수 있다.

Spring Webflux의 경우 Single-Thread + Non-Blocking 방식을 사용한다.
원시적으로 spring은 멀티 스레드로 병렬처리를 했으나, 위와 같이 아무리 컴퓨터 성능이 좋아도 스레드 개수의 제한에 의해 한계가 있었다.
이를 좀 더 효율적으로 사용하고자 Reactive Programming을 적용한 것이다.(자바 라이브러리로 Reactors나 RxJava같은 것이 있다.) Non-Blocking 방식이기에 하나의 스레드를 점유하지 않고 동시에 많은 작업을 사용할 수 있다.
이는 성능이 무조건 좋아진다는 이야기는 아니다. '고가용성'을 보장해준다는 이야기이다. (오히려 어플리케이션의 성능 자체는 반대로 나빠질 수도 있으나, 작은 고정된 수의 스레드와 적은 메모리에서도 최대한의 효율을 내면서 확장 가능하다.)
대규모 서비스에 그만큼 적합한데 배민에서 적용한 사례도 찾아볼 수 있었다. https://techblog.woowahan.com/12903/

Reactive Programming이 뭔데?

리액티브 프로그래밍은 데이터 스트림과 변화의 전파에 관련된 선언적 프로그래밍 패러다임이다. 밑의 코드로 예시를 보자.

// 명령형 ex1
변수 b = 1
변수 c = 2
변수 a = b + c
b = 10
console.log(a) // 3 ("="은 반응 할당 연산자가 아니기 때문에 12가 아님)
// ex2
	@Test
    @DisplayName("명령형 프로그래밍")
    void 명령형_예제()
    {
        String name = "Craig";
        String capitalName = name.toUpperCase();
        String greeting = "Hello, " + capitalName + "!";
        System.out.println(greeting);
    }

// 선언형
// 이제 명시적으로 초기화될 때뿐만 아니라 참조된 변수(연산자의 오른쪽)가 변경될 때 변수의 값을 변경하는 특수 연산자 "$="가 있다고 상상해 보세요.
변수 b = 1
변수 c = 2
var a $= b + c
b = 10
console.log(a) // 12
//ex2
	@Test
    @DisplayName("선언형 프로그래밍")
    void 선언형_예제()
    {
        Stream.of("Crag")
                .map(String::toUpperCase)
                .map(cn -> "Hello, " + cn + "!")
                .forEach(System.out::println);
    }

구체적으로 실행 방법을 명시하는 명령형 프로그래밍(how to do)과 달리 목표만을 정의하는 선언적 프로그래밍 방식(what to do)을 지원한다. 자바의 스트림이 일종의 선언적 프로그래밍이라고 볼 수 있다. 이처럼 데이터 스트림을 선언해두고 데이터가 변화(이벤트)하면 이걸 전파만 해서 자동으로 업데이트한다.

핵심원칙

  • 비동기성(Asynchrony)
    리액티브 시스템은 이벤트 또는 데이터 스트림을 비동기적으로 처리
    이를 통해 다른 작업을 동시에 수행하거나 블로킹을 피할 수 있다.
  • 반응성(Responsiveness)
    리액티브 시스템은 실시간으로 데이터의 변화에 반응
    사용자 요청이나 외부 이벤트에 빠르게 응답할 수 있다.
  • 탄력성(Elasticity)
    리액티브 시스템은 부하나 실패에 유연하게 대응할 수 있다.
    시스템의 자원을 동적으로 조절하여 확장성과 견고성을 제공한다.
  • 메시지 기반(Messaging)
    리액티브 시스템은 메시지 기반 아키텍처를 기반으로 동작한다.
    컴포넌트 간에 비동기적으로 메시지를 교환하여 상호작용한다.

공식문서에서의 비교

사실 두 구조나 설정은 대부분 동일하다. 그러나 MVC에서는 어플리케이션이 스레드를 차단할 수 있다면, 넓은 스레드 풀을 사용하게 되며, WebFluxs 스레드를 차단하지 않아, 적은 스레드풀을 활용해 request들을 처리한다.

Spring Webflux

Spring Webflux는 JVM 위에서 리엑티브 프로그래밍을 가능하게 해주는 라이브러리 Reactor를 활용하고 있다.
Webflux는 데이터 스트림을 두가지 타입으로 정의하는데 아래와 같다.

  • Mono: 0개 또는 하나의 결과 처리를 하기 위한 Reactor객체
  • Flux: 0개 또는 여러개의 결과를 처리하는 Reactor객체

Spring Webclient

WebClient : Spring WebFlux에서 HTTP Client로 사용되는 비동기적으로 작동하는 모듈이다.
Java에서 많이 쓰던 Http Client는 RestTemplate이다. 그러나 Spring은 5.0버전부터 해당 WebClient사용을 강력히 권고하고 있다.

여기서 RestTemplate가 Multi-Thread + Blocking 방식이며, WebClient가 Single-Thread + Non-Blocking방식으로 해석할 수 있다.

심지어 WebClients는 블로킹으로도 사용가능해서 앞으로 api통신에 이를 활용할 것이다.

참고

https://velog.io/@rnqhstlr2297/Spring-Webflux와-WebClient
https://gngsn.tistory.com/154
https://taes-k.github.io/2019/05/21/about-spring-reactive/
https://velog.io/@salgu1998/Reactive-Programming리액티브-프로그래밍-이란
https://velog.io/@suhongkim98/명령형-프로그래밍-선언형-프로그래밍-리액티브-프로그래밍-리액티브-시스템..-1
https://devocean.sk.com/blog/techBoardDetail.do?ID=165099&boardType=techBlog

profile
CS/Software Engineer

0개의 댓글