ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • RSocket
    카테고리 없음 2020. 2. 10. 11:32

    Backpressure in Webflux


    스프링 웹플럭스를 사용하여 리액티브 스트림즈 스펙에 부합하는 웹애플리케이션을 개발할 수 있다. 리액티브 스트림즈에서 가장 중요한 특징은 Backpressure인데, 스프링 웹플럭스(+ 리액터)를 사용하면 같은 애플리케이션 내부에서 이를 효과적으로 실현할 수 있다. 그러나, 만일 두 개의 서로 다른 웹애플리케이션이 네트워크를 통해 http 프로토콜로 통신을 한다면, backpressure가 어떻게 동작할까 하는 의문이 생기게 된다. 가령 다음과 같이 모두 스프링 웹플럭스로 만들어진 서비스A와 B가 있다고 하고, A가 B의 아래 api를 호출한다고 생각해보자.

    // Service B
    @GetMapping("/people")
    public Flux<Person> people() {
    	return PersonService.getPeople();
    }

    분명히 서비스 B는 내부적으로 리액티브 방식으로 구현되어 있고, Flux<Person> 형태로 publisher를 리턴한다. 받는 서비스 A 쪽에서도, 마찬가지로 Flux<Person> 형태로 데이터를 받을 수 있을 것이고, 마치 같은 애플리케이션 내부에서 Flux를 사용하듯 체인을 걸어 사용할 수 있게 된다.

    // Service A에서 B 호출 후 Flux<Person> 타입으로 데이터를 받는다.
    webclient.get()
        .uri("/people")
        .retrieve()
        .bodyToFlux(Person.class)

     

    그렇다면 실제로 받는 Person 객체는 총 100개라고 가정하고, 위의 코드에 .limitRequest(10) 메소드를 사용하여, 내부적으로 request(10)을 통해 10개의 Person 객체만을 요구한다고 생각해보자.

    webclient.get()
        .uri("/people")
        .retrieve()
        .bodyToFlux(Person.class)
        .limitRequest(10)

    limitRequest(10)을 사용했기 때문에 분명히 10개의 Person 객체만을 획득하게 되겠지만, 여기서 주목해야 할 점은 실제로 네트워크를 통해 들어온 Person 데이터가 10개냐는 것이다. 결론부터 말하면 그렇지 않다. 리액티브 스트림즈 스펙을 따른 두 애플리케이션 사이에서, 가장 이상적인 방향은 네트워크를 통하더라도 backpressure가 정상적으로 동작하는 것이지만, 실제로는 서비스 A가 호출한 request(n)은 네트워크를 통하는 순간 의미가 없어진다.

     

    서비스 B에서는 100개의 Person 데이터를 한 번에 JSON으로 serialize하고, 서비스 A에서도 모든 데이터가 소켓 버퍼에 쌓이면 JSON으로 deserialize한다. 서비스 A의 limitReqeust(10)은 이미 모든 데이터가 다 도착한 이후에, 그 중에서 10개의 객체만을 사용하게 되는 것이다. 물론 backpressure의 측면에서 보자면, http 프로토콜을 사용하여 통신하기 때문에, tcp의 특성인 flow control이 동작하기 때문에 byte 단위의 backpressure는 실현된다. 하지만 리액티브 스트림즈 스펙이 제대로 실현되려면 byte 단위가 아닌 logical element 단위의 backpressure가 실현될 수 있어야 한다. http의 stream mimetype (ex. application/stream+json)을 사용하면, 조금 다르게 동작하기는 하지만 http 프로토콜의 한계로 결국 logical element 단위의 backpressure는 실현되지 못한다.

     

    Logical Element based Backpressure


    그래서 리액티브 스트림즈 애플리케이션에 걸맞는 또 다른 네트워크 프로토콜이 필요하다. 그 프로토콜이 바로 앞으로 다룰 RSocket이다. RSocket 프로토콜을 사용함으로써, byte 단위의 backpressure에서 logical element 단위의 backpressure로 한 단계 업그레이드된다.

    [참고]
    RSocket은 tcp layer위에서 동작할 수 있기 때문에, 그 경우 byte 단위의 backpressure가 사라지는 것은 아니다. tcp 레벨에서의 flow control은 여전히 수행될 것이기 때문이다. 따라서 byte 단위의 backpressure가 logical element 단위의 backpressure를 양자택일의 개념으로 보기 보다는, logical 레벨에서도 이제 backpressure가 가능해진다고 보는 것이 적절하다.

     

    Rsocket?


    지금까지는 스프링 웹플럭스를 예를 들어, 리액티브 스트림즈 표준을 따르는 웹애플리케이션의 backpressure 측면에서 RSocket의 필요성을 설명했었다. 이제부터는 RSocket 공식 사이트에 나온 내용을 토대로, RSocket의 컨셉과 특징을 설명하려고 한다.

    RSocket 사이트 메인에 나온 것처럼, RSocket은 리액티브 스트림즈 세만틱을 따르는 애플리케이션 레벨의 네트워크 프로토콜이다.

     

    Motivations

    위에서 설명했던 내용처럼 리액티브 애플리케이션 사이에서의 통신을 위한 적절한 프로토콜이 필요하고, RSocket이 그러한 프로토콜이다. Rsocket의 motivation은 아래와 같다.

    Message-driven

    네트워크 통신이 비동기 방식이다. RSocket 프로토콜은 하나의 네트워크 커넥션을 통해 멀티플렉싱 메세지 스트림 형태로 통신하고, 결코 응답을 기다리느라 블로킹되지 않는다.

     

    Interaction Models

    http를 이용하여 통신을 하면 모든 것은 request/response 구조이다. 하지만 때마다 다양한 구조의 interaction model이 필요한데, 예를 들면 서버 push라던지, request만 보내고 response는 받을 필요가 없는 경우도 있을 수 있다. RSocket은 하나의 interaction model로 제한하지 않고 아래 4가지 형태의 모델을 제공한다.

     

    1. Fire-and-Forget

    request만 보내고 response가 필요 없는 경우이다. 이러한 경우에 response를 스킵하기 때문에 네트워크 사용량을 줄일 뿐 아니라, 클라이언트와 서버의 프로세싱 타임도 줄일 수 있어 성능이 극대화된다.

    Future<Void> completionSignalOfSend = socketClient.fireAndForget(message);

     

    2. Request/Response (single-respone)

    표준 request/response 세만틱도 여전히 지원된다. 그리고 여전히 이러한 경우가 RSocket 통신 중에서 가장 빈번히 발생할 것이다. 이 request/response 모델은 "오직 1개의 response stream"이라고 여겨질 수 있다. 그리고 하나의 커넥션 상에서 멀티플렉싱 비동기 메세지 형태로 주고 받게 된다.

     

    컨슈머가 응답 메세지를 기다리기 때문에, 전형적인 requeust/response로 보이겠지만 사실 내부적으로 결코 블록킹되지 않는다.

    Future<Payload> response = socketClient.requestResponse(requestPayload);

     

    3. Request/Stream (multip-response, finite)

    request/response로부터 확장한 것이 request/stream인데, 응답이 여러 메세지를 허용하는 stream 형태이다. 각 element는 순서가 지켜진다.

    예시 Use case는 아래와 같다.

    • fetching a list of videos
    • fetching products in a catalog
    • retrieving a file line-by-line
    Publisher<Payload> response = socketClient.requestStream(requestPayload);
    

     

    4. Channel

    채널은 양방향 메세지 스트림이다.

    예시 Use case는 아래와 같다.

    • client requests a stream of data that initially bursts the current view of the world
    • deltas/diffs are emitted from server to client as changes occur
    • client updates the subscription over time to add/remove criteria/topics/etc.

    양방향 채널이 없다면, 클라이언트는 초기 request를 취소하고 다시 요청해야 하지만, 양방향 채널로는 단순히 subscription을 업데이트하면 된다.

    Publisher<Payload> output = socketClient.requestChannel(Publisher<Payload> input);

     

    Behaviors

    interaction model말고도, 애플리케이션과 시스템 효율성을 높이는 다른 특징들도 있다.

     

    1. Single-Response vs. Multi-Response

    single-response와 multi-response의 차이는 RSocket stack이 애플리케이션에 데이터를 전달하는 방식에 있다. single-response는 여러 frame으로 전달될 것이고, 하나의 RSocket connection에서 멀티플렉싱 형태로 쪼개진 메세지들이 전달될 것이다. 그러나 single-response는 하나의 응답을 기대하기 때문에, 애플리케이션이 전체 메세지가 도착해야만 처리를 할 수 있을 것이다. 반면에 multi-response는 여러 응답이 올 것을 기대하기 때문에, 클라이언트가 첫 번째 chunk가 도착하자마자 처리를 시작할 수 있다.

     

    2. Bi-Directional

    RSocket은 클라이언트와 서버 둘 다 requester와 responder가 될 수 있도록 양방향 요청을 지원한다.

     

    3. Cancellation

    모든 스트림은 (request/response 모델 포함) 응답자의 리소스를 효율적으로 제거할 수 있도록 스트림 취소를 지원한다. 이는 만일 클라이언트가 취소를 한다면, 서버는 초기에 일 처리를 중단할 수 있는 기회가 주어진다는 뜻이다.

     

    Resumability

    오래 유지되는 스트림의 경우, 특히 모바일 클라이언트에게 subscription을 제공하는 경우, 네트워크 불안정으로 연결이 해제되어 다시 연결되었을 때 모든 subscription을 다시 제공해야한다면 비용과 성능 저하가 막대할 것이다. 특히 네트워크가 끊기자마자 다시 연결된 경우나, 와이파이에서 셀 네트워크로 전환되는 경우에 말이다.

     

    RSocket은 세션 재개를 지원하며, 간단한 핸드쉐이크로 클라이언트/서버 세션을 재개할 수 있도록 한다.

     

    Application Flow Control

    RSocket은 애플리케이션 레벨의 flow control을 두 가지 형태로 제공한다. 바로 리액티브 스트림즈의 request(n)과 leasing이다.

     

    1. 리액티브 스트림즈의 request(n)

    서버 to 서버와 서버 to 디바이스 모델에 적합한 flow control이다. 리액티브 스트림즈 내용이므로 별도 설명은 하지 않겠다.

     

    2. Leasing

    주로 데이터 센터에서 서버 to 서버 모델에 적합한 flow control이다. 응답자가 요청 속도를 조절하기 위해 적절히 'lease'를 issue할 수 있고, 요청자 입장에서는 애플리케이션 레벨의 로드 밸런싱을 수행할 수 있게 된다. 이 시그널은 서버가 클라이언트에게 더 현명한 라우팅을 가능할 수 있게 하고 데이터 센터의 머신 클러스터 사이의 로드 밸런싱 알고리즘을 가능케 한다.

     

    Polyglot Support

    서로 다른 언어와 기술 스택으로 이루어진 애플리케이션들이 서로 통신하는 경우, HTTP/1.1의 request/response로 단일화된 통신방법 보다는 위에서 설명한 4개의 더 다양한 모델로 통신을 할 수 있다. 또한, 언어와 상관 없이 리액티브 스트림즈 애플리케이션 레벨의 flow control이 가능하다.

     

    Transport Layer Flexibility

    HTTP request/response 방식이 애플리케이션 사이의 유일한 통신 방식이 아니듯, TCP 또한 유일한 transport layer의 통신 방식이 아니며, 모든 케이스에 대해 최적의 방식인 것도 아니다. 따라서, RSocket은 tranport layer의 통신 방식을 상황에 따라, 디바이스 가용성, 성능에 따라 바꿀 수 있다. RSocket은 웹소켓, tcp, Aeron 을 타겟으로 하고 있고, tcp 같은 특성을 가진 QUIC 같은 tranport layer위에서 사용할 수 있다.

     

    Efficiency & Performance

    RSocket이 효율성과 성능을 위해 추구하는 바는 다음 과 같다.

     

    • 만연한 latency를 줄이고 non-blocking, 양방향, 비동기 애플리케이션 통신 방식을, 다양한 언어와 다양한 tranposrt layer 사이에서의 flow control을 지원함으로써, 시스템 효율성을 높인다.
    • 하드웨어 footprint를 감소시킨다.
      • 바이너리 인코딩을 통한 CPU와 메모리 효율성 증가
      • 지속적인 커넥션을 통해 커넥션을 매 번 맺는 중복 처리를 피한다.
    • 만연한 유저 latency를 감소시킨다.
      • 핸드쉐이크와 관련된 라운드 트립 네트워크 오버헤드를 피한다.
      • 바이너리 인코딩을 통해 수행 시간을 줄인다.
      • 더 적은 메모리를 할당하고 GC 비용을 줄인다.

     

     

     

     

     

     

    댓글

Designed by Tistory.