Support us

Сказ о том, как разбирался в gRPC и Websocket

1 комментарий
Сказ о том, как разбирался в gRPC и Websocket

Эта тема меня заинтересовала еще пару лет назад и не было возможности, а главное повода в ней разбираться. Интерес подогревали конференции, статьи на специализированных сайтах, видео. Пример такой статьи. Одним словом — хайп захватил мой разум. За это нужно сказать большое спасибо маркетингу от Google и аффилированных с ними лиц раз я пару лет к ряду размышлял об этих технологиях. Чтобы сэкономить читателю время на прочтение моей статьи, скажу коротко о мыслях, к которым пришел:

я не стал бы выбирать gRPC как основу для выбора общения между сервисами, тк является слишком громоздким и лишает вас от гибкости в принятии решения при проектировании;

я не стал бы выбирать websocket для общения между сервисами или браузером юзера, см пункт ниже;

большинство задач, ставящихся перед веб приложением, решается с помощью HTTP/2 протокола;

перед использованием новой технологии, лучше взвесить все за и против.

gRPC

​​​​​​​Исследовать данный вопрос я начал с сайта gRPC. Тогда я еще не думал, что вступил на территорию старых холиваров: RPC vs RESTHTTP vs Sockets, etc. Есть два варианта определения:

  1. gRPC — google implementation of remote procedure call.
  2. The letters «gRPC» are a recursive acronym which means, gRPC Remote Procedure Call.

А если поискать информацию подольше, то окажется что под gRPC понимается семейство rpc протоколов разных версий, где g была goggle/green/etc. Эта технология не монолитная. И считать ее библиотекой, протоколом я бы не стал, скорее фреймворк. Составными частями его являются на сегодняшний день по словам одног из работчиков:

Проект Protobuf

HTTP/2 протокол

Расширение protobuf scheme в виде IDL (intermediate definition language)

Составляющие библиотеки

Protocol Buffers

Protocol buffers библиотека предоставляющая механизм сериализации данных вне зависимости от платформы и используемого языка программирования. Это полезно в случае если у нас есть два сервиса, написанных на разных языках, например java и с++. И нам необходимо между ними обмениваться данными. Можно использовать старый проверенный JSON как формат обмена сообщениями. Но если у нас есть ограничение на размер пакета сообщения или на ресурсы процессора/памяти, то стоит обдумать как можно решить проблему с сериализацией как средство оптимизации.

Примеры работы с Protobuf на картинках ниже:

Пример POJO объекта
Сериализация объекта на Java стороне
Десериализация объекта на С++ стороне

Данная библиотека поддерживает языки программирования С++, С#, Dart, Go, Java, Python.

Как у любой другой библиотеки, Protobuf имеет аналоги: FlatBuffersThe Apache ThriftCap’n Proto. К последней я бы обратил внимание побольше, т. к. в тестах выдает очень интересные результаты. Ссылки на сравнение:

Protobuf vs Cap’n Proto

Apache Thrift vs GRPC

Apache Thrift vs Protocol Buffers vs Fast Buffers

HTTP/2

Транспортным уровнем для gRPC является НТТР/2, до этого им был SPDY. По своей сути это одно и тоже, т. к. SPDY превратился НТТР/2 после рассмотрения в W3C как кандидат на следующую версию НТТР.

Для описания того, что же есть такое HTTP/2 можно почитать спецификацию. Уверен, что многие так не сделают и пойдут читать что-то иное. Поэтому советую сразу статью описывающую HTTP/2 сделал Ilya Grigorik High Performance Browser Networking, более короткую версию можно почитать на портале Google Fundamentals, я лишь перескажу основные тезисы и историю.

Первую основную вещь которую стоит упомянуть про HTTP/2 это то, что он не заменяет старый протокол на новый, а лишь расширяет его. Основными целями для HTTP/2 являются снижение задержек за счет обеспечения полного мультиплексирования запросов и ответов, минимизации накладных расходов протокола посредством эффективного сжатия полей заголовков HTTP и добавления поддержки приоритизации запросов и отправки на сервер.

HTTP/2 никак не изменяет семантику приложения HTTP. Все основные понятия, такие как методы HTTP, коды состояния, URI и поля заголовка, остаются на своих местах и не изменяются. Вместо этого HTTP/2 обновляет способ форматирования (кадрирования) данных и их транспортировки между клиентом и сервером, которые управляют всем процессом, и скрывает всю сложность наших приложений в новом слое кадрирования. В результате все существующие приложения могут быть доставлены без изменений. Итак суммируем какие же основные основные изменения появились в HTTP/2:

Протокол стал бинарным, а не текстовым. Байтовое представление само по себе более эффективно передавать по сети, хоть и убирают возможность удобного чтения сообщений для человека (binary data);

Мультиплексирование (multiplexing). HTTP/1.x имел проблему с медленными запросами, которые могли остановить соединение для других запросов. В новой версии запросы выполняются параллельно и через одно соединение;

Компрессия заголовков уменьшает размер передаваемых сообщений (compression);

Приоритизация сообщений (prioritization);

Технология сервер пуш, позволяющая серверу отправить клиенту без запроса на них дополнительные кэшируемые ресурсы (ассеты, картинки, css). Клиент может отказаться от принятия данных (server push/full-duplex).

Фактически HTTP/2 расширяет первую версию протокола добавляя новые фичи. Можно провести аналогию со спортом: HTTP стал быстрее, выше, сильнее.

Interface definition language (IDL)

Это уже расширение синтаксиса языка Proto Buffers. Он позволяет описывать сервисы для RPC вызовов и на основе этого генерировать базовую имплементацию. Она будет являться стабовой и на любой вызов будет возвращать код ошибки.

Protobuf as IDL

gRPC предлагает четыре способа взаимодействия между сервисами

Unary RPC

Server streaming RPCs

Client streaming RPCs

Bidirectional streaming RPCs

При любом взаимодействии gRPC гарантирует очередность сообщений. Также стоит упомянуть об основных понятиях технологии:

channels — абстракция соединения между хостом и клиентом

metadata — информация описывающая запрос/ответ, аналог заголовков в HTTP

deadlines/timeout — максимальное время на выполнение RPC вызова.

Интересно, что таймаут первичного запроса распространяется на под-запросы. Т.е. под-запросы будут отменены если глобальный таймаут будет исчерпан. Также дополнительными фичами идут:

Механизм авторизации

ssl/tls шифрование данных, которое проистекает из HTTP/2

поддержка Google/Custom-token авторизаций. Авторизация с помощью Google Identity and Access Management или аналогичным сторонним сервисов

Модель ошибок

Google message types описывает набор сущностей для работы со статусами сообщений, ошибками выполнения предоставляемые из коробки.

Google example of message описывает маппинг HTTP/2 кодов выполнения к gRPC кодам

Пример имплементации gRPC сервиса

Интересным моментом является то, что до сих пор браузеры не имели поддержки данного формата взаимодействия. И gRPC жил только на стороне бекенда. Поэтому относительно недавно для организации общения бэкенда и фронта использовались прокси/gateway, транслирующие RPC вызовы в REST запросы.

Недавно Google создал две библиотеки для Node.Js экосистемы:

gRPC C++ wrapper library

gRPC pure JS lib

Нужно отметить, что для их работы необходимо иметь все также нужен прокси (Прим. Envoy homepage).

Для локального дебага gRPC сервиса можно использовать Bloom RPC.

А если интересно почитать про лучшие наработки в данной сфере то стоит пройти по ссылке — gRPC Best Practice.

Из всего выше перечисленного можно выделить следующие sale-points данной библиотеки:

Compatibility

single «source of truth»

handle non-breaking changes

cross platforms/languages

Performance

network: connection handling

speed: transmission of data

cpu: improved resource usage

Maintenance

tracing: follow requests through infrastructure

monitoring: live statistics

debugging: unified logging

Benchmarks

И по итогу какое впечатления на меня произвел gRPC? Вцелом для меня эта библиотека обёртка над http/2 протоколом, предоставляющая реактивное API для разработки и свой сериализатор для уменьшения объема сообщения. Хорошо или плохо это было говорить рано. Не первая и не последняя такая библиотека появилась в списках возможных инструментов разработчиков. После этого мне захотелось посмотреть на аналоги позволяющие увеличить эффективность передачи данных. И первым на ум пришел WebSocket.

WebSocket

WebSocket — протокол связи, предоставляющий full-duplex соединение поверх TCP. Этот протокол был стандартизирован IETF и W3C в 2011 году.

WebSocket предоставляет API приложению, который использует одно TCP соединения в качестве транспортного уровня. Идея протокола WebSocket состоит в повторном использовании установленного TCP-соединения между клиентом и сервером. Клиент устанавливает соединение, выполняя процесс так называемого рукопожатия (handshake). Этот процесс начинается с того, что клиент отправляет серверу обычный HTTP-запрос. В этот запрос включается заголовок Upgrade, который сообщает серверу о том, что клиент желает установить WebSocket-соединение.

В отличие от обычных междоменных HTTP-запросов, запросы WebSocket не ограничиваются cross-origin политиками. Поэтому серверы WebSocket должны проверять заголовок «Origin» на соответствие ожидаемым источникам во время установления соединения, чтобы избежать Cross-Site атак, которые могут быть возможны при аутентификации соединения с помощью файлов cookie или HTTP-аутентификации.

Данные, передаваемые с помощью данной технологии, могут быть представлены в бинарном виде или текстовом. Их формат задается с помощью под-протоколов, описываемые заголовками Sec-WebSocket-Extensions и Sec-WebSocket-Protocol. Первый описывает способ передачи данных, их сжатие например. Второй описывает протокол кодирования передаваемых данных. Например, soap, wamp.

В базовом варианте протокол не имеет шифрование («ws»). Это решили добавлением SSL/TLS в данный протокол и выпуском новой версии («wss»).

Пример кода
HTTP/2 vs Websocket

Любопытно, что были даже энтузиасты, которые предлагали портировать websocket на http2

По своей сути, данный протокол — это отказ от всех наработок HTTP. Это стоит рассматривать как способ вернуть прямое и полное управление данными в руки разработчиков без наложения на них ограничений. Но все это были лишь мои голые размышления, стоило посмотреть и поискать на тесты производительности.

Тесты

Первое что пришло в голову поискать для сравнения это простой тест REST vs gRPC.

Далее текст, написанный в в виде цитаты, будет представлять из себя выводом из теста.

etcd JSON RPC vs gRPC: etcd: distributed key-value store with grpc/http2

 «As you see, gRPC is much faster and lighter than jsonrpc. Not only performant, but also gRPC is easier to reason about concurrency. HTTP/1.x requires multiple TCP connections for concurrent requests, while HTTP/2 can have multiple requests over one single TCP connection and still process them asynchronously. Tests above show that gRPC with multiple clients solely speeds up gRPC by 80%, without opening multiple TCP connections».

google JSON RPC vs gRPC: Announcing gRPC Alpha for Google Cloud Pub/Sub

«There are two major factors at work here: more efficient data encoding and HTTP/2. gRPC keeps data in binary both in client memory and on the wire by building on HTTP/2 and Protocol Buffers. This eliminates processing and space required for string encoding schemes such as Base64 or JSON. In addition, HTTP/2 itself makes things go faster with multiplexed requests over a single connection and header compression».

Из этих тестов можно сделать вывод, что gRPC даст вам прирост в производительности. Основными составляющими в увеличении производительности выделяют http2 и protocol buffers. Насколько велика разница между этими составляющими косвенно проливает следующий тесты.

Сторонний тест на GO: gRPC vs. REST: Performance Simplified

Итог: «While REST over HTTP/2 scales about as well as gRPC does, in terms of pure performance gRPC brings a reduction in the processing time of 50–75% throughout the entire workload range».

Т.е. вклад Protocol Buffers в производительности где-то 50–75% пропускной способности.

Также из данного текста можно увидеть как влияет использование http/1 на количество параллельно выполняемых задач в приложении по сравнению с http2 (для любопытных описание работы горутин и виртуальных машин в GO).

Сторонний тест по пересылке сообщений большого объема: Sending files via gRPC«I think it’s evident that for this workload a raw transfer of bytes over http2 should be faster than the gRPC one as there’s no encoding and decoding of messages — just raw transmission of data.»

Интересный результат для сообщений больших размеров: пересылка сырых данных по http2 является гораздо более быстрым вариантом, чем использование gRPC. По всей видимости для такого краевого случая оверхед генерируемого кода с помощью Protocol Buffers начинает сказываться на производительности.

Косвенно данный факт подтверждается в данной статье — Бенчмарк RPC систем (и Inverted Json).

«GRPC: вариант для питона производит много шаблонного питон кода, т. е. обработчик получается тяжелым и быстро упирается в CPU, для компилируемых языков вероятно такой проблемы нет». 

Все это были тесты про http2 vs http2 в большинстве случаев. Что можно найти только про сериализацию?

RPC framework evaluation Тут интересны графики:

1 Server, 1 Client (1 Thread)
1 Server, 2 Client (1 Thread)

 Сторонний тест стримминг технологий: A comparison of different data streaming options

«If you need to stream to the browser it’s probably best to use Chunked Transfer Encoding over HTTP/1.1. However, if you are doing systems programming and need to communicate between machines, I wouldn’t hesitate to use GRPC».

Вывод, как мне кажется не особо интересен. Но в статье находятся гораздо более любопытные графики, чем слова.

Total transfer size from client to server
Total transfer size from server to client.

 Здесь меня заинтересовало сравнение websocket с gRPC. Удивительно было для меня, что gRPC идет рука об руку с websocket. При вносимом, как кажется оверхеде, библиотека работает также как websocket. Я считаю, что этот результат является впечатляющим для библиотеки.

Мысли и размышления

​​​​​​​Итак, на тот момент наступления 2010 года HTTP не уже не мог похвастаться такой эффективностью для веб приложений, как в начале 1990. Не удивительно, что за 20 лет появилась потребность в большей производительности при передаче данных. WebSocket как и gRPC появились на свет как ответ на эту потребность.

Websocket предлагает прямое использование сокетов без какого-либо оверхеда в full-duplex режиме без каких-либо ограничений. gRPC же предлагает в обратную сторону все плюсы HTTP/2, но в нагрузку тянет за собой дополнительные зависимости из библиотек и привязке к экосистеме Google.

В данный момент уже половина сервисов перешла на HTTP/2. Таким образом можно считать его де-факто уже стандартом современного интернета.

Но в сети можно найти уже новую версию HTTP/3, которая улучшает вторую версию, переходя с TCP на UDP. Почитать об этом можно HTTP/3QUIC и «Протокол QUIC: переход Web от TCP к UDP».

Для меня удивительно то, что первая версия стандарта продержалась более двух десятилетий, захватив весь интернет. Второй возможно удастся продержаться хотя бы десятилетие. По всей видимости, уже и рядовой пользователь прыгнул в технологическую сингулярность.

К чему я это все веду? Временные промежутки по обновлению стандартов стали сокращаться. Для повышения прироста производительности для рядового проекта, возможно нет необходимости переходить на новый стек или платформу, если задача решается стандартными средствами. Для экстремальных задач стоит искать соответствующие решения.

Звучит, думаю, банально. Но я не раз слышал, да и впрочем сам говорил подобное, что стоит привнести в проект что-то новое, попробовать новую технологию.

В текущих условиях я предпочту простой REST поверх HTTP/2. Если мне будет необходим life-update/server-push/streaming, то стоит вспомнить о давно реализованном Event-Source events. Взгляните на поддержку в браузерах на сaniuse.com.

Для приближения к производительности gRPC стоит подумать о переходе на новую версию HTTP и об отдельном использовании сериализаторов, а не в купе со всей платформой. Как показывают тесты, можно найти варианты сериализаторов более производительные, чем Proto Buffers. Уверен, на практике прикрутить его не составит труда.

Также в текущую эру микросервисов, которые должны быть маленькими и независимыми, вы себе облегчите жизнь если не будете тащить в проект что-то огромное и неповоротливое под капотом.

Я не говорю, что WebSocket, gRPC будут забыты и перестанут использоваться. WebSocket как инструмент необходим для задач с потребностью в full-duplex обмене данными при минимуме затрат ресурсов на передачу заголовков и пакетов. gRPC для более быстрого построение систем на основе разработок от Google, да и продвижение от такого технологического гиганта никто не отменял. Они в конечном счете станут специализированным инструментарием.

Дополнителные источники

Allan Denis. Will WebSocket survive HTTP/2?

Ilya Grigorik, Surma. Introduction to HTTP/2

Ilya Grigorik, Surma. WebSocket

Alexander Zlatkov. Как работает JS: WebSocket и HTTP/2+SSE. Что выбрать

HTML Specification about SSE

Туториал по использованию SSE от Mozilla

Server-Sent Events (SSE) In JAX-RS 

Место солидарности беларусского ИТ-комьюнити

Далучайся!

Хотите сообщить важную новость? Пишите в Telegram-бот

Главные события и полезные ссылки в нашем Telegram-канале

Обсуждение
Комментируйте без ограничений

Релоцировались? Теперь вы можете комментировать без верификации аккаунта.

Комментарий скрыт за нарушение правил комментирования.
[censored - П. 4.1.2. Пользовательского соглашения — https://devby.io/pages/polzovatelskoe-soglashenie]