Development Memories/SPRING

【Spring】WebFlux란 무엇인가?

친절한올드보이 2021. 4. 17. 18:32
반응형

2017년 8월에 릴리즈되어 Spring5에 새롭게 추가된 Client, Server에서 reactive 스타일의 어플리케이션 개발을 도와주는 모듈 입니다.

Spring framework는 Servlet API와 Sevlet 컨테이너로 이루어져 있는데, Spring5에서 WebFlux가 추가 되었습니다. 

WebFlux는 reactive-stack web framework이며 non-blocking에 reactive stream을 지원 합니다. 

 

WebFlux가 생긴 이유를 간략히 보면....(인터넷에 공유되고 있는 내용을 짜집기 하였습니다.)

  1. 적은 양의 스레드와 최소한의 하드웨어 자원으로 동시성을 핸들링하기 위해 만들어졌다.
  2. 서블릿 3.1이 논블로킹을 일부분만 지원하기 때문에 만들어지게 되었다.
  3. Tomcat이 아닌 netty와 같은 async, non-blocking 서버를 사용한다.
  4. Java5에서 Rest controllers나 unit test가 만들어지고, Java8에서는 함수형 API를 위한 Lambda 표현식이 추가 되었는데, 이는 논블로킹 어플리케이션 API의 토대가 되었다.

 

Reactive라는 건 무엇을 의미하는 것일까?

 

'Reactive'라는 용어는 I/O 이벤트에 반응하는 네트워크 컴포넌트, 마우스 이벤트에 반응하는 UI 컨트롤러 등과 같은 변화에 대한 반응을 중심으로 구축된 프로그래밍 모델을 나타낸다. 따라서 non-blocking은 blocking과 다르게 작업이 완료되거나 데이터를 사용가능한 상태가 되면 알림에 반응하는 방식이 있으므로 reactive하다고 말 할 수 있다.

 

'non-blocking back pressure' 또한 'reactive'와 연관되는 중요한 메커니즘이다. 동기식 명령형 코드에서 blocking call은 호출자가 대기하기 위한 back pressure 역할을 한다. non-blocking 코드에서는 빠른 생산자가 목적지를 압도하지 않도록 이벤트 속도를 제어하는 것이 중요하다.

 

Reactive Streams는 back pressure가 있는 비동기 컴포넌트간의 상호 작용을 정의하는 소규모 사양이다. (이는 Java 9에서도 채택되었다.) 예를 들어 (Publisher) 데이터 레파지토리는 (Subscriber) HTTP 서버가 응답에 사용할 데이터를 생성할 수 있다. Reactive Streams의 주된 목적은 Subscriber가 Publisher의 데이터 생성 속도(빠르게/느리게)를 제어 할 수 있도록 하는 것이다.

 

결국 Spring이 가지지 못했던 비동기 프로그래밍을 보완하기 위한 Spring5에 webflux 모듈을 사용한 것으로 보입니다.

 


 

아래 그래프는 DZone에서 Spring WebFlux와 Spring MVC의 퍼포먼스를 테스트한 결과 라고 합니다.

 

그래프를 보면 유저(Request)가 늘어나면 날수록 성능 차이가 나는것을 볼수 있습니다.

Spring MVC방식은 서버의 하드웨어 성능으로 커버가 가능한 구간을 지나면 쓰레드 풀이 점점 많아져 Queue에 쌓이기 때문에 느려지게 되어 있습니다.

WebFlux방식은 non-blocking을 통해 I/O를 잘 이용하고 있고, Request를 Event-Driven을 통해서 효율적으로 처리하기 때문이라고 합니다.

 

아래는 blocking과 non-blocking의 테스트 코드 입니다. (출처 : github.com/viviennes7/io-example)

 

## Blocking

@Test
    public void blocking() {
        final RestTemplate restTemplate = new RestTemplate();

        final StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        for (int i = 0; i < 3; i++) {
            final ResponseEntity<String> response =
                    restTemplate.exchange(THREE_SECOND_URL, HttpMethod.GET, HttpEntity.EMPTY, String.class);
            assertThat(response.getBody()).contains("success");
        }

        stopWatch.stop();

        System.out.println(stopWatch.getTotalTimeSeconds());
    }

 

## non-Blocking

@Test
    public void nonBlocking3() throws InterruptedException {
        final StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        for (int i = 0; i < LOOP_COUNT; i++) {
            this.webClient
                    .get()
                    .uri(THREE_SECOND_URL)
                    .retrieve()
                    .bodyToMono(String.class)
                    .subscribe(it -> {
                        count.countDown();
                        System.out.println(it);
                    });
        }

        count.await(10, TimeUnit.SECONDS);
        stopWatch.stop();
        System.out.println(stopWatch.getTotalTimeSeconds());
    }

 


WebFlux는 아래와 같은 용도로 사용하기를 추천한다고 합니다. (by 토비)

  • 비동기 - non-blocking reactive 개발에 사용하는 것이 좋음
  • 효율적으로 동작하는 고성능 웹어플리케이션 개발에 사용하는 것이 좋음
  • 서비스간 호출이 많은 마이크로스 서비스 아키텍처에 적합

일반적으로 blocking 코드 작성에는 익숙하기 때문에 생산성이 높을 것입니다. 그러나 non-blocking 코드에는 익숙하지 않고 학습코스트도 높을 것입니다. 확실히 이해하고 코딩하지 않으면 알수 없는 오류도 발생하기 쉽고 디버깅도 어렵기 때문에 학습이 필요합니다.

그리고 non-blocking과 blocking코드를 같이 사용하게 되면 비동기 코드가 무의미 해 지고 성능적인 이점도 볼 수 없기 때문에 고려해야 할 부분도 많다고 생각 합니다.