Learning Platform
Глоссарий Troubleshooting
Урок 11.02 · 35 мин
Начальный
gRPCProtobufHTTP/2RPCStreamingCode Generation

gRPC и Protobuf: бинарные RPC поверх HTTP/2

REST мыслит ресурсами: «получи коллекцию заказов», «обнови клиента». gRPC мыслит вызовами функций: «вызови SearchProducts(query)», «выполни RecommendItems(userId)». Это переосмысление подхода —

Remote Procedure Call
: фокус на действиях и контрактах функций, а не на состоянии ресурсов.

gRPC родился в Google в 2015 году как open-source наследник внутреннего фреймворка Stubby. Под капотом — HTTP/2 как транспорт и Protocol Buffers (Protobuf) как формат сериализации и язык описания интерфейсов.

Почему HTTP/2 + бинарка

REST работает поверх HTTP/1.1, и это создаёт ограничения. gRPC использует HTTP/2 — и берёт у него три ключевые фичи:

  1. Мультиплексирование — несколько RPC-вызовов параллельно по одному TCP-соединению, без head-of-line blocking.
  2. Бинарные frame-ы — заголовки сжимаются HPACK-ом, payload в Protobuf-е.
  3. Двунаправленные потоки — server и client могут отправлять frame-ы в обе стороны независимо.

Бинарный Protobuf вместо JSON даёт компактность и скорость. Числа кодируются

varint
, строки — длина + UTF-8, поля идентифицируются numeric tag-ом, а не именем. На реальных данных gRPC payload в 3-10 раз меньше JSON и в 5-20 раз быстрее парсится.

REST/JSON против gRPC/Protobuf на одном вызове

REST + JSON

POST /api/v1/users { name: 'Alice', age: 30 }
Payload (UTF-8 текст)46 байт ASCII-текста с именами полей и кавычками
ПарсингJSON.parse: токенизация, построение dict, конверсия типов из строк

gRPC + Protobuf

UserService.Create(User { name = 'Alice', age = 30 })
Payload (бинарный)9 байт: tag+wire_type+length+value для каждого поля. Имена полей не передаются.
ПарсингЧтение varint-ов и копирование байт. Без аллокаций строк для имён полей.
Protobuf в Schema Registry: эволюция .proto схем

Schema-first: файл .proto

В REST контракт обычно живёт в OpenAPI спеке (которую часто пишут после кода), а в gRPC он первичен. Без .proto файла никакой gRPC-сервис не существует.

syntax = "proto3";

package marketplace.v1;

service ProductService {
  rpc GetProduct(GetProductRequest) returns (Product);
  rpc SearchProducts(SearchRequest) returns (stream Product);
  rpc UploadCatalog(stream Product) returns (UploadStats);
  rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}

message GetProductRequest {
  string product_id = 1;
}

message Product {
  string id = 1;
  string name = 2;
  double price = 3;
  Currency currency = 4;
  repeated string tags = 5;
  Inventory inventory = 6;
}

message Inventory {
  int32 in_stock = 1;
  int32 reserved = 2;
}

enum Currency {
  CURRENCY_UNSPECIFIED = 0;
  USD = 1;
  EUR = 2;
  RUB = 3;
}

message SearchRequest {
  string query = 1;
  int32 limit = 2;
  Currency price_currency = 3;
}

message UploadStats {
  int32 created = 1;
  int32 updated = 2;
  int32 errors = 3;
}

message ChatMessage {
  string text = 1;
  int64 ts_unix_ms = 2;
}

Несколько ключевых правил Protobuf 3:

  • Numeric tag (= 1, = 2) — идентификатор поля на проводе. После выпуска API менять нельзя — иначе старые клиенты сломаются.
  • repeated — массив. map<string, int32> — словарь.
  • enum обязан иметь значение с тегом 0 (это default для не указанного поля).
  • Reserved: чтобы не переиспользовать выпиленные поля, пишут reserved 4, 5; или reserved "old_field";.
  • Поля не обязательны по умолчанию — отсутствующее поле получает
    zero value
    .

Имена сервисов и методов — PascalCase, имена полей — snake_case. Это конвенция, на которой основан стандартный кодогенератор.

Кодогенерация

Из .proto файла генерируются типизированные клиентские и серверные stub-ы для нужного языка. Для Python это два пакета: protobuf (типы сообщений) и grpcio-tools (компилятор + gRPC-stubs).

pip install grpcio grpcio-tools

python -m grpc_tools.protoc \
  --python_out=. \
  --pyi_out=. \
  --grpc_python_out=. \
  --proto_path=./proto \
  ./proto/product_service.proto

На выходе появляются три файла:

  • product_service_pb2.py — классы сообщений (Product, GetProductRequest, …).
  • product_service_pb2.pyi — type stubs для IDE.
  • product_service_pb2_grpc.pyProductServiceStub (клиент) и ProductServiceServicer (база для сервера).

Сервер на Python — это унаследованный класс с реализованными методами:

import grpc
from concurrent import futures
import product_service_pb2 as pb
import product_service_pb2_grpc as pb_grpc

class ProductService(pb_grpc.ProductServiceServicer):
    def GetProduct(self, request: pb.GetProductRequest, context) -> pb.Product:
        product = db.find_product(request.product_id)
        if not product:
            context.abort(grpc.StatusCode.NOT_FOUND, "product not found")
        return pb.Product(
            id=product.id,
            name=product.name,
            price=product.price,
            currency=pb.Currency.USD,
            inventory=pb.Inventory(in_stock=product.in_stock),
        )

server = grpc.server(futures.ThreadPoolExecutor(max_workers=16))
pb_grpc.add_ProductServiceServicer_to_server(ProductService(), server)
server.add_insecure_port("[::]:50051")
server.start()
server.wait_for_termination()

Клиент:

import grpc
import product_service_pb2 as pb
import product_service_pb2_grpc as pb_grpc

with grpc.insecure_channel("localhost:50051") as channel:
    stub = pb_grpc.ProductServiceStub(channel)
    response = stub.GetProduct(pb.GetProductRequest(product_id="P-100"))
    print(response.name, response.price)

Никакой ручной сериализации, никакого парсинга URL — всё сводится к вызову метода.

Четыре типа RPC

gRPC поддерживает четыре сценария взаимодействия. Синтаксис задаётся словом stream слева или справа от типа:

rpc Get(Req) returns (Resp);                 // unary
rpc Search(Req) returns (stream Resp);        // server streaming
rpc Upload(stream Req) returns (Resp);        // client streaming
rpc Chat(stream Req) returns (stream Resp);   // bidirectional streaming

Unary — классический request/response, как REST. 90% методов будут такими.

Server streaming — клиент шлёт один запрос, сервер отвечает потоком сообщений. Идеально для выгрузки больших каталогов, страничного чтения, push-уведомлений по подписке.

Client streaming — клиент шлёт поток, сервер отвечает одним сообщением. Используется для batch upload-а (телеметрия, события из IoT).

Bidirectional streaming — оба шлют поток одновременно. Ровно тот сценарий, для которого иначе пришлось бы изобретать WebSocket. Чат, real-time коллаборация, ML inference с протяжённым диалогом.

Bidirectional streaming в gRPC
Клиент
Сервер
HEADERS :path=/Chat/StreamDATA ChatMessage(text='hi')DATA ChatMessage(text='hello!')DATA ChatMessage(text='how are you?')DATA ChatMessage(text='good!')END_STREAMTRAILERS grpc-status=OK

Преимущества gRPC

  • Контракт-first. Изменение в .proto ломает кодогенерацию — нельзя случайно опубликовать несовместимое API.
  • Тип-безопасность. На обеих сторонах сильно типизированные классы; IDE подсказывает поля.
  • Бинарный размер и скорость. В internal-сетях с большим QPS экономия 5-10x на сериализации = меньше CPU и сетевого трафика.
  • Streaming из коробки. Не нужно изобретать SSE или WebSocket.
  • Multi-language. Один .proto -> клиенты на Python, Go, Java, C++, Node.js, Rust.

Недостатки и подводные камни

  • Браузер не умеет HTTP/2 trailers так, как нужно gRPC. Поэтому из браузера ходят через
    gRPC-Web
    или REST gateway.
  • Debugging сложнее. Нельзя открыть Postman и посмотреть JSON. Помогают grpcurl, BloomRPC, расширения для Postman.
  • Schema evolution требует дисциплины. Добавлять поля можно (новые игнорируются старыми клиентами), удалять и менять tag — нет.
  • Public API через интернет — gRPC не любит CDN, корпоративные прокси и L7-балансировщики, не понимающие HTTP/2 stream-ы.
  • Нет встроенного API для browser caching — все запросы идут через POST, кэш заводится самостоятельно.

Где встретится у Data Engineer

gRPC — сетевой стандарт внутри компаний, особенно в инфраструктуре данных:

  • ML serving. TensorFlow Serving, NVIDIA Triton, KServe, BentoML — все экспонируют модели через gRPC. Запрос Predict(Tensor) -> ответ Tensor, до тысяч RPS.
  • Метаданные. Apache Atlas, DataHub, OpenMetadata — gRPC поверх Protobuf или Thrift для inter-service.
  • Observability. OpenTelemetry Collector принимает spans/metrics/logs через gRPC (OTLP).
  • Streaming движки. Apache Beam Flink runner общается с runner harness по gRPC. Spark Connect (3.4+) — gRPC API для работы со Spark из тонких клиентов.
  • Внутренние API хранилищ. ClickHouse имеет gRPC interface; Etcd, Consul, Vitess — всё gRPC.

Junior DE регулярно сталкивается с gRPC при подключении к фичестору, ML-сервису или service mesh. Уметь прочитать .proto, сгенерировать stub и сделать вызов — необходимый навык на год-два карьеры.

TIP

Сравнение Protobuf и Avro: оба бинарные, оба schema-first. Protobuf лучше для межсервисных RPC (компактные сообщения, кодогенерация для языков). Avro лучше для долгосрочного storage и Kafka (schema внутри файла, embedded JSON-schema, легче эволюция). Это не конкуренты, а инструменты для разных задач.

Когда выбрать gRPC

Выбирайте gRPC, если:

  1. Internal микросервисы с высоким QPS и низкой latency.
  2. Команда комфортна со schema-first и code generation.
  3. Нужен streaming в обе стороны.
  4. Polyglot stack — клиенты на нескольких языках.

Не выбирайте, если:

  • Public API через интернет с большим разнообразием клиентов.
  • Браузер — основной потребитель и нет ресурсов на gRPC-Web.
  • Команда из 3 человек на простую CRUD-задачу — overhead toolchain не окупится.
Проверка знанийKnowledge check
Команда хочет передавать предсказания ML-модели в мобильный клиент в реальном времени по мере того, как пользователь печатает запрос (autocomplete с ranking-ом). Запросы идут несколько раз в секунду, ответы тоже. Какой тип RPC и почему?
ОтветAnswer
Это сценарий bidirectional streaming. Обоснование: клиент отправляет много request-ов подряд (каждое нажатие клавиши -- обновление префикса), сервер отвечает потоком обновлённых ranking-ов; держать отдельный TCP/HTTP-вызов на каждое нажатие клавиши -- расточительно (handshake-задержки, рост latency, лишние ресурсы на keep-alive). С bidirectional stream-ом -- один HTTP/2 поток на всю сессию ввода, frame-ы в обе стороны мультиплексируются. Альтернативы: client streaming (клиент шлёт, но сервер ответит ОДНИМ финальным сообщением) -- не подходит, нужна реакция на каждый префикс. Server streaming (один запрос -- поток ответов) -- клиент не сможет дослать новые префиксы. Unary -- слишком много round-trip-ов. На уровне инфры нужно убедиться, что L7-балансер поддерживает HTTP/2 stream-ы и не закрывает их по таймауту. Альтернатива через WebSocket работала бы тоже, но gRPC даёт типизированный контракт + кодогенерацию, что важно для команды, использующей TF Serving или Triton.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Чем gRPC принципиально отличается от REST на уровне транспорта и payload?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 3