Spring 5 Reactive 웹개발
스프링 5 리액티브 웹 개발
17년 강의라 현재와는 조금 다른 부분 있을수도
WebFlux 소개
WebFlux의 핵심 = 비동기 논블로킹 방식의 웹 서버 어플리케이션 (구 Spring-Web-Reactive)
메인 테마가 원래는 JDK9 였는데 이제는 WebFlux로 변경되었음. 스프링 리액티브 스택의 웹 파트 담당
용도
효율적으로 동작하는 고성능 웹 어플리케이션 개발 서비스 간 호출이 많은 마이크로서비스 아키텍처에 적합.
잘못 사용하면 애먹기 때문에 조심스럽게 사용..
2가지 개발 방식 지원
- 기존의 @MVC 방식
- 새로운 함수형 모델
새로운 요청-응답 모델
- 서블릿 스택과 API에서 탈피
- 서블릿 API는 리액티브 함수형 스타일에 적합하지 않기 때문
- HttpServletRequest, HttpServletResponse -> ServerRequest, ServerResponse
동기 / 비동기
Singleton 은 어떤 숫자가 생각나나? = 1
Dependency Injection 은 어떤 숫자가 생각나나? = 3
- 두 개의 오브젝트가 있어야 하고
- 하나의 오브젝트는 다른 오브젝트를 의존하고 있어야 함
- 의존 관계는 클래스 레벨에서 생길 수 있음
- 스프링에서 이야기 하는 DI는 오브젝트 간의 의존 관계
- 이 의존은 런타임에 관계 정의됨
- 프레임워크가 런타임에 의존관계를 정의하고 의존성을 주입한다
Synchronous / Asynchronous는 어떤 숫자가 생각나나? = 2+
- Syn == together
- chrono = time
- 동기 Synchronous = 두 가지 이상의 존재가 시간을 함께 맞춘다
- 비동기 Asynchronous = 두 가지 이상의 존재가 시간을 맞추기 않는다
- 뭐랑 뭐가 동기인가? 혹은 비동기인가? 또 어떤 시간을?
동기
A, B가 시작시간 또는 종료시간이 일치하면 동기
- A, B 쓰레드가 동시에 작업을 시작하면 동기 (CyclicBarrier)
- 메소드 리턴(A)시간과 결과를 전달받는(B) 시간이 일치하면 동기
A가 끝나는 시간과 B가 시작하는 시간이 같으면 동기
- synchronized
- BlockingQueue
블록킹, 논블록킹
- 동기, 비동기와는 관점이 다름
- 내가 직접 제어할 수 없는 대상을 상대하는 방법
- 대상이 제한적임 - IO, 멀티쓰레드 동기화
대상을 뭉뚱그려 나오는 경우가 많기 때문에 혼동 많이 함
비동기-논블록킹 개발
스프링에서는 어떻게 말해왔나?
Thread pool hell : 서버의 쓰레드 풀과 latency는 밀접한 관계가 있음 (초당 처리율은 떨어짐) 왜 그런가? 블록킹 방식 (특히 I/O) - 요청이 들어오면 서버가 쓰레드 하나를 꺼내서 사용하고 리턴 그 서버에서 응답이 끝나면 ok 그런데 그 서버에서 다른 서버로 요청하는 경우? 계속 대기 대기하는 동안에 CPU는 거의 사용하지 않음. 하지만 쓰레드는 계속 점유하고 있으므로 문제 발생 쓰레드는 서버 입장에서 굉장히 비싼 자원.. 메모리 사용도 많고. 더 많은 요청을 처리하기 위해서는 쓰레드를 무한정 늘릴 수는 없다 (리소스의 한계 때문) 백엔드에 API 요청을 보내고 기다리는 동안에는 쓰레드 자원이 빠르게 고갈됨
Servlet 3.0 - 비동기 요청 처리
- 서블릿이 어떤 리소스나 이벤트를 기다리는 요청 처리를 완료하지 못하는 경우가 많아지는 문제
- 대기 상태에 들어가는 성격의 요청 처리라면
- 비동기 작업 등을 만들고
- 응답을 생성하지 않은 채로 서블릿을 종료
- 자원이 확보되거나 이벤트가 발생하면
- 해당 쓰레드에서 응답 처리하거나 컨테이너에게 전달
2009년에 제안된 Servlet 3.0 똑같이 2009년에 등장한 Node.js - 싱글 쓰레드를 논블로킹으로 처리한다 자바로 개발하던 사람들에게 자바를 쓰지 말자고 제안
Spring 3.2 - 서블릿 3+ 기반 비동기 요청 처리
- 2012년에 등장
- 스프링 @MVC에 다양한 방식으로 비동기 요청 처리가 가능한 수단 제공
- 4.3까지 지속적인 기능 확장
- Callable
- DeferredResult
- ListenableFuture
- CompletionStage
- ResponseBodyEmitter
코드로 보는 스프링 비동기 웹 개발 방법의 종류
- 2개의 API 호출의 조합으로 이루어진 웹 로직
- 각 API는 1초 정도의 작업 소요
- 동기 - 블록킹 방식 - 1개
- 비동기 - 논블록킹 방식 - 4개
- 명령형 스타일 : ListenableFuture
- 함수형 스타일 : CompletableFuture
- 리액티브 : WebFlux @MVC
- 리액티브 : WebFlux 함수형 모델
- 서버의 HTTP 요청 처리 쓰레드를 1개로 제한
- 첫 API를 호출해서 결과를 받아 두번째 API를 호출하고 로직 적용하여 리턴하는 작업
- 테스트는 200개의 쓰레드를 만들어서 동시에 200개의 요청 전송
- 각 요청에 대한 처리 결과와 시간, 총 요청 처리시간 확인
비동기 방식에서는 요청 후 거의 바로 리턴 나중에 결과가 필요할텐데, 이 결과를 받기 위해서 특수한 결과 형태 사용
비동기-논블록킹 API 호출 - 함수형 스타일
- 비동기 API 호출 결과를 CompletableFuture(CompletionStage)로 변환해서 함수형 조합 방식 적용
- 비동기 작업이 완료됐을 때 의존할 작업을 다음 비동기 작업에 조합하거나 결과 값을 조작하는 방식 구성
- 간결한 예외 처리, 예외 복구
- 두 개의 비동기 작업 결과의 결합
- 각 비동기 작업의 ExecutorService 지정 가능
리액티브 - WebFlux @MVC
- 스프링 5의 WebFlux 모듈 기반
- Reactor 리액티브 라이브러리 이용
- 비동기-논블록킹 리액티브 웹 서버 - 리액티브 웹 클라이언트의 조합
- 기존 API 호출 방식과 유사한 Mono 방식, 혹은 스트림을 지원하는 Flux 제공
웹의 응답은 3가지 구성을 가짐 - HTTP 상태 코드, 응답 헤더, 응답 body
함수형 스타일 WebFlux
RouterFunction + HandlerFunction
스프링이 웹 요청을 처리하는 방식
- 요청 매핑
- 웹 요청을 어느 핸들러에게 보낼지 결정
- URL, 헤더
- @RequestMapping
- 요청 바인딩
- 핸들러에 전달할 웹 요청 준비
- 웹 URL, 헤더, 쿠키, 바디
- 파라미터, 어노테이션 적절히 붙여주면 스프링이 알아서 변환해서 메소드에 넣어줌
- 핸들러 실행
- 전달 받은 요청 정보를 이용해 로직 수행하고 결과를 리턴
- 핸들러 결과 처리 (응답 생성)
- 핸들러의 리턴 값으로 웹 응답 생성
- 웹 응답의 3가지 구성
WebFlux 함수형 개발의 구성 요소
RouterFunction
- 함수형 스타일의 요청 매핑
- 웹 요청 정보 중에서 URL 경로 패턴 검사
HandlerFunction
- 요청 바인딩
- 핸들러 실행
- 핸들러 결과 처리 (응답 생성)
RouterFunction + HandlerFunction 조합하여 사용
- RouterFunctions.route(predicate, handler)
RequestPredicate = RequestMapping의 함수형 버전
- GET(), POST(), PUT(), HEAD()…
- method()
- path()
- contentType()
- queryParam()
- accept()
많은 경우 핸들러 (컨트롤러) 로직 복잡함
핸들러 내부 로직이 복잡하다면 분리한다
- 핸들러 코드만 람다 식을 따로 선언하거나
- 메소드를 정의하고 메소드 참조로 가져온다
공통의 조건을 컨트롤러 레벨에서 분리할 수 있음 -> nest() 메소드 사용
WebFlux 함수형 스타일의 장점
- 모든 웹 요청 처리 작업을 명시적인 코드로 작성
- 메소드 시그니쳐 관례와 타입 체크가 불가능한 어노테이션에 기반하는 @MVC 스타일보다 명확
- 정확한 타입 체크 가능
- 함수 조합을 통한 편리한 구성, 추상화에 유리
- 테스트 작성의 편리함
- 핸들러 로직은 물론
- 요청 매핑과 리턴 값 처리까지 단위 테스트로 작성 가능
WebFlux 함수형 스타일의 단점
- 함수형 스타일의 코드 작성이 편하지 않으면 코드 작성과 이해 모두 어려움
- 익숙한 방식으로도 가능한데 뭐하러 적용함?
@MVC WebFlux
@Controller + @RequestMapping
기존의 어노테이션 + 메소드 형식의 관례를 이용하는 @MVC 방식과 유사 비동기 + 논블록킹 리액티브 스타일로 작성
ServerRequest, ServerResponse
- WebFlux의 기본 요청, 응답 인터페이스 사용
- 함수형 WebFlux의 HandlerFunction을 메소드로 만들었을 때와 유사
- 매핑만 어노테이션 방식 사용
@MVC 요청 바인딩 = Mono / Flux 리턴 값
- 가장 대표적인 @MVC WebFlux 작성 방식
- 파라미터 바인딩은 @MVC 방식 그대로
- 핸들러 로직 코드 결과를 Mono / Flux 타입으로 리턴
@RequestBody 바인딩 (JSON, XML)
- T
- Mono <T>
- Flux <T>
@ResponseBody 리턴 값 타입
- T
- Mono <T>
- Flux <T>
- Flux <ServerSideEvent>
- void
- Mono <Void>
WebFlux와 리액티브 기술
WebClient + Reactive Data
WebFlux 만으로 성능이 좋아질까?
- 비동기 - 논블록킹 구조의 장점은 블록킹 I/O를 제거하는 데에서 나온다
- HTTP 서버에서 논블록킹 I/O는 오래 전부터 지원
- 뭘 개선해야 할까?
개선할 블록킹 I/O
- 데이터 엑세스 리포지토리
- HTTP API 호출
- 기타 네트워크를 이용하는 서비스
JPA - JDBC 기반 RDB 연결
- 블로킹 메소드로 점철된 JDBC API
- 일부 DB는 논블록킹 드라이버가 존재하지만…
- @Async 비동기 호출과 CFuture를 리액티브로 연결하고 쓰레드풀 관리 통해 웹 연결 자원을 효율적으로 사용하도록 만드는 정도
- JDK 10에서 Async JDBC가 등장할수도? — R2DBC 등장으로 사용 가능 한듯??
Spring Data JPA의 비동기 쿼리 결과 방식
리포지토리 메소드의 리턴 값을 @Async 메소드처럼 작성
본격 리액티브 데이터 엑세스 기술
- 스프링 데이터의 리액티브 리포지토리 이용
- MongoDB
- Cassandra
- Redis
- CouchDB
- ReactiveCrudRepository 확장
논블록킹 API 호출은 WebClient
- AsyncRestTemplate의 리액티브 버전
- 요청을 Mono / Flux 형태로
- 응답도 Mono / Flux 형태로
비동기-논블록킹 리액티브 웹 어플리케이션의 효과를 얻으려면
- WebFlux + 리액티브 리포지토리
-
- 리액티브 원격 API 호출
-
- 리액티브 지원 외부 서비스
-
- @Async 블록킹 I/O
-
- 코드에서 블록킹 작업이 발생하지 않도록 Flux 스트림 또는 Mono에 데이터를 넣어 전달
리액티브 함수형은 꼭 성능 때문에 사용?
- 함수형 스타일 코드를 이용해 간결하고 읽기 좋고 조합하기 편한 코드 작성
- 데이터 흐름에 다양한 오퍼레이터 적용
- 연산을 조합해서 만든 동시성 정보가 노출되지 않는 추상화된 코드 작성
- 동기, 비동기, 블록킹, 논블록킹 등을 유연하게 적용
- 데이터의 흐름의 속도를 제어할 수 있는 메커니즘 제공
논블록킹 I/O에만 효과가 있나?
- 시스템 외부에서 발생하는 이벤트에도 유용
- 클라이언트로부터의 이벤트에도 활용 가능
ReactiveStreams
- WebFlux가 사용하는 Reactor 외에 RxJava2를 비롯한 다양한 리액티브 기술에 적용된 표준 인터페이스
- 다양한 기술, 서비스 간의 상호 호환성에 유리
- 자바 9에 Flow API로 포함
뭘 공부해야 하나
- Java 8 + 함수형 프로그래밍에 익숙해질 것
- CompletableFuture와 같이 비동기 작업의 조합, 결합에 뛰어난 툴의 사용법을 익힐 것
- ReactorCore 학습 - Mono/Flux, 오퍼레이터, 스케줄러
- WebFlux와 스프링의 리액티브 스택 공부
- 비동기 논블록킹 성능과 관련된 벤치마킹, 모니터링, 디버깅 연구
- 테스트