[Spring Webflux] 1. 동기 vs 비동기와 Blocking vs Non-blocking

목포·2022년 9월 18일
4

최근 MSA 공부를 하면서 마주하게된 WebFlux 라는 것을 파볼까한다. 쓰레드 중심의 개발을 한 적이 많이 없어서 사실 좀 생소하고 이해가 안 가는 부분들이 많아 여러 글을 거쳐 천천히 정리해볼 예정이다.

Spring WebFlux

Spring Webflux는 Spring 5(Spring boot2)부터 새롭게 추가된 모듈로 reactive-stack의 웹 프레임워크이며 non-blocking에 reactive stream을 지원한다.

위 그림은 Spring Boot2가 지원하는 Reactor에 대한 내용인데 Reactive 프로그래밍을 위한 핵심 모듈이라고 보면 된다.
리액티브를 사용하는 이유는 비동기/논블로킹을 이용해 더 적은 자원으로 더 많은 트래픽을 처리하기 위함이다.

blocking vs non-blocking

그러면 위에서 계속 언급하는 blocking 과 non-blocking은 도대체 무엇이냐. 단어 자체만 봤을 때도 뜻은 대충 유추할 수 있지만 정확히 어떤 식으로 돌아간다는건지 알아볼 필요가 있겠다.

먼저 다 알고 있는 동기, 비동기 개념을 한 번 짚고 넘어가보자.

동기(Synchronous)

함수를 호출한 곳에서 응답받는 것

즉, 이 말은 데이터의 요청과 결과가 동일한 자리에서 일어난다는 뜻이다. 그니까 현재 작업이 끝날 때까지는 그 자리를 떠나지 않는다는거다.

위 그림에서 1번이 끝날 때 까지 2번 작업을 시작하지 않는 이유가 그것이다. 이렇게하면 설계가 직관적이라 만드는 사람은 편할거다.
하지만, 단점은 하나의 작업이 끝날 때까지 다른 작업이 시작할 수 없다. 또, 트래픽이 몰리면 작업을 실행할 때 만들어지는 Thread들이 다량으로 사용 요청을 받게되어 Thread pool에서 사용가능한 스레드가 적어지게 된다. 이는

비동기(Asynchronous)

함수를 호출하는 곳에서 결과를 기다리지 않고, 다른 함수(Callback)에서 결과를 처리하는 것

비동기의 경우는 서로 다른 두 주체가 서로의 시작이나 종료에 관계없이 별도의 시작, 종료 시간을 가지고 있다. 그래서 결과를 기다리지 않고 다른 작업을 할 수 있게 된다.

Blocking

뭐 어떻게 돌아가는지는 알겠다. 그러면 Blocking은 뭘까? blocking과 non-blocking은 주로 I/O 작업 시 사용된다.

자신의 작업을 진행하다가 다른 주체의 작업이 시작되면 다른 작업이 끝날 때까지 기다렸다가 자신의 작업을 시작하는 것

예를 들어, 함수A가 함수B를 호출하면, 함수B가 끝날 때까지 함수A는 기다려야한다. 이는, 함수B의 결과를 함수A에서 처리하는 것과 같은 말이다. 여기서 제어권은 자신의 함수 코드를 실행할 권리 같은 것이다. 제어권이 다른 함수에게 가면 그 동안은 함수를 실행할 수 없다.

Non-blocking

다른 주체의 작업과 관련없이 자기 자신은 작업을 계속 진행하는 것

위와 같은 예로, 함수A가 함수B를 호출해서, 함수B의 작업이 다 끝나지 않았더라도 함수A는 자기 일을 계속할 수 있다. (제어권을 넘겨주지 않아도 된다.)

비교

대충 설명만 보면 blocking은 동기적으로 보이고, non-blocking은 비동기적으로 보인다. 하지만, 이 두 개념(동기 비동기/blocking non-blocking)에는 서로 다른 관점으로 접근하고 있다.

동기와 비동기는 호출되는 함수의 작업 완료 여부를 누가 신경쓰느냐의 관점이고 blocking/non-blocking은 호출되는 함수가 return을 하느냐 마느냐의 관점이다.
즉, 이 말의 전자는 함수 실행/리턴의 순차적 흐름이 중요한 것이 되겠고, 후자는 제어권이 누구에게 있는지 중요하다.

결국, 이 메커니즘들은 서로 다르기 때문에 여러 방향으로 조합해 사용할 수 있다. 더 자세히 알아보자.

Sync-Blocking

함수 A는 함수 B의 리턴값이 필요하고(동기), 제어권을 함수B에게 넘겨주고 완료될 때까지 기다렸다가(블로킹) 리턴값과 제어권을 돌려받는다.

👍 Java에서 다음과 같이 DB에서 데이터를 불러오는 작업들이나, 컬렉션에 데이터를 담기 위한 함수 호출 등이 그 예 이다.

public UserOrder order(String email) {
	try{
    	User user = findUserApi(email);
        List<Order> orders = getOpenOrders(user);
        return new UserOrder(email, orders);
    }catch(Exception e) {
    	return UserOrder.FAIL;
    }
}

Sync-Nonblocking

함수A는 함수B에게 제어권을 주지 않고, 자신의 코드를 계속 실행한다(논블로킹), 하지만, A함수는 B함수의 리턴 값이 필요하기 때문에, 계속 함수B에게 함수 완료 여부를 물어본다.(동기)

👍🏼 게임에서의 데이터 로드율 표시하기
맵을 이동할 때 맵 데이터를 계속 물어본다. 하지만,제어권은 계속 나한테 있어 화면에 로드율이 표시된다.

Async-Nonblocking

함수A가 함수B를 호출 했을 때, 제어권을 함수B에게 주지 않고, 자신이 계속 갖고 있는다. (논블로킹)
그리고 함수B를 호출할 때 콜백함수를 함께 주는데, 함수B는 완료 후 함수A가 준 콜백함수를 실행한다.(비동기)

👍 JS 비동기 콜백
frontend에서 backend로 API 요청을 보내고, 응답을 기다리지 않은 채 자신의 작업을 처리한다.

아래는 위에서 예시로 든 상황을 비동기적으로 구현한 것이다.

public DeferredResult<UserOrder> asyncOrders(String email) {
	DeferredResult dr = new DeferredResult(); 
    asyncFindUserApi(email).addCallback(
    	user -> asyncOrdersApi(user).addCallnack(
        	orders -> {
            	dr.setResult(new UserOrder(email, orders));
            },
            e -> dr.setErrorResult(UserOrder.FAIL)
        ),
            e -> dr.setErrorResult(UserOrder.Fail)
    );
}


//2. CompletableFuture
public CompletableFuture<UserOrder> asyncOrders(String email) {
	return asyncFindUser(email)
    	.thenCompose(user -> asyncOrders(user))
        .thenApply(orders -> new UserOrder(email, orders))
        .exceptionally(e -> UserOrder.FAIL);
}

//3. Reactor Flux/Mono
public Mono<UserOrder> asyncOrder(String email) {
	return asyncFindUser(email)
    	.flatMap(user - asyncGetOrders(user))
        .map(orders -> new UserOrder(email, orders))
        .onErrorReturn(UserOrder.FAIL);
}

참고로 3번 Reactor Flux/Mono 방법은 이 본 포스팅의 WebFlux와 관련된 데이터 타입이다. 이 부분은 다음 글에서 자세히 다뤄보도록 하겠다.

Async-blokcing

함수A는 함수B의 리턴 값과 상관없이 콜백함수를 보낸다(비동기), 하지만 이 때 제어권도 같이 넘기기 때문에(블로킹) 함수A는 자신과 관련없는 함수B의 작업이 끝날 때 까지 기다려야한다.

결론

  • 동기/비동기 : 요청받은 함수가 작업을 완료헀는지를 체크하는 순차적 흐름에 초점을 맞추고 있다.
    • 동기 : 요청한 함수 작업이 완료되었는지 계속 확인
    • 비동기 : 함수 요청 후 신경쓰지 않고 계속 자신의 작업을 하며, 요청한 함수의 작업이 끝나면 콜백함수로 알려준다.(작업 시작/종료 시간이 안 맞을 수 있다.)
  • 블로킹/논블로킹 : 함수 요청 시 제어권이 누구에게로 가는가에 초점을 맞추고 있다.
    • 블로킹 : 요청함수가 끝날 때까지 제어권을 주었다가 끝나고 받음.
    • 논블로킹 : 요청한 함수가 계속 제어권을 가지고 있을 수 있다.

Reference

👩‍💻 동기&비동기 / 블로킹&논블로킹 💯 완벽 이해하기

블로킹 Vs. 논블로킹, 동기 Vs. 비동기

profile
mokpo devlog

2개의 댓글

comment-user-thumbnail
2023년 5월 10일

알기 쉬운 예시랑 같이 적어놓으신게 굉장히 좋네요

답글 달기
comment-user-thumbnail
2023년 5월 26일

글 감사합니다.

답글 달기