본문 바로가기
백엔드/스프링 핵심 개념

동기, 비동기 / WEBFLUX, MONO

by 임지혁코딩 2024. 2. 23.

 

프로젝트를 진행하며, 비동기적으로 진행하자는 팀 의견(많은 사람들이 요청할 것이라는 가정), 그리고 PORTONE의 SRC를 직접 받아서 사용하는 형태로 인해 그 형태의 틀을 따라야 하는데, 그 틀이 비동기였던 이유로 전 과정을 비동기적으로 진행하게 되었다. 

 

동기/ 비동기

동기: 작업 a,b,c가 있다면, a이후 , a의 종료를 기다리고 b -> c를 진행한다

비동기: a이후, a의 종료 동안 b를 진행하고, b가 종료되면 callback되어 그 떄로 돌아가고, 그 이후 b,c를 진행한다. 

 

이 돌아가는 것, 즉 일꾼을 쓰레드(프로세스의 작업단위가 된다) 라고 한다.

 

 

WEBFLUX란?

 

implementation 'org.springframework.boot:spring-boot-starter-webflux'

WEBFLUX를 사용하기 위해 주입받아야 하는 의존성

 

SPRIGN WEBFLUX는, SPRING MVC와 비교되는 관점에서 많이 등장한다.

MVC는 하나의 요청에 대해, 하나의 스레드가 사용된다. 

EX) 외부 서버(API등)으로 요청을할때, REST TEMPLATE를 사용하는데, 각 5초걸리는게 5번 들어오면

25초가 걸린다

 

반면 WEBFLUX는, 제한된 스레드 수를 사용한다.

많은 요청을 하나의 스레드로 사용하며, 이에 비동기적인 특성이 더해진다. 

EX) 외부서버 로 요청을 할때, WEBCLIENT를 사용하며 5개의 요청중 1번이 끝나는걸 2번이 기다리지 않기 때문에, 
거의 5초와 비슷하게 모든 작업들이 진행된다. 

 

*다만, RDB는 비동기적으로 진행되지 않는 특성이 있다. (가능한 경우도 있긴 하다) 

*요청들을 비동기적으로 구현하여도, RDB내부적으론 동기적으로 동작했을 수도 있다.

 

@PostMapping("/payments/complete")
public Mono<ResponseEntity<PurChaseCheck>> validatepayment(@RequestBody ValidationRequest validation) {
    return accessTokenService.GetToken()
            .flatMap(token -> validateService.getpurchaseinfobyportone(validation.getPaymentId(), token)
                    .flatMap(purchasecheckresponsewebclient -> {
                        return Mono.just(purchaseService.validateandsave(purchasecheckresponsewebclient,validation.getPaymentId()));
                    }));
}

 

이와 같이, 작업 a,b,c가 있을때 a의 종료를 기다리지 않고 쓰레드는 b를 진행하고, b를 진행하다 a가 완료되면 다시 b를 진행하는 형태로 진행한다. 

 

그래봤자, 여러 사람이 요청하면 각각 쓰레드가 독립적으로 구성 되는거 아니야? 

예시를 들어보겠다.

사용자 A,B,C가 동시에 CONTROLLER단에 요청을 보냈다.

출처 , AHEA STUDY BLOG

 

앞서 말한대로 EVENT LOOP를 통해 적은 수의 쓰레드로, A,B,C등의  요청을 받을 수 있다.

즉 한쓰레드가 A의 작업을하다, CALLBACK 전까지 B의 요청을 하고 다시 A의 CALLBACK으로 돌아가 RESPONSE하고, 

이런식으로 동작이 가능하다.

 

내부 동작

A의 동작 내부적인 관점(CONTROLLER단의 코드를 보라) 에서도, 작업1의 동작중 작업 2를 진행하고, 작업 1이 CALLBACK되면 돌아가는 식으로 동작한다.

 

즉, 여러 요청, 한 요청의 여러 작업의 관점에서 모두 NON-BLOCKING적인 동작이 가능하며,

@PostMapping("/payments/complete")
public Mono<ResponseEntity<PurChaseCheck>> validatepayment(@RequestBody ValidationRequest validation) {
    return accessTokenService.GetToken()
            .flatMap(token -> validateService.getpurchaseinfobyportone(validation.getPaymentId(), token)
                    .flatMap(purchasecheckresponsewebclient -> {
                        return Mono.just(purchaseService.validateandsave(purchasecheckresponsewebclient,validation.getPaymentId()));
                    }));
}

 

( .FLATMAP은, 이러한 작업들의 동작을 연결해주는역할을한다.

 

이 내용이 사용자 1명의 작업의 내부적인 비동기적인 동작을 구현했다면,

사용자 A,B,C가 있을때는 WEBFLUX자체적으로 이러한 내용을 구축한다.

 

주의

만약, 생각보다 사용자가 적다면, 오히려 WEBFLUX를 사용하는 것이 많은 쓰레드를 사용하는 것 보다 느릴 수 있다.

속도나 쓰레드의 수는, 성능 그자체는 아님을 기억하자. 

 

 

그래서, MONO는 뭔데?

 

public Mono<PurChaseCheck> getpurchaseinfobyportone(String paymentid, String token)
{

    Mono<PurChaseCheck> purchasecheck = webClient.get()
            .uri("/payments/{paymentId}", paymentid)
            .header("Authorization", "Bearer " + token)
            .retrieve()
            .bodyToMono(PurChaseCheck.class) ;

    return purchasecheck ;

}

 

내가 구현한 코드의 예시. 

 

비동기적으로 실행되는 작업들을 스트림(Stream)이라고 한다.

 

MONO는, 이러한 STREAM을 다루기 위한 TYPE이다. 

현재 비동기적인 작업에선 , A,B,C, 모두중 완료되었을때 왔다갔다 CALLBACK되는 특성을 가지고 있기 때문에.

마지막 C의 종료 전까진 하나의 작업이 끝났다고 볼 수 없다.

이때, A의 작업결과를 활용할 수 있는 TYPE이 바로 MONO 인 것이다. 

 

Mono<String> PortoneTokenmono = webClient
        .post()
        .uri("/login/api-secret")
        .contentType(MediaType.APPLICATION_JSON)
        .body(BodyInserters.fromValue(portoneTokenRequest))
        .retrieve()
        .bodyToMono(PortoneTokenResponse.class)
        .map(PortoneTokenResponse::getAccessToken);

 

.RETRIEVE는 응답을 가져온다.

.BODYTOMONO는, 응답을 MONO로 변환한다.

.MAP은,  그 중 GETACCESSTOKEN을 한것만 을 응답에 대입한다. 

 

WEBFLUX, MVC만을 활용하든,

쓰레드의 수를 우선순위로 할 것인지 혹은 속도를 우선으로 할 것인지는

반드시 고려해보도록 하자. 

'백엔드 > 스프링 핵심 개념' 카테고리의 다른 글

TDD  (2) 2024.01.03
Handler Methods  (0) 2024.01.02
완성한 개발자 저장 프로젝트를 통해 핵심 복습  (0) 2023.12.30
Null Safety  (1) 2023.12.27
SpEL  (0) 2023.12.27