Введение

Доброго времени суток, коллеги. Я 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. Автоматическая генерация кода:

  • Генерация клиентского и серверного кода на основе описания API в proto файле (Protocol Buffers).

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

  • Возможность сжимать данные для уменьшения объема трафика.

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

  • Возможность обеспечивать безопасную передачу данных с использованием SSL/TLS.

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

  • Возможность отправлять и получать метаданные в заголовках запросов.

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

  • Клиенты могут отменять отправленные запросы, что особенно полезно в асинхронных сценариях.

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

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

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

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

  1. gRPC Web:

  • Возможность использовать gRPC в веб-браузерах с использованием 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 и вообще распределенную архитектуру зависит только от потребностей.

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

Комментарии (5)


  1. Throwable
    18.01.2024 07:14
    +1

    gRPC - это всё-таки клиент-серверный протокол, нежели полностью распределённый, как например RMI или CORBA. Основное их отличие в том, что endpoint также является объектом, который можно сериализовать и передавать по сети, таким образом осуществляя двустороннюю связь. Одним из кейсов является remote invoke with callback, где помимо структур передается локальный объект, которому удаленный сервис передаст результат. Все же современные протоколы работают лишь в одну сторону.


  1. savostin
    18.01.2024 07:14

    А расскажите про Восток кто-нибудь?

    Всегда было интересно на каких технологиях «Китайский Амазон» (есть же у них аналог?) и вообще программисты работают. Имхо трафик и нагрузки там на порядок больше чем в любом крупном по европейским или американским меркам.


    1. Bearatol Автор
      18.01.2024 07:14

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

      Спасибо Вам, я с думал об этом.)


  1. Dmitrii_Zz
    18.01.2024 07:14

    как у себя, так и у людей.

    У каких людей?

    Но как лучше связать связать сервисы?

    2xСвязаться

    В статье по сути нет информации о том капитан плюсы и минусы от использования той или иной технологии, большая часть статьи это описание технологии. На мой взгляд, тут более наглядно описано https://habr.com/ru/amp/publications/729528/


    1. Bearatol Автор
      18.01.2024 07:14

      1) У людей, которым будет интересно.

      2) Я не вижу минусов или плюсов каких-то определенных в сравнении. Тут выбор зависит от требований, т.к. все инструменты прежде всего идут под определенные задачи. Надо брать определенный кейс все же и рассматривать под него плюсы и минусы.

      Большое спасибо Вам за дополнительные материалы. Я почитаю, возможно узнаю много нового.