GithubHelp home page GithubHelp logo

rsocket's Introduction

RSocket protocol

Протокол TCP

Для того чтобы понять, почему использование классического Request-Response через HTTP/1.1 нам не подходит, нужно посмотреть на некоторые аспекты протокола TCP.

HTTP/1.1 для каждого цикла взаимодействия создает TCP соединение с сервером. Процесс установки TCP соединения называется трехфазным рукопожатием и выглядит следующим образом:

TCP Connection Establish

Получается, для установки соединения тратится 1.5 RTT (Round Trip Time) и так на каждый запрос, что сказывается на утилизации ресурсов.

Вторым важным аспектом является то, что сетевая инфраструктура сейчас очень мощная, а значит скорость передачи данных может быть очень большой (100Mb/s+), но после установки соединения нет информации о доступной скорости передачи данных, а значит скорость передачи начинается с маленьких значений и постепенно увеличивается в процессе передачи большого объема данных.

Объем передаваемых данных определяется размером окна отправителя. Для управления размером окна используется механизм управления перегрузкой Congestion Control – динамическое изменение размера окна отправителя.

Аддитивное увеличение, мультипликативное уменьшение

При получении каждого подтверждения мы прибавляем к размеру окна некоторое значение, обычно это MSS, а при появлении перегрузки уменьшаем размер окна в два раза.

Медленный старт

Первоначально размер окна устанавливается маленьким, при каждом подтверждении размер окна увеличивается на 2, происходит экспоненциальный рост размера окна. После сигнала о перегрузке размер окна снова становится минимальным.

Комбинированный вариант

На практике в TCP используется комбинация методов аддитивного увеличения, мультипликативного уменьшения и медленного страта. Для того чтобы быстро заполнить пропускную способность используется медленный старт, после того, как случилась первая перегрузка размер окна уменьшается вдвое – порог медленного старта, и после этого уже используется метод аддитивного увеличения, мультипликативного уменьшения.

Congestion Control

RSocket

RSocket – двоичный протокол Point-To–Point, обеспечивающий семантику Reactive Streams. Протокол RSocket определяет именно протокол кодирования и кадров передачи данных без привязки к транспорту. В роли транспорта может выступать:

  • TCP;
  • WebSocket;
  • HTTP/2 Stream;
  • Aeron.

Мотивация

Предположим, есть сервисы, которые общаются друг с другом по HTTP (REST API).

Первая проблема — модель взаимодействия запрос/ответ HTTP . Некоторые шаблоны коммуникации трудно реализовать эффективным способом, используя модель взаимодействия запрос / ответ. Даже выполнение простой операции Fire–And–Forget имеет побочные эффекты — сервер должен отправить ответ обратно клиенту, даже если клиенту это не нужно. Вторая проблема — производительность. Обмен сообщениями на основе RabbitMQ/Kafka, gRPC или даже HTTP/2 с его поддержкой мультиплексирования будет намного лучше с точки зрения производительности и эффективности, чем простой HTTP/1.1.

Главная задача протокола – реализовать удобный и эффективный протокол, реализующий концепцию Reactive Streams и поддерживающий как форматы запрос–ответ, так и streaming.

gRPC vs RSocket

gRPC и RSocket пытаются решить разные проблемы. gRPC — это среда RPC, использующая HTTP/2, т.е. он находится на L7 OSI – т.е. RPC поверх прикладного HTTP/2. При взаиможействии из браузера (если нет поддержки HTTP/2) gRPC взаимодействует через Envoy Proxy, т.е. до proxy выполняются POST запрос HTTP/1.1, а от Envoy до приложения уже испольузется HTTP/2.

RSocket — это уровень OSI 5/6, который моделирует семантику Reactive Streams по сети. Реактивные потоки позволяют моделировать асинхронные потоки с backpressure.

gRPC предназначен для работы с семантикой HTTP/2. Если вы хотите отправить его через другой транспорт, вы должны имитировать семантику HTTP/2. По всей сети он фактически связан с HTTP/2 и TCP.

RSocket требуется только дуплексное соединение — т.е. то, что может отправлять и получать байты. Это может быть TCP, WebSockets и т. д.

RSocket не имеет взаимодействия клиент-сервер в традиционном смысле HTTP. В RSocket, когда клиент подключается к серверу, они оба могут быть запросчиком и ответчиком. Это означает, что после того, как они подключены, RSocket является полностью дуплексным, и запрашивающая сторона сервера может инициировать вызовы запрашивающей стороне клиента — то есть сервер может совершать вызовы в веб-браузер после подключения.

WebSocket vs RSocket

WebSocket не обеспечивают BackPressure на уровне приложения, только backpressure на уровне байтов на основе TCP. WebSocket также обеспечивают только создание, они не обеспечивают семантику приложения. Разработчик должен разработать протокол приложения для взаимодействия с WebSocket.

RSocket обеспечивает кадрирование, семантику приложения, backpressure на уровне приложения, и оно не привязано к конкретному транспорту.

Стратегии использования

  1. Request-Response — отправляет сообщение и получает результат.
    Request–Response
  2. Request-Stream — отправляет сообщение и получает обратно поток данных.
    Request Stream
  3. Channel — отправляет потоки сообщений в обоих направлениях.
    Channel
  4. Fire-and-Forget — предназначен для передачи данных от отправителя к получателю, в котором отправитель не заботится о результате операции — он отправил запрос и забыл о нем.
    Fire and Forget

Внутреннее устройство

RSocket использует кадрирование. Кадр (Frame) RSocket — это отдельное сообщение, которое содержит запрос, ответ или обработку протокола. К кадру RSocket может добавляться 24-битное поле длины кадра (Frame Length), представляющее длину кадра в байтах. Зависит от базового транспортного протокола, используемого RSocket, поле длины кадра может не требоваться.

Общая структура сообщения:

 0                   1                   2
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Frame Length               |
+-----------------------------------------------+
|                RSocket Frame          ...
|
+-----------------------------------------------+

Каждый кадр состоит из заголовка кадра (RSocket Frame), который содержит идентификатор потока, определение типа кадра и другие данные, относящиеся к типу кадра. За заголовком кадра следуют метаданные и полезная нагрузка — эти части несут данные, указанные пользователем. Данные и метаданные могут быть закодированы в разных форматах. Кадры отправляются в виде потока байтов. Если объем кадра большой, то он разбивается на фрагменты (Fragment) и каждому кадру добавляется порядковый номер фрагмента.

Кадр с метаданными и полезной нагрузкой:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|              Metadata Length                  |
+---------------------------------------------------------------+
|                       Metadata Payload                       ...
+---------------------------------------------------------------+
|                       Payload of Frame                       ...
+---------------------------------------------------------------+

Формат заголовка кадра:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                         Stream ID = 0                         |
+-----------+-+-+---------------+-------------------------------+
|Frame Type |0|0|    Flags      |
+-------------------------------+-------------------------------+
|0|                                                             |
+               Last Received Client Position                   +
|                                                               |
+---------------------------------------------------------------+
  • Stream ID – ID потока для этого кадра или 0, если это единственный кадр в соединении;
  • Frame Type – тип кадра;
  • Fags – флаги зависят от типа кадра, но все типы кадров должны иметь зарезервированное место для флага I (Ignore) – если фрем не распознан, M (Metadata) – флаг, указывающий, что присутствуют метаданные.

RSocket использует мультеплексирвоание, т.е. создает каналы поверх единственного TCP соединения. Каждый поток имеет свой уникальный идентификатор.

RSocket не различает клиента и сервер после фазы настройки соединения. Каждая сторона может начать отправку данных другой — это делает протокол почти полностью симметричным.

Типы кадров

Тип Значение Описание
RESERVED 0x00 зарезервировано
SETUP 0x01 установка соединения, отправляется клиентом для инициации процесса обмена сообещниями
LEASE 0x02 отправляется принимающей стороной, чтобы известить сколько сообщений она готова обработать за указанный период
KEEPALIVE 0x03 keepalive
REQUEST_RESPONSE 0x04 ответ за запрос
REQUEST_FNF 0x05 сообщение без подтверждения доставки
REQUEST_STREAM 0x06 клиент ожидает стрим данных в ответ на свой запрос
REQUEST_CHANNEL 0x07 запрашивается двунаправленный канал данных
REQUEST_N 0x08 запрашивается еще N значений
CANCEL 0x09 отмена невыполненного запроса
PAYLOAD 0x0A полезная нагрузка (для Reactive Stream onNext)
ERROR 0x0B ошибка
METADATA_PUSH 0x0C push с асинхронными метаданными
RESUME 0x0D возобновление обмена сообщениями
RESUME_OK 0x0E подтверждение, что возобновление возможно
EXT 0x3F зарезервировано для расширения протокола

Примеры

$ ./gradlew clean build

$ curl http://localhost:8080/api/v1/generate

$ brew install making/tap/rsc

$ rsc --route generate --debug tcp://localhost:7090
2021-12-24 00:46:34.099 DEBUG 9089 --- [actor-tcp-nio-2] io.rsocket.FrameLogger                   : sending -> 
Frame => Stream ID: 0 Type: SETUP Flags: 0b0 Length: 75
Data:

2021-12-24 00:46:34.100 DEBUG 9089 --- [actor-tcp-nio-2] io.rsocket.FrameLogger                   : sending -> 
Frame => Stream ID: 1 Type: REQUEST_RESPONSE Flags: 0b100000000 Length: 22
Metadata:
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| fe 00 00 09 08 67 65 6e 65 72 61 74 65          |.....generate   |
+--------+-------------------------------------------------+----------------+
Data:

2021-12-24 00:46:34.227 DEBUG 9089 --- [actor-tcp-nio-2] io.rsocket.FrameLogger                   : receiving -> 
Frame => Stream ID: 1 Type: NEXT_COMPLETE Flags: 0b1100000 Length: 194
Data:
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| d1 82 d1 8b 20 d1 87 d1 83 d0 b5 d1 88 d1 8c 3f |.... ..........?|
|00000010| 20 d0 b2 d0 ba d1 83 d1 81 d0 bd d0 be d0 b5 20 | .............. |
|00000020| d1 80 d0 be d0 b6 d0 b4 d0 b5 d0 bd d1 8c d0 b5 |................|
|00000030| 20 d0 bf d0 b5 d1 80 d0 b5 d0 b4 d0 be d0 bc d0 | ...............|
|00000040| be d0 bd d0 be d0 b9 20 d1 81 d1 82 d0 b8 d1 85 |....... ........|
|00000050| d0 be d0 b2 20 d0 bb d0 b8 d1 81 d1 82 d1 8b 20 |.... .......... |
|00000060| d0 ba d0 b0 d0 ba 20 d0 b4 d0 b5 d1 82 d0 be d1 |...... .........|
|00000070| 80 d0 be d0 b4 d0 bd d0 be d0 b5 20 d0 bf d0 b0 |........... ....|
|00000080| d1 80 d0 b5 d0 bd d1 8c d0 b5 20 d0 ba d0 b0 d0 |.......... .....|
|00000090| ba 20 d1 81 d0 bf d0 b0 d1 80 d1 82 d0 b0 20 d0 |. ............ .|
|000000a0| b3 d1 80 d1 8f d0 b7 d0 bd d0 be d0 b9 20 d1 81 |............. ..|
|000000b0| d1 80 d0 b0 d0 bc d0 be d1 82 d1 8b             |............    |
+--------+-------------------------------------------------+----------------+

Stress testing

# запускаем Grafana, Prometheus (для метрик Spring Boot), Influx
$ docker compose up -d

$ brew install k6

# запускаем нагрузку на наше приложение
$ k6 run --out influxdb=http://localhost:8086/k6 scripts/k6.js

# открываем браузер и смотрим http://localhost:3000

Для просмотра используемых потоков использовать параметры запуска:

-Djava.rmi.server.hostname=localhost \
  -Dcom.sun.management.jmxremote.port=1199 \
  -Dcom.sun.management.jmxremote.authenticate=false 

Литература

  1. Protocol
  2. RSocket протокол по-русски
  3. Олег Докука — Протокол RSocket — будущее реактивных приложений
  4. Олег Докука — RSocket to bring the next generation of Service Mesh

rsocket's People

Stargazers

 avatar

Watchers

James Cloos avatar Romanov Alexey avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.