Введение
Доброго времени суток, коллеги. Я go разработчик, по-этому примеры будут преимущественно на нём. Хочу порассуждать о методах взаимодействия сервисов. Тема очень обширна. Зачастую мы пользуемся реализациями, которые не всегда подходят, т.к. не знаем куда применить ту или иную технологию. Я хочу попытаться начать закрывать этот пробел как у себя, так и у людей. Любые комментарии и конструктивные исправления приветствуются.
В данной статье хочу разобрать как работает gRPC, что он может, а так же когда и зачем его использовать.
Основные методы взаимодействия сервисов
-
REST (Representational State Transfer)
-
Архитектурный стиль, основанный на принципах, описанных в диссертации Роя Филдинга.
-
Использует стандартные методы HTTP (GET, POST, PUT, DELETE) для взаимодействия с ресурсами.
-
Обмен данных часто происходит в формате JSON или XML.
-
RPC (Remote Procedure Call):
-
Механизм, позволяющий вызывать функции или процедуры на удаленном сервере, как если бы они были локальными.
-
Примеры: gRPC, SOAP, JSON-RPC.
-
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; }
Запрос:
-
POST /path/to/resource/CreateUser HTTP/2
-
Host: example.com
-
Content-Type: application/grpc
-
User-Agent: grpc-go/1.42.0
-
Content-Length: 5
-
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.
-
Протокол-независимая сериализация:
-
По умолчанию используется Protocol Buffers (ProtoBuf) для сериализации данных.
-
Возможна поддержка других форматов сериализации, таких как JSON.
-
Поддержка множества языков:
-
gRPC поддерживает множество языков программирования, включая C++, Java, Python, Go, Ruby, C#, Node.js, и многие другие.
-
Одновременные запросы (Multiplexing):
-
Позволяет отправлять несколько запросов и получать несколько ответов одновременно на одном соединении.
-
Streaming:
-
Поддерживает как однонаправленный, так и двунаправленный поток данных.
-
Клиенты и серверы могут отправлять последовательности сообщений.
-
Автоматическая генерация кода:
-
Генерация клиентского и серверного кода на основе описания API в proto файле (Protocol Buffers).
-
Сжатие данных:
-
Возможность сжимать данные для уменьшения объема трафика.
-
Поддержка SSL/TLS:
-
Возможность обеспечивать безопасную передачу данных с использованием SSL/TLS.
-
Библиотека метаданных:
-
Возможность отправлять и получать метаданные в заголовках запросов.
-
Отмена запросов:
-
Клиенты могут отменять отправленные запросы, что особенно полезно в асинхронных сценариях.
-
gRPC-заголовки:
-
Поддержка кастомизированных заголовков для передачи дополнительной информации.
-
Дополнительные аутентификационные механизмы:
-
Поддержка аутентификации с использованием токенов и других механизмов.
-
gRPC Web:
-
Возможность использовать gRPC в веб-браузерах с использованием gRPC Web.
-
Средства мониторинга и трассировки:
-
Интеграция с инструментами мониторинга, такими как Prometheus, и трассировки, такими как Jaeger.
-
Встроенная поддержка статусов:
-
Поддержка стандартных и пользовательских кодов статуса.
В репозитории github есть примеры <- использования gRPC.
Пару слов о gateway
gRPC можно использовать и для генерации REST API, с помощью grpc-gateway. Останавливаться не буду, дам пример и объяснение смысла этого. Grpc-gateway генерирует интерфейсы под RPC и REST. Таким образом к серверу одновременно можно подключаться и по RPC, и по REST. Например, браузер посылает запрос по REST для получения пользователей, а какой-нибудь промежуточный сервис будет вызывать методы RPC для создания этих пользователей.
Я находил не так много примеров реализации gateway, так что хотел бы добавить свой пример <-. В Makefile можно найти пример генерации интерфейсов.
Заключение
Я считаю, что надо чаще поднимать темы проектирования. Вопросы о применении технологий, решений и архитектуры должны решаться верно сразу, т.к. ошибки в фундаментальных выборах влекут за собой большие потери и тех. долг. Стоит ли применять gRPC, RPC и вообще распределенную архитектуру зависит только от потребностей.
Я буду корректировать и дополнять информацию статьи и свои знания, если будет конструктивная критика. Прошу Вас о ней, буду благодарен. Хорошего дня. =)
ссылка на оригинал статьи https://habr.com/ru/articles/787164/
Добавить комментарий