Введение в gRPC
Так как Data Engineer работают преимущественно с Python, то примеры и некоторые описания в статье приведены с упоминанием Python. Однако, сама технология gRPC работает с различными языками.
Что такое gRPC и зачем он нужен
gRPC — это фреймворк удалённых вызовов процедур (Remote Procedure Call, RPC), разработанный Google. Он позволяет приложениям, работающим на разных машинах, вызывать функции друг друга, будто они локальные, при этом всё работает поверх HTTP/2 и с использованием бинарного формата сериализации данных (Protocol Buffers).
gRPC — это способ сделать так, чтобы один сервис (например, микросервис A) мог «позвать» функцию в другом сервисе (микросервисе B), передав ей параметры и получив результат, как будто это просто функция в Python, но на самом деле — это запрос по сети.
Где применяется gRPC
1. Взаимодействие микросервисов: для высокопроизводительного обмена данными между микросервисами внутри одной системы (например, в распределенной архитектуре бэкенда).
- Быстрая сериализация данных через Protocol Buffers
- HTTP/2 — поддержка мультиплексирования
- Строгая типизация и контракты
2. Потоковая передача данных: двунаправленная потоковая передача данных (подходит для сервисов, работающих с большими объемами данных в реальном времени).
- Видеостриминг
- Онлайн-чат
- Передача телеметрии от IoT-устройств
3. Мобильные приложения: При использовании с Protocol Buffers gRPC позволяет минимизировать объем передаваемых данных, что важно для мобильных устройств с ограниченными ресурсами и нестабильным соединением.
- Малый объем сообщений
- Быстрая обработка
- Поддержка различных языков (включая Swift, Kotlin)
4. Внутренние API в корпорациях: Для взаимодействия между внутренними сервисами, где нет необходимости в человекочитаемом формате (как JSON).
- Обработка платежей
- Анализ данных
- Управление пользователями и правами доступа
5. Машинное обучение и аналитика: высокоскоростная передача данных между ML-моделями и обработчиками в распределённых системах.
- Вызов моделей TensorFlow/ONNX через gRPC
- Передача результатов аналитики в реальном времени
6. Встраивание в облачные и Kubernetes-экосистемы: в cloud-native решениях.
- Service Mesh (Istio, Linkerd)
- Прокси-сервисы (Envoy)
- Интеграция с Prometheus, OpenTelemetry
gRPC — это фреймворк, разработанный Google с акцентом на межъязыковую совместимость. Он поддерживает множество популярных языков, включая:
- Python
- Java
- Go
- C++
- C# / .NET
- Node.js
- Ruby
- PHP
- Objective-C
- Kotlin и др.
Благодаря использованию .proto
файлов и автогенерации кода, gRPC позволяет без усилий интегрировать сервисы, написанные на разных языках, в единую распределённую систему.
Как это выглядит на практике (в случае с Python)
Ты пишешь .proto
файл вроде:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
syntax = "proto3"; package myapp; // Определение сообщения (структуры данных) message HelloRequest { string name = 1; } message HelloReply { string message = 1; } // Определение сервиса (API) service Greeter { rpc SayHello(HelloRequest) returns (HelloReply); } |
gRPC на основе этого файла сгенерирует Python-код, который ты используешь на клиенте и сервере. Все типы данных уже определены, никакого JSON вручную.
Протокол RPC (Remote Procedure Call)
RPC (Remote Procedure Call) — это подход в программировании, при котором одна программа может вызывать функцию, которая физически выполняется на другой машине, как будто она локальная.
Ключевая идея: Ты вызываешь удалённую функцию (процедуру) как обычную, а всё сетевое взаимодействие происходит «под капотом».
Как работает удаленный вызов функции?
Когда ты вызываешь удалённую функцию через RPC
Клиент вызывает локальную «заглушку» (stub-функцию).
Stub:
- сериализует аргументы,
- отправляет их по сети на сервер.
Сервер:
- получает запрос,
- десериализует данные,
- вызывает реальную функцию с переданными аргументами.
Ответ возвращается по той же схеме обратно.
Схема от ByteByteGo:
Видео от ByteByteGo «What is RPC? gRPC Introduction» (YouTube)
Еще 1 схема по gRPC «Как выполняется удаленный вызов процедуры по сети»
В RPC-системе сервер реализует набор функций, доступных для удаленного вызова. Клиентское приложение может сгенерировать заглушку с абстракциями, которые можно использовать напрямую для взаимодействия с этими функциями сервера.
Определение gRPC-сервиса ProductInfo с помощью Protocol Buffers
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// ProductInfo.proto syntax = "proto3"; package ecommerce; service ProductInfo { rpc addProduct(Product) returns (ProductID); rpc getProduct(ProductID) returns (Product); } message Product { string id = 1; string name = 2; string description = 3; } message ProductID { string value = 1; } |
Чтобы понять, как происходит удаленный вызов процедур по сети, рассмотрим сервис ProductInfo. Одной из функций, которые были реализованы в этом сервисе, была getProduct
; с ее помощью клиент мог получить описание товара, предоставив его ID. Действия, выполняемые в ходе вызова удаленной функции, показаны на рисунке.
Вызов клиентом функции getProduct
из сгенерированной заглушки можно разделить на следующие ключевые этапы.
- Клиентский процесс вызывает функцию
getProduct
из сгенерированной заглушки. - Клиентская заглушка создает HTTP-запрос типа POST с закодированным сообщением. В gRPC все запросы передаются методом POST и с заголовком content-type вида
application/grpc
. Удаленная функция, которая при этом вызывается (/ProductInfo/getProduct
), передается в отдельном HTTP-заголовке. - HTTP-запрос с сообщением отправляется на серверный компьютер по сети.
- Получив сообщение, сервер анализирует его заголовки, чтобы узнать, какую функцию нужно вызвать, и передает его заглушке сервиса.
- Заглушка сервиса преобразует байты сообщения в структуры данных конкретного языка.
- Затем сервис, используя преобразованное сообщение, вызывает локальную функцию
getProduct
. - Ответ функции сервиса кодируется и возвращается клиенту. Ответ с сообщением проходит тот же процесс, который мы наблюдали на клиентской стороне (ответ → кодирование → HTTP-ответ, готовый к передаче по сети). Сообщение распаковывается, и его значение возвращается ожидающему клиентскому процессу.
Когда gRPC-клиент обращается к gRPC-сервису, его gRPC-библиотека выполняет удаленный вызов процедуры по HTTP/2, используя Protocol Buffers и маршалинг. Серверная сторона распаковывает полученный запрос и вызывает соответствующую процедуру с помощью Protocol Buffers. Затем аналогичным образом сервер возвращает ответ клиенту. Для передачи данных gRPC задействует HTTP/2 — высокопроизводительный двоичный протокол обмена сообщениями с поддержкой двунаправленности.
Пример для понимания
Как выглядит обычный вызов:
1 |
result = add(2, 3) # локальная функция |
Как выглядит RPC-вызов (примерно):
1 |
result = rpc_client.add(2, 3) # эта функция вызывается на удалённом сервере |
Ты не видишь ни requests, ни сокетов — вызов абстрагирован.
Видео «Что такое gRPC и Protobuf?» (YouTube)
Примеры реализаций RPC
Название | Протокол | Формат данных | Особенности |
---|---|---|---|
gRPC | HTTP/2 | Protobuf (бинарный) | Быстрый, стриминг, автогенерация кода |
Thrift | TCP/HTTP | Бинарный | Разработан Facebook |
JSON-RPC | HTTP | JSON | Простой, но медленнее |
XML-RPC | HTTP | XML | Устаревший формат |
SOAP | HTTP/SOAP | XML | Очень «тяжёлый», часто в корпоративных системах |
Зачем нужен RPC?
- Инкапсулирует сетевую логику: ты вызываешь функцию, а не пишешь HTTP-запросы.
- Повышает согласованность API между клиентом и сервером (общая схема).
- Ускоряет взаимодействие сервисов в распределённых системах.
- Позволяет использовать типизированные интерфейсы (особенно важно для больших команд и продакшн-систем).
Плюсы и минусы gRPC. Сравнение gRPC и REST
Преимущества и недостатки gRPC
Преимущества gRPC
Преимущества, предоставляемые технологией gRPC, — залог ее популярности. Некоторые из них перечислены ниже.
- Высокая эффективность. Вместо текстовых форматов, таких как JSON или XML, для взаимодействия с сервисами и клиентами gRPC использует двоичный протокол на основе Protocol Buffers. Кроме того, Protocol Buffers в gRPC работает поверх HTTP/2, что еще больше ускоряет межпроцессное взаимодействие. Благодаря этому gRPC — одна из самых эффективных технологий IPC.
- Простые, четкие интерфейсы и форматы сообщений. gRPC исповедует контрактный подход к разработке приложений. Сначала определяются интерфейсы сервисов, и только потом начинается работа над их реализацией. Поэтому, в отличие от OpenAPI/Swagger (для REST-сервисов) и WSDL (для веб-сервисов SOAP), gRPC делает процесс разработки приложений простым, но в то же время согласованным, надежным и масштабируемым.
- Сильная типизация. Поскольку для определения gRPC-сервисов используется Protocol Buffers, их контракты однозначно определяют типы данных, которыми будут обмениваться приложения. Это делает разработку распределенных приложений куда более стабильной, поскольку статическая типизация позволяет избежать большинства ошибок выполнения и совместимости, возникающих в ситуациях, когда облачно ориентированные проекты создаются сразу несколькими командами и с помощью разных технологий.
- Многоязычие. Протокол gRPC рассчитан на поддержку многих языков программирования. Protocol Buffers позволяет определять gRPC-сервисы без привязки к конкретному языку. Поэтому вы можете взаимодействовать с любым существующим gRPC-сервисом или клиентом, используя тот язык, который вам нравится.
- Двунаправленная потоковая передача. gRPC имеет встроенную поддержку потоковой передачи на стороне клиента и сервера, интегрированную непосредственно в сервис. Это существенно упрощает разработку потоковых сервисов и клиентов. Возможность реализовывать как традиционный обмен сообщениями вида «запрос — ответ», так и потоковую передачу данных между клиентом и сервером — ключевое преимущество gRPC перед традиционным архитектурным стилем REST.
- Встроенные практичные возможности. gRPC имеет встроенную поддержку таких часто используемых возможностей, как аутентификация, шифрование, устойчивость (крайние сроки, время ожидания), обмен метаданными, сжатие, балансировка нагрузки, обнаружение сервисов и т.д.
- Интеграция с облачно ориентированными экосистемами. gRPC входит в фонд CNCF и напрямую поддерживается в большинстве современных фреймворков и технологий. В частности, многие проекты, принадлежащие к CNCF (например, Envoy), используют gRPC в качестве коммуникационного протокола. Технология gRPC совместима со многими инструментами, предоставляющими такие стандартные возможности, как сбор метрик и мониторинг (например, для мониторинга gRPC-приложений можно задействовать Prometheus).
- Зрелость и широкая распространенность. Зрелость проекта gRPC обусловлена его активным использованием в Google и внедрением другими крупными технологическими компаниями, такими как Square, Lyft, Netflix, Docker, Cisco и CoreOS.
Как и любая другая технология, gRPC имеет не только преимущества, но и недостатки. Понимание последних в процессе разработки приложений может оказаться весьма полезным. Поэтому рассмотрим некоторые ограничения, характерные для gRPC.
Недостатки gRPC
Ниже перечислен ряд недостатков gRPC, которые необходимо учитывать при выборе этой технологии для разработки своих приложений.
- gRPC может не подойти для сервисов, доступных снаружи. Если вы хотите, чтобы ваши приложения или сервисы были доступны снаружи по Интернету, то gRPC будет не самым удачным выбором, поскольку большинство потребителей совсем недавно начали обращать внимание на этот протокол, равно как и на REST/HTTP. Контрактно-ориентированная и сильно типизированная природа gRPC может сделать сервисы, доступ к которым вы открываете, менее гибкими и ограничить контроль со стороны потребителей (в отличие от таких протоколов, как GraphQL). Для преодоления этой проблемы был разработан шлюз gRPC.
- Кардинальные изменения в определении сервисов требуют больших усилий. Структурные изменения далеко не редкость при использовании современных средств межсервисного взаимодействия. Если определение gRPC-сервиса меняется слишком сильно, то разработчику обычно следует заново сгенерировать код как для клиента, так и для сервера. Эту процедуру необходимо внедрить в существующий процесс непрерывной интеграции, что может усложнить цикл разработки в целом. Тем не менее большинство изменений, вносимых в определение сервисов, можно применять, не нарушая их контракты. gRPC сможет без каких-либо проблем взаимодействовать с клиентами и серверами, основанными на другой версии proto-файлов, — главное, чтобы сохранялась обратная совместимость. Таким образом, генерация кода в большинстве случаев не требуется.
- Относительно небольшая экосистема. Экосистема gRPC все еще уступает по своему охвату традиционному протоколу REST/HTTP. Поддержка gRPC в браузерах и мобильных приложениях по-прежнему находится в зачаточном состоянии.
Планируя разработку своих приложений, принимайте во внимание эти ограничения. Естественно, gRPC подходит не для всех разновидностей межпроцессного взаимодействия. Вы должны проанализировать свои бизнес-требования и выбрать подходящий протокол обмена сообщениями.
В области межпроцессного взаимодействия существует множество известных и новых технологий. Вы должны четко представлять сильные и слабые стороны gRPC по сравнению с аналогичными инструментами, завоевавшими популярность в мире разработки современных приложений; это поможет вам выбрать для своих сервисов наиболее подходящий протокол.
Почему gRPC лучше REST? gRPC vs REST — краткое сравнение
Почему gRPC лучше REST в некоторых случаях:
1. Скорость и производительность
- Protobuf гораздо компактнее и быстрее, чем JSON.
- HTTP/2 поддерживает мультиплексирование (несколько запросов по одному соединению), что снижает накладные расходы.
2. Streaming данных
gRPC позволяет:
- Server Streaming: сервер отправляет данные по мере готовности
- Client Streaming: клиент отправляет поток данных
- Bi-directional Streaming: обе стороны общаются в потоке
В REST нужно «эмулировать» потоки через WebSocket или polling.
3. Типизация и автогенерация
- gRPC использует
.proto
схемы → можно автоматически генерировать строго типизированный код для разных языков (Python, Java, Go и т.д.). - REST требует ручного согласования API и типов.
4. Надёжность для микросервисов
gRPC лучше подходит для внутренних API между микросервисами:
- строгость интерфейсов,
- меньшая задержка,
- лёгкость обновлений,
- поддержка deadline и отмены вызова.
Когда REST лучше?
- Когда тебе нужен доступ из браузера напрямую (gRPC не работает из JS без прокси).
- Когда тебе важна читаемость и отладка в Postman/cURL.
- Когда взаимодействие идёт с третьими сторонами, не использующими gRPC.
- Когда простота важнее производительности.
Дополнительное видео про всю теорию «Что такое Rest API (http)? Soap? GraphQL? Websockets? RPC (gRPC, tRPC). Клиент — сервер. Вся теория» (YouTube)
Основы Protocol Buffers (protobuf)
Что такое .proto файлы
.proto
— это файл-схема, написанный на языке описания данных Protocol Buffers (protobuf). Он описывает:
- Структуру сообщений (данных)
- Интерфейс сервиса — какие функции доступны и какие данные они принимают/возвращают.
Зачем нужен .proto?
- Позволяет описывать структуру данных и API в одном месте.
- Из него автоматически генерируется код для клиента и сервера (на любом поддерживаемом языке).
- Устраняет дублирование: один файл — единый источник правды.
Пример .proto
файла
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
syntax = "proto3"; package myapp; // Определение сообщения (структуры данных) message HelloRequest { string name = 1; } message HelloReply { string message = 1; } // Определение сервиса (API) service Greeter { rpc SayHello(HelloRequest) returns (HelloReply); } |
syntax = "proto3";
— используется синтаксис protobuf версии 3.message
— это как классы или словари: структура передаваемых данных.service
— определяет RPC-интерфейс.rpc SayHello(...)
— описание удалённой функции, которую можно вызывать через gRPC.
Как использовать .proto
в Python
Установи нужные пакеты:
1 |
pip install grpcio grpcio-tools |
Сгенерируй Python-код из .proto
:
1 |
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. your_file.proto |
После этого у тебя появятся два файла:
your_file_pb2.py
— содержит классы сообщений (HelloRequest
,HelloReply
)your_file_pb2_grpc.py
— содержит классы серверов и клиентов (GreeterStub
,GreeterServicer
)
Сериализация данных
Что такое сериализация?
Сериализация — это процесс преобразования структуры данных (например, Python-объекта) в формат, пригодный для передачи по сети или хранения, например:
- JSON (текст)
- XML
- BSON
- Protobuf (в gRPC)
А десериализация — это обратный процесс: из байт в объект.
Как сериализация работает в gRPC
В gRPC используется Protocol Buffers (Protobuf) — это бинарный формат:
- Компактнее и быстрее JSON/XML.
- Требует схему (описанную в .proto).
- Поддерживает типизацию и версионирование.
Пример: сериализация на практике
.proto
файл:
1 2 3 4 |
message User { string name = 1; int32 age = 2; } |
На Python:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from user_pb2 import User # Создание объекта user = User(name="Alice", age=30) # Сериализация в байты binary_data = user.SerializeToString() # Десериализация обратно new_user = User() new_user.ParseFromString(binary_data) print(new_user.name) # Alice |
gRPC делает это автоматически — тебе не нужно руками сериализовать/десериализовать при вызове RPC. Всё это встроено в клиент и сервер.
Особенности сериализации в Protobuf
- Каждое поле в .proto имеет уникальный номер (= 1, = 2 и т.д.) — они используются в бинарном представлении вместо имён → меньше размер.
- Отсутствующие поля не передаются → экономия.
- Есть поддержка default значений, optional, repeated, nested messages и т.д.
4 вида методов обслуживания gRPC
gRPC позволяет определить четыре вида методов обслуживания
1. Unary RPCs (Унарные) — при которых клиент отправляет один запрос на сервер и получает один ответ, как при обычном вызове функции.
1 |
rpc SayHello(HelloRequest) returns (HelloResponse); |
Описание процесса:
- После того как клиент вызывает метод-заглушку, сервер уведомляется о том, что RPC был вызван с метаданными клиента для этого вызова, именем метода и указанным крайним сроком, если применимо.
- Затем сервер может либо немедленно отправить обратно свои собственные начальные метаданные (которые должны быть отправлены до любого ответа), либо дождаться сообщения-запроса клиента. Что произойдет первым, зависит от приложения.
- Как только сервер получает сообщение запроса клиента, он выполняет всю необходимую работу для создания и заполнения ответа. Затем ответ возвращается (в случае успеха) клиенту вместе с подробностями статуса (код статуса и необязательное сообщение о статусе) и необязательными завершающими метаданными.
- Если статус ответа ОК, то клиент получает ответ, который завершает вызов на стороне клиента.
2. Server streaming RPCs (Серверные потоковые) — где клиент отправляет запрос на сервер и получает поток для чтения последовательности сообщений обратно. Клиент читает из возвращенного потока до тех пор, пока не останется больше сообщений. gRPC гарантирует упорядочение сообщений в рамках отдельного вызова RPC.
1 |
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse); |
Описание процесса:
Серверный потоковый RPC похож на унарный RPC, за исключением того, что сервер возвращает поток сообщений в ответ на запрос клиента. После отправки всех сообщений клиенту отправляются сведения о состоянии сервера (код состояния и необязательное сообщение о состоянии) и необязательные завершающие метаданные. Это завершает обработку на стороне сервера. Клиент завершает, как только получает все сообщения сервера.
3. Client streaming RPCs (Клиентские потоковые) — где клиент пишет последовательность сообщений и отправляет их на сервер, снова используя предоставленный поток. После того, как клиент закончил писать сообщения, он ждет, пока сервер прочитает их и вернет свой ответ. Опять же gRPC гарантирует упорядочение сообщений в рамках отдельного вызова RPC.
1 |
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse); |
Описание процесса:
Клиентский потоковый RPC похож на унарный RPC, за исключением того, что клиент отправляет поток сообщений на сервер вместо одного сообщения. Сервер отвечает одним сообщением (вместе с подробностями своего статуса и необязательными завершающими метаданными), как правило, но не обязательно после того, как он получил все сообщения клиента.
4. Bidirectional streaming RPCs (Двунаправленные потоковые) — где обе стороны отправляют последовательность сообщений с помощью потока чтения-записи. Два потока работают независимо, поэтому клиенты и серверы могут читать и писать в любом порядке: например, сервер может дождаться получения всех сообщений клиента, прежде чем писать свои ответы, или он может поочередно читать сообщение, а затем писать сообщение, или выполнять некоторую другую комбинацию чтения и записи. Порядок сообщений в каждом потоке сохраняется.
1 |
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse); |
Описание процесса:
В двунаправленном потоковом RPC вызов инициируется клиентом, вызывающим метод, и сервером, получающим метаданные клиента, имя метода и крайний срок. Сервер может выбрать, отправлять ли обратно свои начальные метаданные или ждать, пока клиент начнет потоковую передачу сообщений.
Обработка потоков на стороне клиента и сервера зависит от приложения. Поскольку два потока независимы, клиент и сервер могут читать и записывать сообщения в любом порядке. Например, сервер может ждать, пока не получит все сообщения клиента, прежде чем писать свои сообщения, или сервер и клиент могут играть в «пинг-понг» — сервер получает запрос, затем отправляет ответ, затем клиент отправляет другой запрос на основе ответа и т.д.
Что еще почитать про gRPC
- 🕸 gRPC и все-все-все: Часть I. Введение
- https://adityamattos.com/grpc-in-python-part-1-building-a-grpc-server
- https://adityamattos.com/grpc-in-python-part-2-building-a-grpc-client
- https://www.velotio.com/engineering-blog/grpc-implementation-using-python
- https://grpc.io/docs/what-is-grpc/introduction/
- https://protobuf.dev/overview/
Leave a Reply