ABOUT ME

Today
Yesterday
Total
  • 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 방식이 처리하기엔 한계가 있었다.
      1. 트래픽이 많아지면 많아질수록 스레드도 많이 사용되는데,
      2. 스레드풀에 스레드 200개가 default로 존재하고, 만약 만명이 동시접근한다…? 와우..
      3. 많은 쓰레드를 사용함으로써, 컨텍스트 스위칭 비용이 증가할 수 있다.
      4. 요청이 들어오면 요청 당 하나의 Thread를 점유하여 요청을 처리
      5. 동시 다발적으로 스레드 수를 초과하는 요청이 발생한다면 계속해서 요청 큐에 대기하게되는 Thread Pool Hell 현상이 발생할 수 있다.
    • Spring Webflux를 이용한 극복
      • 대용량 트래픽을 감당하기 위해선, 비동기/논블로킹 방식의 I/O를 사용해야했으며 이 방식이 적용되어, 대용량도 안정적으로 리할 수 있는 Spring Webflux가 생겨났다.

    Spring Webflux 스택

    • Netty (논블로킹 환경 기본 서버엔진)
    • 리액티브 스트림즈 어댑터를 통한, 리액티브 스트림즈 지원
    • WebFilter (Spring Security 사용)
    • NoSQL모듈 사용(Spring Data R2DBC, Non-Blocking I/O 지원)

    Netty의 장점

    1. NIO(Non-Blocking I/O) 네트워크 기반 Netty 프레임워크는 비동기식 이벤트 기반 네트워킹을 지원
    2. 이벤트 기반 방식으로 동작하기 때문에 톰캣과 달리 스레드풀의 적은 스레드 갯수, 효율적인 리소스 사용이 가능하다.
    3. 즉 스레드 수가 작다 ⇒ 경합이 잘 일어나지 않는다.(상세히 찾아봐야할듯? 이벤트 ↔스레드 관련)

    https://www.baeldung.com/spring-webflux-concurrency

    리액티브 프로그래밍이란, 변화의 전파와 데이터 흐름과 관련된 선언적 프로그래밍 패러다임이다.

    • 변화의 전파와 데이터 흐름 : 데이터가 변경될 때 마다 이벤트를 발생시켜서 데이터를 계속적으로 전달한다.
    • 선언적 프로그래밍 : 실행할 동작을 구체적으로 명시하는 명령형 프로그램과 달리 선언형 프로그래밍은 단순히 목표를 선언한다.

    이벤트 루프

    1. 비동기 콜백 - 콜백 함수 등록 : 애플리케이션에서 특정 I/O 작업(외부 시스템)을 요청할 때, 이 작업이 완료되었을 때 실행될 콜백 함수(또는 핸들러)를 함께 등록한다.
    2. 이벤트 루프 - 이벤트 처리 : 이벤트 루프는 등록된 채널(외부 시스템과 네트워크 연결)들의 상태를 주기적으로 확인한다. 특정 채널에서 읽기, 쓰기, 연결, 종료 등의 이벤트가 발생하면 이벤트 루프가 해당 이벤트에 연결된 콜백 함수를 적절한 시점에 실행한다.
    3. 이벤트 루프 - 콜백 함수 실행 : 콜백 함수는 이벤트 루프에 의해 관리되는 스레드에서 실행된다. 이를 통해 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 참고용

    Introduction to Reactive Programming with Spring Webflux | Gökhan Ayrancıoğlu - Software Engineer, Blogger

    성능

    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
Designed by Tistory.