-
JAVA 병렬 프로그래밍스터디 2024. 6. 9. 03:40
Spring Webflux란?
Spring Webflux는 리액티브 웹 애플리케이션 구현을 위해 Spring 5.0부터 지원되는 Reactive Web Framework이다.
- Event-Driven 방식
- Async Non-Blocking
- 즉, 이벤트 루프가 돌아서 요청이 발생할 경우, 그것에 맞는 핸들러에게 처리를 위임하고 처리가 완료되면 callback 메소드 등을 통해 응답을 반환 받는다.
- 이 방식의 경우 요청이 처리될 때까지 기다리지 않기 때문에 Spring MVC에 비해 사용자의 요청을 대량으로 받아낼 수 있다는 장점이 있다.(CPU, Thread, Memory에 자원을 낭비하지 않고 효율적으로 동작, 서비스 간 호출이 많은 마이크로 서비스 아키텍처에 적합하다.)
Spring Webflux 탄생 배경
- Spring MVC : 서블릿 기반의 Blocking I/O 방식, 요청당 하나의 스레드를 사용, 스레드의 작업이 끝날 때까지 스레드가 차단됨.
- Spring MVC의 한계
- 대용량 요청 트래픽을 Spring MVC 방식이 처리하기엔 한계가 있었다.
- 트래픽이 많아지면 많아질수록 스레드도 많이 사용되는데,
- 스레드풀에 스레드 200개가 default로 존재하고, 만약 만명이 동시접근한다…? 와우..
- 많은 쓰레드를 사용함으로써, 컨텍스트 스위칭 비용이 증가할 수 있다.
- 요청이 들어오면 요청 당 하나의 Thread를 점유하여 요청을 처리
- 동시 다발적으로 스레드 수를 초과하는 요청이 발생한다면 계속해서 요청 큐에 대기하게되는 Thread Pool Hell 현상이 발생할 수 있다.
- Spring Webflux를 이용한 극복
- 대용량 트래픽을 감당하기 위해선, 비동기/논블로킹 방식의 I/O를 사용해야했으며 이 방식이 적용되어, 대용량도 안정적으로 리할 수 있는 Spring Webflux가 생겨났다.
Spring Webflux 스택
- Netty (논블로킹 환경 기본 서버엔진)
- 리액티브 스트림즈 어댑터를 통한, 리액티브 스트림즈 지원
- WebFilter (Spring Security 사용)
- NoSQL모듈 사용(Spring Data R2DBC, Non-Blocking I/O 지원)
Netty의 장점
- NIO(Non-Blocking I/O) 네트워크 기반 Netty 프레임워크는 비동기식 이벤트 기반 네트워킹을 지원
- 이벤트 기반 방식으로 동작하기 때문에 톰캣과 달리 스레드풀의 적은 스레드 갯수, 효율적인 리소스 사용이 가능하다.
즉 스레드 수가 작다 ⇒ 경합이 잘 일어나지 않는다.(상세히 찾아봐야할듯? 이벤트 ↔스레드 관련)
https://www.baeldung.com/spring-webflux-concurrency
리액티브 프로그래밍이란, 변화의 전파와 데이터 흐름과 관련된 선언적 프로그래밍 패러다임이다.
- 변화의 전파와 데이터 흐름 : 데이터가 변경될 때 마다 이벤트를 발생시켜서 데이터를 계속적으로 전달한다.
- 선언적 프로그래밍 : 실행할 동작을 구체적으로 명시하는 명령형 프로그램과 달리 선언형 프로그래밍은 단순히 목표를 선언한다.
이벤트 루프
- 비동기 콜백 - 콜백 함수 등록 : 애플리케이션에서 특정 I/O 작업(외부 시스템)을 요청할 때, 이 작업이 완료되었을 때 실행될 콜백 함수(또는 핸들러)를 함께 등록한다.
- 이벤트 루프 - 이벤트 처리 : 이벤트 루프는 등록된 채널(외부 시스템과 네트워크 연결)들의 상태를 주기적으로 확인한다. 특정 채널에서 읽기, 쓰기, 연결, 종료 등의 이벤트가 발생하면 이벤트 루프가 해당 이벤트에 연결된 콜백 함수를 적절한 시점에 실행한다.
- 이벤트 루프 - 콜백 함수 실행 : 콜백 함수는 이벤트 루프에 의해 관리되는 스레드에서 실행된다. 이를 통해 I/O 작업과 관련된 로직이 비동기적으로 처리되며, 이벤트 루프는 다른 이벤트들을 계속해서 처리할 수 있다.
Reactor
Reactor는 RxJava2 와 함께 Reactive Stream의 구현체이기도 하고, Spring Framework 5부터 리액티브 프로그래밍을 위해 지원되는 라이브러리 입니다.
Mono & Flux
- 리액터는 리액티브 스트림을 구현하는 라이브러리로 Mono 와 Flux 2가지 데이터 타입으로 스트림을 정의한다.
- 그렇기 때문에 WebFlux에서는 모든 응답을 Mono 혹은 Flux에 담아서 반환해주어야한다.
- Mono 0~1개의 결과처리, Flux 0~N개의 결과처리
- Reactor를 사용해 일련의 스트림 코드로 작성하다 보면 보통 여러 스트림을 하나의 결과로 모아줄 때 Mono를 쓰고, 각각의 Mono를 합쳐서 여러 개의 값을 여러 개의 값으로 처리하는 Flux로 표현할 수도 있다.’
여기서 결과는 비동기적으로 동작한 이벤트 결과, 즉 콜백 함수의 동작 결과를 말하는듯 함. 애매..? 확실하게 단정 짓기는 힘들듯?
Mono와 Flux가 Publisher 인터페이스를 구현하는데, Subscriber를 통해 콜백 함수를 등록하고, Publisher에서 비동기 작업 수행 후, 그 결과를 subscriber에게 전달? 한다.
ex ) 예를 들어 Flux에서 하나의 결과로 값을 모아주는 reduce연산자는 Mono를 리턴하고, Mono에서 flatMapMany라는 연산자를 사용하면 하나의 값으로부터 여러 개의 값을 취급하는 Flux를 리턴할 수 있다.
다양한 예제 코드
순수 webflux
val intFlux = Flux.range(1, 5).log() intFlux.subscribe() [ INFO] (main) | onSubscribe([Synchronous Fuseable] FluxRange.RangeSubscription) [ INFO] (main) | request(unbounded) [ INFO] (main) | onNext(1) [ INFO] (main) | onNext(2) [ INFO] (main) | onNext(3) [ INFO] (main) | onNext(4) [ INFO] (main) | onNext(5) [ INFO] (main) | onComplete()
class NumberException: RuntimeException("숫자가 왜 4지?") val intFlux = Flux.range(1, 5) .handle<Int> { num, sink -> if (num == 4) sink.error(NumberException()) else sink.next(num) } intFlux.subscribe( { num -> println("Item: $num") }, { error -> println("Error message: ${error.message}") } ) Item: 1 Item: 2 Item: 3 Error message: 숫자가 왜 4지?
@Override public Mono<MemberDTO.Crud> create(MemberDTO.Crud dto) { return memberRepository.save(memberMapper.ToCrudEntity(dto)) .flatMap(member -> { return memberRepository.findById(member.getId()); }).map(memberMapper::ToDTOCrud); }
Mono<Employee> employeeMono = client.get() .uri("/employees/{id}", "1") .retrieve() .bodyToMono(Employee.class); employeeMono.subscribe(System.out::println);
flux 참고용
성능
Full Reactive Stack: Conclusions
Spring WebFlux는 어떻게 적은 리소스로 많은 트래픽을 감당할까?
'스터디' 카테고리의 다른 글
Try with Resources / Try Finally (0) 2024.06.10 Spring Annotation 의 내부구조 동작원리 (0) 2024.06.09 Spring Transactional 뜯어보기 (0) 2024.05.15 Spring Tomcat 분석 (0) 2024.05.01 SQL Query 최적화 (Spring JPA, Go gorm) (1) 2024.03.15