Spring Webflux는 복수개의 서비스로 이루어진 분산 시스템이 정상 상황 뿐만 아니라 장애 상황에서도 일관된 동작을 보장해 주는 시스템이며 Microservice가 지향하는 방향이다
용도
- 비동기 - 논블록킹 리엑티브 개발에 사용
- 효율적으로 동작하는 고성능 웹 어플리케이션 개발
- 서비스 간 호출이 많은 마이크로서비스 아키텍쳐에 적합
개발방식
- MVC (@Controller, @RestController, @RequestMapping)
- 새로운 함수형 모델
새로운 요청 - 응답 모델
- 서블릿 스택과 API에서 탈비 (Servlet API는 리엑티브 함수형 스타일에 적합하지 않음)
- ServerRequest, ServerResponse
지원 웹 서버 / 컨테이너
- Servlet 3.1 (Tomcat, Jetty, ..)
- Netty
- Undertow
Handler Function
- 함수형 스타일의 웹 핸들러 (컨트롤러 메소드)
- 웹 요청을 받아 웹 응답을 돌려주는 함수
함수형 Webflux가 웹 요청을 처리하는 방식
- 요청 매핑: RouterFunction - url 같은 정보로 어떤 핸들러가 이 요청을 처리할지 결정함
- 요청 바인딩 - Handler Function
- 핸들러 실행 - HandlerFunction
-핸들러 결과 처리 (응답 생성) - HandlerFunction
Spring MVC와 다른점
- Spring MVC는 Java EE의 Servlet spec에 기반하여 만들어져 있고, 본질적으로 블럭킹 동기방식이다
- 비동기 방식이 있지만 서블릿은 응답을 기다리는 동안 pool의 쓰레드들을 여전히 지연시킬 수 있기 때문에 전체 stack 이 reactive 해야 한다
- 스프링 프레임워크 5에 Webflux가 도입됐다.
- MVC는 서블릿 컨테이너와 서블릿을 기반으로 웹 추상화 계층을 제공
- Webflux는 서블릿 컨테이너 뿐만 아니라 Netty, Undertow와 같은 네트워크 어플리케이션 프레임워크도 지원하므로 HTTP와 Reactive Stream 기반으로 웹 추상화 계층을 제공합니다
- 따라서 Webflux 모듈에 HTTP abstractions, Reactive Stream adapter, Reactive codes 그리고 non-blocking servlet api를 지원하는 core web api가 포함되어 있습니다.
- 따라서 server-side webflux 상에서 두 가지 프로그램 모델로 구성이 가능합니다.
-- annotated Controller: Spring MVC 모델 기반의 기존 spring-web 모듈과 같은 방식으로 구성하는 방법으로 Spring MVC에서 제공하는 어노테이션들을 그대로 사용가능합니다
-- Functional Endpoints: Java 8 lambda style routing과 handling 방식입니다. 가벼운 routing 기능과 request 처리 라이브러리. callback 형태로써 요청이 있을 때만 호출된다는 점이 nnotated controller과 다름
동작 흐름
- Spring MVC는 서블릿 컨테이너로 들어온 요청이 디스패처 서블릿으로 전달되면, 시스패처 서블릿은 순차적으로 HandlerMapping, HandlerAdapter에 요청에 대한 처리를 위임하고, ViewResolver에 응담에 대한 처리를 위임하는 방식
- Webflux도 크게 다르지 않음
- 웹 서버 (Servlet Container, Netty, Undertow, etc)로 들어온 요청이 HttpHandler에서 전달되면 HttpHandler는 전처리 후 WebHandler에 처리를 위임하게 된다. 이 WebHandler 내부에서 HandlerMapping, HandlerAdapter, HandlerResultHandler 3개의 컴포넌트가 요청과 응답을 처리하게 됩니다. 처리가 끝나면 HttpeHandler가 후처리 후 응답을 종료합니다.
- 여기에서 보듯 Spring MVC와 동일하게 컴포넌트가 존재하지만 동작 방식이 달라 서로 다른 인터페이스를 사용합니다.
Image Service 생성
- web apps를 만들때 가장 중요한 규칙 중 하나는 컨트롤러들을 가능한 한 가볍게 유지하는 것. 그렇게 하기 위해서 독립된 ImageService를 생성할 필요가 있다.
Spring WebFlux는 어떻게 적은 리소스로 많은 트래픽을 감당할까
- non-blocking을 통해서 적은 수의 리소스로 동시성을 다룬다는 것
- 사용자가 I/O 요청을 할 때 CPU가 I/O Controller에 요청을 하고 I/O Controller가 파일을 다 가져오면 그것을 Memory에 적재시키고 CPU에게 완료되었다고 알려준다. 즉 큰 그림은 CPU -> I/O Controller -> CPU의 형태이다. 이때 핵심은 CPU가 I/O를 직접 가져오는 것이 아니라, 작은 CPU라고 불리는 I/O Controller가 수행한다는 것이다. 작업을 단순히 위임시키고 작업이 완료되는 동안에는 다른 일을 수행할 수 있다는 말이다.
- 기본적으로 Spring MVC와 RDBMS. Application에서 I/O 요청을 한 후 완료되기 전까지는 Application이 Block이 되어 다른 작업을 수행할 수 없다. 이는 자원이 효율적으로 사용되지 못하고 있음을 의미한다. 하지만 마치 동시에 동작하는 것처럼 느껴지는데 이는 Multi Thread 를 기반으로 동작하기 때문이다. 하지만 Thread간 Context Switching에 드는 비용이 존재하므로 여러 개의 I/O를 처리하기 위해 여러 개의 Thread를 사용하는 것은 비효율적이다
- Synchronos Non-blocking은 Application에서 I/O를 요청 후 바로 리턴되어 다른 작업을 수행하다가 특정 시간에 데이터가 준비가 다되었는지 상태를 확인한다. 데이터의 준비가 끝날 때까지 틈틈이 확인을 하다가 완료가 되었으면 종료된다. 여기서 주기적으로 체크하는 방식을 polling이라고 한다. 그러나 이러한 방식은 작업이 완료되기 전까지 주기적으로 호출하기 때문에 불필요하게 자원을 사용하게 된다
- Asynchronos Non-blocking은 I/O 요청을 한 후 Non-blocking I/O와 마찬가지고 즉시 리턴된다. 하지만, 데이터 준비가 완료되면 이벤트가 발생하여 알려주거나, 미리 등록해 놓은 callback을 통해서 이후 작업이 진행된다. 이전 두 I/O와 다르게 blocking이나 polling이 없어 더 효율적이다.
Event Driven
- Event Driven Programming은 프로그램 실행 흐름이 이벤트 (마우스 클릭, 키 누르기 또는 다른 프로그램의 메시지와 같은 사용자 작업)에 의해 결정되는 프로그래밍 패러다임이다. Event가 발생활 때 이를 감지하고 적합한 이벤트 핸들러를 사용하여 이벤트를 처리하도록 설계됐다. 순차적으로 진행되는 과거의 프로그래밍 방식과는 달리 유저에 의해 종잡을 수 없이 진행되는 GUI가 발전됨에 따라 Event-Driven 방식은 더욱 더 많이 쓰인다
- 이때 단순히 Listener에 등록한다고 자동적으로 인지하지 않는다. 무한히 이벤트 루프가 돎으로써 인지하게 된다
- 일일이 Event를 제어했던 과거와는 달리 요즘은 이를 단순히 Listener에 행위만 등록해 주면 간편하게 Event를 제어할 수 있다. 이는 Event Handler만이 관심의 대상이고 아제 집중할 수 있게 한다.
- 이러한 Event 처리는 Server에도 적합하다. 왜냐하면 HTTP Request라는 Event가 발생하기 때문이다. 그래서 Webflux는 Event-Driven 형태로 Architecture가 구현되어있다.
Spring MVC
- thread pool hell이란 설정해 놓은 thread poll size를 넘게 되면 작업이 처리될 때까지 Queue에서 계속해서 기다려야 한다. 그래서 전체의 대기산이 늘어난다. Thread pool이 감당할 수 있는 요청수를 넘는 순간부터는 평소보다 수배나 많은 지연시간을 보여준다. Thread pool이 감당 할 수 있을때 까진 빠른 처리속도를 보이지만, 넘는 순간부터는 지연시간이 급격하게 늘어난다.
- 코드가 보통 느려지는 경우는 DB, Network등의 I/O가 일어나는 부분에서 많은 시간을 소비하기 때문
Spring Webflux
- 사용자들에 의해 요청이 들어오면 Event Loop을 통해서 작업이 처리 된다.
- worker thread defult size는 서버의 core 개수로 설정되어 있다.
- 특히나 유행하는 MSA에서는 수많은 마이크로서비스가 거미줄처럼 서로를 네트워크를 통해서 호출하고 있다. 그래서 non blocking I/O로 성능을 끌어 올릴 수 있다.
- 성능을 최대치로 끌어 올리려면 모든 작업이 I/O Non-blocking 기반으로 동작해야 한다.
- 아직은 DB connection을 non-blocking을 지원하는 라이브러리가 널리 보급되어 있지 않다.
- 그렇다고 무조건 webflux를 쓰는것은 아니다. 일정 구간 서버 성능이 비슷한 구간이 있어 어느 정도 사용자가 많아졌을때 사용하는 것이 좋다.
Publisher의 구현체인 mono와 Flux를 활용하여 데이터를 발행하는 객체를 만들고 Controller로 리턴을 하면 Spring에서 알아서 subscribe 하도록 되어 있다. 즉, Controller에서 각 명령을 실행 한다기 보다, 데이터 발행하는 Publisher를 만드는 코드를 작성하도록 접근을 해야 한다.
Citation
https://hojak99.tistory.com/453