RPC на примере gRPC. Когда применять и как работает

046049d66d9038b7edc3593a95350cb0

Введение

Доброго времени суток, коллеги. Я go разработчик, по-этому примеры будут преимущественно на нём. Хочу порассуждать о методах взаимодействия сервисов. Тема очень обширна. Зачастую мы пользуемся реализациями, которые не всегда подходят, т.к. не знаем куда применить ту или иную технологию. Я хочу попытаться начать закрывать этот пробел как у себя, так и у людей. Любые комментарии и конструктивные исправления приветствуются.

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

Основные методы взаимодействия сервисов

  1. REST (Representational State Transfer)

  • Архитектурный стиль, основанный на принципах, описанных в диссертации Роя Филдинга.

  • Использует стандартные методы HTTP (GET, POST, PUT, DELETE) для взаимодействия с ресурсами.

  • Обмен данных часто происходит в формате JSON или XML.

  1. RPC (Remote Procedure Call):

  • Механизм, позволяющий вызывать функции или процедуры на удаленном сервере, как если бы они были локальными.

  • Примеры: gRPC, SOAP, JSON-RPC.

  1. Message Brokers:

  • Асинхронный метод обмена сообщениями между различными компонентами системы.

  • Примеры: RabbitMQ, Apache Kafka, Apache ActiveMQ.

Что такое RPC?

Remote Procedure Call (RPC) — это протокол взаимодействия между клиентом и сервером, который позволяет клиенту вызывать процедуры (функции, методы) на удаленном сервере, как если бы они были локальными. Это обеспечивает абстракцию взаимодействия по сети и позволяет программам работать в распределенной среде, скрывая сложности передачи данных и выполнения удаленных операций.

Основные компоненты RPC

  • Клиент: Программа или компонент, инициирующий вызов удаленной процедуры.

  • Сервер: Программа или компонент, предоставляющий методы, которые могут быть вызваны удаленно.

  • Прокси: Клиент использует прокси-объект для вызова удаленных процедур на сервере, как если бы они были локальными.

  • Сериализация: процесс преобразования данных и параметров процедур в формат, который может быть передан через сеть (например, в формат JSON, бинарный формат и т. д.).

  • Транспорт: механизм передачи сериализованных данных между клиентом и сервером, например, http.

  • IDL (Interface Definition Language): Язык определения интерфейса, который определяет структуру и сигнатуры удаленных процедур.

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

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

Когда применять?

В чём основное отличие монолитного приложение от распределенного?

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

У нас одна программа, но в ней выделено множество отдельно связанных функциональностей. Все они обращаются за какими-то «просьбами» друг к другу. Т.к. у нас всё в одной программе, мы выделяем какие-то подходящие абстракции, связи между ними, которые отделяют реализацию и типизируют вызовы. Мы реализовали интерфейс, условно имплементировали его к себе в модуль и начинаем общаться через эти абстракции. По факту всё лежит в куче (иногда попадает в стек) в оперативной памяти, мы тянем функции по адресам, но в коде мы отделили реализации. Разработчик выдохнул, можно разрабатывать поверх продуманной архитектуры.

Однако, что если эти модули вынести в отдельные программы-сервисы? Это будут разные программы. Один сервис не может получить доступ к реализации другого сервиса напрямую, через его виртуальную оперативную память. Для связи нам нужно задействовать транспортные протоколы связи. Необходимо послать «просьбу» на сервис. По просьбе сервиса-клиента, этот сервис-сервер сам у себя активирует нужный участок памяти и вызовет функцию.

Методы связи разные, но нам по-прежнему надо писать человекоудобный код. А еще система остается одна, просто состоит из множества программ, а не из одной.

В итоге мы сели, рисовали и думали о плюсах и минусах подходов. Под наши нужды была выбрана распределенная микросервисная архитектура. Но как лучше связать связать сервисы? Хочется упростить написание, делать сразу читаемый и чистый код. Мы хотим как в монолите сделать абстракцию-интерфейс. С помощью абстракции мы могли бы вызывать нужные нам методы напрямую. С нужным интерфейсом мы бы точно знали реализацию, а не просто посылали бы запросы по HTTP. Звучит как RPC. Можно реализовать интерфейс и так же тянуть методы напрямую.

Почему gRPC?

И так, мы выбрали RPC подход. Но как нам лучше его реализовать, ведь выбор есть. Какой инструмент выбрать? Ситуации бывают разные, реализации бывают разные так же. Нужно знать и понимать какие инструменты бывают в принципе. Приведу некоторые для примера:

  • gRPC (gRPC Remote Procedure Calls) — это фреймворк, разработанный компанией Google, который предоставляет мощный и эффективный механизм для реализации RPC в различных языках программирования, включая Go. Он использует Protocol Buffers для определения структуры данных и HTTP/2 для передачи данных. Хотя Protobuf не единственный способ представления данных, но самый распиаренный. Появляются такие инструменты, например, как FlatBuffers, которые пытаются ускорить сериализацию данных. В gRPC также поддерживаются различные языки, что делает его очень гибким.

  • JSON-RPC — это простой протокол для обмена данными в формате JSON между сервером и клиентом. В Go есть библиотеки, такие как «github.com/gorilla/rpc/jsonrpc» или «github.com/ybbus/jsonrpc», которые могут использоваться для реализации JSON-RPC в Go.

  • Пакет net/rpc в стандартной библиотеке Go предоставляет базовую поддержку RPC. Он использует формат Gob (Binary JSON) для сериализации данных. Вместе с пакетом net/rpc/jsonrpc, он может обеспечивать JSON-RPC.

  • Twirp — это фреймворк для создания простых и эффективных API с использованием протокола RPC. Он также основан на Protocol Buffers.

Допустим, у меня несколько сервисов на go с общими моделями, и единственное что может произойти — добавиться новый сервис также на go. При том запросы будут максимально простые, вида: отправь сообщение, дай список сообщений. Тогда я подумаю о выборе стандартного пакета net/rpc.

Если потребуется что-то сложнее, то мне придётся писать дополнительные «обвязки» на net/rpc, а если добавятся сервисы на других языках, то нужно будет реализовывать совершенно другие интерфейсы для работы с ними.

По таким причинам обычно берут самый популярный, удовлетворяющий производительностью, хорошо документированный, мультиязычный и функциональный инструмент. В европейских регионах это gRPC. В европейских, потому что Азия живет в «своём мире», со своими решениями и перекликаются они с западом далеко не всегда.

Что такое gRPC?

gRPC — это фреймворк, т.е. достаточно комплексное решение. На данный момент он отправляет данные по протоколу http 2. На просторах github я находил попытки в http 3, но эти решения не пользуются большой популярностью.

gRPC реализует своё виденье http 2. Обычно поверх него не добавляются шифрование в виде протокола TLS, однако при необходимости его можно добавить.

Передача данных идет в бинарном формате. Данных получается меньше, они более оптимизированы, чем тот же json (весь json объект будет приведен в символы ASCII для передачи, что влечёт за собой передача неоптимальных и ненужных байтов). Сериализация проходит в бинарную систему для более оптимальной передачи.

Для примера, я хочу создать пользователя с именем 321 (такой себе Оруэлл, людей называем по номерам=)). Тогда мне надо вызвать метод, с его данными.

Данные в protobuf:

syntax = "proto3";

message User {
  string name = 1;
}

Запрос:

  1. POST /path/to/resource/CreateUser HTTP/2

  2. Host: example.com 

  3. Content-Type: application/grpc 

  4. User-Agent: grpc-go/1.42.0  

  5. Content-Length: 5

  1. 0a033231

Разбор по строкам:

1: Указывает метод запроса и версию HTTP (HTTP/2.0).

2: Заголовок Host указывает на адрес сервера.

3: Указывает, что контент запроса представляет собой gRPC.

4: Информация о пользователе, указывающая на использование gRPC на сервере go 1.42.0 версии.

5: Указывает на длину бинарного тела запроса в байтах.

6: Тело запроса, представленное в виде 16-ричной системы для более удобной читаемости. Оно содержит бинарное представление protobuf-сообщения.

Разбор тела запроса:

0a: Тег и тип поля. В данном случае, 0a указывает на поле с тегом 1 (поле name) и типом данных «байтовая строка» (string).

03: Длина следующей строки в байтах. Здесь 03 означает, что следующая строка имеет длину 3 байта.

3231: Это ASCII-кодированное представление строки »321» в шестнадцатеричной системе счисления.

Дополнительно заголовки шифруются по принципам http 2 с помощью алгоритма HPACK.

На чём реализуется клиент и сервер не так важно, т.к. поддержка идёт множества популярных языков. Можно с помощью общей схемы protobuf реализовать go сервер, а клиент на python без трудностей, используя уже готовые и расписанные решения.

Возможности gRPC

Хотелось бы кратко структурировать возможности gRPC, которые хорошо используют функционал http 2.

  1. Протокол-независимая сериализация:

  • По умолчанию используется Protocol Buffers (ProtoBuf) для сериализации данных.

  • Возможна поддержка других форматов сериализации, таких как JSON.

  1. Поддержка множества языков:

  • gRPC поддерживает множество языков программирования, включая C++, Java, Python, Go, Ruby, C#, Node.js, и многие другие.

  1. Одновременные запросы (Multiplexing):

  1. Streaming:

  • Поддерживает как однонаправленный, так и двунаправленный поток данных.

  • Клиенты и серверы могут отправлять последовательности сообщений.

  1. Автоматическая генерация кода:

  1. Сжатие данных:

  1. Поддержка SSL/TLS:

  1. Библиотека метаданных:

  1. Отмена запросов:

  1. gRPC-заголовки:

  1. Дополнительные аутентификационные механизмы:

  1. gRPC Web:

  1. Средства мониторинга и трассировки:

  • Интеграция с инструментами мониторинга, такими как Prometheus, и трассировки, такими как Jaeger.

  1. Встроенная поддержка статусов:

В репозитории github есть примеры <- использования gRPC.

Пару слов о gateway

gRPC можно использовать и для генерации REST API, с помощью grpc-gateway. Останавливаться не буду, дам пример и объяснение смысла этого. Grpc-gateway генерирует интерфейсы под RPC и REST. Таким образом к серверу одновременно можно подключаться и по RPC, и по REST. Например, браузер посылает запрос по REST для получения пользователей, а какой-нибудь промежуточный сервис будет вызывать методы RPC для создания этих пользователей.

Я находил не так много примеров реализации gateway, так что хотел бы добавить свой пример <-. В Makefile можно найти пример генерации интерфейсов.

Заключение

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

Я буду корректировать и дополнять информацию статьи и свои знания, если будет конструктивная критика. Прошу Вас о ней, буду благодарен. Хорошего дня. =)

© Habrahabr.ru