Все уроки
БэкендПродвинутый12 мин чтенияПолезно знать

gRPC и Protocol Buffers

Быстрые типизированные вызовы между сервисами со сгенерированными клиентами и стримингом поверх HTTP/2.

  • Protocol Buffers
  • HTTP/2
  • Streaming
  • Codegen
Шаг 1/4
HTTP/1.1: одно соединение везёт один запрос за раз. Файлы ждут в очереди, как машины на однополосной дороге.

За 30 секунд

gRPC — способ, которым сервисы вызывают функции друг друга по сети так, будто они локальные. Вы описываете API один раз в .proto-файле, а инструменты генерируют типизированный код клиента и сервера на многих языках. Сообщения кодируются через Protocol Buffers — компактный бинарный формат — и передаются по HTTP/2, который позволяет стриминг в обе стороны. gRPC особенно хорош внутри бэкенда между микросервисами; браузерам нужен дополнительный слой.

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

Почему это важно

  • Один .proto-контракт генерирует типизированных клиентов для Go, Java, Python, Node.js, C#, Kotlin и других языков, что убирает целый класс интеграционных багов между командами.
  • Бинарные Protobuf-сообщения меньше и быстрее парсятся, чем JSON, что важно при большом объёме запросов между внутренними сервисами.
  • Встроенный стриминг, дедлайны и стандартные коды статусов делают его сильным выбором для real-time потоков и service mesh.
  • Крупные платформы вроде Kubernetes (через etcd и CRI), Envoy и многие облачные API используют gRPC, так что его стоит понимать, когда проект разрастается до множества сервисов.

Как это работает

01Идея: удалённые вызовы процедур по контракту

В REST вы мыслите ресурсами и HTTP-глаголами: GET /orders/42. В gRPC вы мыслите функциями: OrderService.GetOrder(id). Это называется RPC — remote procedure call, удалённый вызов процедуры. Важное отличие — контракт. В gRPC API описывается в .proto-файле на Protocol Buffers (Protobuf) — языке описания интерфейсов. Компилятор (protoc или Buf CLI) превращает этот файл в код: классы сообщений, клиентский stub, который вызывается как обычный метод, и серверный интерфейс, который вы реализуете. Если сервер меняет тип поля, клиенты, которые перегенерируют код, получают ошибку компиляции, а не сюрприз в продакшене. Представьте .proto-файл как юридически обязывающее меню, которому должны следовать и кухня, и гости.

  • Protobuf: язык схем и бинарная кодировка.
  • Stub: сгенерированный клиентский объект, методы которого делают сетевые вызовы.
  • Channel: долгоживущее соединение с адресом сервера.

02Пишем .proto-файл

.proto-файл объявляет package, сообщения (формы данных) и сервисы (группы RPC-методов). У каждого поля есть тип и номер поля. По сети передаётся именно номер, а не имя, поэтому после публикации его нельзя менять или переиспользовать. Синтаксис proto3 — распространённый выбор; более новые «editions» Protobuf объединяют поведение proto2 и proto3 через feature-флаги, но proto3-файлы остаются полностью поддерживаемыми. Для дат используйте well-known типы, например google.protobuf.Timestamp, и держите по одному сообщению запроса и ответа на каждый метод, чтобы потом добавлять поля, не ломая сигнатуры.

protobuf
syntax = "proto3";
 
package shop.orders.v1;
 
import "google/protobuf/timestamp.proto";
 
service OrderService {
  rpc GetOrder(GetOrderRequest) returns (GetOrderResponse);
  rpc WatchOrders(WatchOrdersRequest) returns (stream OrderEvent);
}
 
message GetOrderRequest { string order_id = 1; }
message GetOrderResponse { Order order = 1; }
message WatchOrdersRequest { string customer_id = 1; }
message OrderEvent { Order order = 1; string type = 2; }
 
message Order {
  string id = 1;
  int64 total_cents = 2;
  google.protobuf.Timestamp created_at = 3;
}

03Под капотом: HTTP/2, фрейминг и коды статусов

gRPC работает поверх HTTP/2. Каждый вызов — это HTTP/2-поток на общем TCP-соединении, поэтому много вызовов мультиплексируются без head-of-line blocking на уровне HTTP. Запрос — это POST на /package.Service/Method с content-type application/grpc. Каждое сообщение — бинарный Protobuf с префиксом длины. Metadata (аналог заголовков) несёт auth-токены и ID трассировки. Результаты завершаются трейлерами с grpc-status и grpc-message. У gRPC свои коды статусов, отдельные от HTTP: OK, INVALID_ARGUMENT, NOT_FOUND, ALREADY_EXISTS, PERMISSION_DENIED, UNAUTHENTICATED, RESOURCE_EXHAUSTED, UNAVAILABLE, DEADLINE_EXCEEDED и другие. Клиенты должны повторять запрос только при безопасных кодах вроде UNAVAILABLE и только для идемпотентных методов.

http
POST /shop.orders.v1.OrderService/GetOrder HTTP/2
content-type: application/grpc
te: trailers
authorization: Bearer <token>
grpc-timeout: 2S
 
<5-byte prefix><binary GetOrderRequest>
 
HTTP/2 200
content-type: application/grpc
<5-byte prefix><binary GetOrderResponse>
grpc-status: 0

04Четыре вида RPC

gRPC поддерживает четыре формы вызова. Unary — один запрос и один ответ, как обычная функция. Server streaming отправляет один запрос и получает поток ответов — полезно для live-обновлений заказов или больших выборок. Client streaming отправляет поток запросов и получает один ответ — полезно для загрузки телеметрии или чанков. Bidirectional streaming позволяет обеим сторонам независимо отправлять данные в одном вызове — используется для чатов, совместного редактирования или real-time управления. Внутри одного вызова потоки упорядочены, а flow control обеспечивает HTTP/2. Долгоживущим потокам нужны keepalive и логика переподключения, потому что прокси и балансировщики закрывают простаивающие соединения.

  • Unary: rpc Get(Req) returns (Res)
  • Server streaming: rpc Watch(Req) returns (stream Event)
  • Client streaming: rpc Upload(stream Chunk) returns (Summary)
  • Bidirectional: rpc Chat(stream Msg) returns (stream Msg)

05Сервер и клиент на Node.js на практике

В Node.js официальная реализация — @grpc/grpc-js. Можно загружать .proto в рантайме через @grpc/proto-loader, как ниже, или заранее сгенерировать TypeScript-код через Buf и плагины вроде ts-proto или protobuf-es для полной типобезопасности. Каждый клиентский вызов должен передавать дедлайн; без него зависший сервер может держать ресурсы бесконечно. Продакшен-серверы используют TLS (createSsl) или работают в plaintext только внутри mesh, который обеспечивает mTLS.

ts
import * as grpc from "@grpc/grpc-js";
import * as loader from "@grpc/proto-loader";
 
const def = loader.loadSync("orders.proto", { keepCase: false, longs: String });
const pkg = grpc.loadPackageDefinition(def) as any;
const Svc = pkg.shop.orders.v1.OrderService;
 
const server = new grpc.Server();
server.addService(Svc.service, {
  getOrder: (call: any, cb: any) => {
    if (!call.request.orderId) return cb({ code: grpc.status.INVALID_ARGUMENT, details: "orderId required" });
    cb(null, { order: { id: call.request.orderId, totalCents: "4200" } });
  },
});
server.bindAsync("0.0.0.0:50051", grpc.ServerCredentials.createInsecure(), () => {});
 
const client = new Svc("localhost:50051", grpc.credentials.createInsecure());
client.getOrder({ orderId: "42" }, { deadline: Date.now() + 2000 }, console.log);

06gRPC в браузере и на edge

Браузеры не могут полностью управлять HTTP/2-фреймами или читать трейлеры из fetch, поэтому не умеют говорить на нативном gRPC. Есть два распространённых решения. gRPC-Web — немного другой wire-формат, который прокси (Envoy) транслирует в настоящий gRPC; он поддерживает только unary и server streaming. Connect — более новое семейство протоколов от Buf, серверы которого одновременно говорят на gRPC, gRPC-Web и простом, дружелюбном к HTTP/JSON протоколе Connect, так что браузер может вызывать тот же сервис обычным fetch. Многие команды вместо этого оставляют REST или GraphQL на публичном edge и используют gRPC только за ним, с шлюзом вроде grpc-gateway или Envoy, транскодирующим JSON в gRPC.

07Инструменты и чеклист для продакшена

Используйте Buf CLI, чтобы линтить .proto-файлы, ловить ломающие изменения в CI и воспроизводимо генерировать код из buf.gen.yaml. Версионируйте пакеты в имени (orders.v1, orders.v2). Включите стандартный сервис health checking, чтобы балансировщики и пробы Kubernetes могли проверять готовность, а в непродакшен-окружениях — server reflection, чтобы инструменты вроде grpcurl могли исследовать API. Пробрасывайте дедлайны и метаданные трассировки (OpenTelemetry) через вызовы. Лимиты размера сообщений задавайте осознанно; в большинстве реализаций максимальный размер получаемого сообщения по умолчанию — 4 MB.

bash
# Lint, check for breaking changes vs main, then generate code
buf lint
buf breaking --against ".git#branch=main"
buf generate
 
# Call a running server (needs reflection or the .proto)
grpcurl -plaintext -d '{"order_id":"42"}' localhost:50051 shop.orders.v1.OrderService/GetOrder
grpcurl -plaintext localhost:50051 grpc.health.v1.Health/Check

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

  • Внутренние вызовы между сервисами в многоязычной микросервисной системе, где важен общий, принудительно соблюдаемый контракт.
  • Пути с высокой пропускной способностью или низкой задержкой, где парсинг JSON и размер payload — измеримые затраты.
  • Real-time потоки между бэкендами: телеметрия, ценовые фиды, прогресс задач, двунаправленные каналы управления.
  • Мобильные или IoT-клиенты в ограниченных сетях, где помогают компактные бинарные payload и сгенерированные SDK.

Когда не стоит

  • Публичные API для третьих сторон, которые ждут удобный для curl JSON, читаемую документацию и простую отладку; REST дружелюбнее.
  • Простые веб-приложения, где браузер — основной клиент и его обслуживает один бэкенд; накладные расходы на прокси и кодогенерацию того не стоят.
  • Команды без CI и дисциплины в инструментах для эволюции схем; неуправляемые изменения .proto молча ломают клиентов.

Альтернативы

  • REST + OpenAPI

    Универсальный, кэшируемый, человекочитаемый JSON; клиентов можно генерировать из OpenAPI-спецификаций.

  • GraphQL

    Запросы, управляемые клиентом, через один эндпоинт; лучше подходит для гибких потребностей фронтенда в данных, чем для RPC между сервисами.

  • Connect RPC

    Совместимый с gRPC протокол, который работает и с обычным HTTP/JSON, и в браузерах без транслирующего прокси.

  • Очереди сообщений (Redis/BullMQ, Kafka, NATS)

    Асинхронные события, когда вызывающей стороне не нужен немедленный ответ; развязывают сервисы во времени.

Лучшие практики

  1. Никогда не меняйте и не переиспользуйте номера полей; помечайте удалённые поля как reserved, чтобы никто не занял их случайно.
  2. Давайте каждому RPC своё сообщение Request и Response, даже если сегодня они крошечные.
  3. Всегда задавайте дедлайны на клиенте и передавайте оставшийся дедлайн в downstream-вызовы.
  4. Запускайте buf lint и buf breaking в CI, чтобы несовместимые изменения схемы роняли сборку.
  5. Возвращайте точные коды статусов gRPC с полезными деталями; для ошибок по полям используйте rich error model (google.rpc.Status).
  6. Используйте TLS или mTLS везде за пределами localhost и передавайте auth-токены в metadata, а не в сообщениях.
  7. Публикуйте стандартный health-сервис и добавьте interceptor-ы OpenTelemetry для трассировки и метрик.

Частые ошибки

  • Обычный балансировщик уровня L4 (TCP) перед gRPC: долгоживущие HTTP/2-соединения прибивают весь трафик к одному бэкенду.
  • Отсутствие дедлайнов, из-за чего одна медленная зависимость занимает потоки и соединения по всей цепочке вызовов.
  • Повтор неидемпотентных методов вроде CreatePayment при UNAVAILABLE без ключа идемпотентности.
  • Переименование или перенумерация полей либо смена типов, что молча портит данные для старых клиентов.
  • Открыть нативный gRPC напрямую браузерам и потом обнаружить, что без gRPC-Web или Connect это не работает.

Глубже

08Wire-формат Protobuf и почему он компактный

Каждое поле кодируется как тег (номер поля, сдвинутый влево на три бита, плюс wire type), за которым следует значение. Целые числа используют varint: маленькие числа занимают один байт, большие — больше. Поэтому номера полей с 1 по 15 так ценны: их тег помещается в один байт, так что приберегите их для частых полей. Отрицательные числа в int32/int64 всегда занимают десять байт; для значений, которые часто бывают отрицательными, используйте sint32/sint64 (zigzag-кодирование). Строки, bytes и вложенные сообщения передаются с префиксом длины. Значения по умолчанию (0, пустая строка, false) в proto3 вообще не записываются, если поле не помечено optional, — поэтому без optional или wrapper-типов нельзя отличить «не задано» от «ноль». Repeated-скаляры в proto3 по умолчанию упакованы (packed).

protobuf
message Reading {
  int32 sensor_id = 1;        // tag 0x08, varint
  sint32 delta = 2;           // zigzag: -1 encodes as 1 byte
  optional double value = 3;  // presence tracked: unset != 0.0
  repeated int32 samples = 4; // packed into one length-delimited block
  reserved 5, 6;
  reserved "legacy_name";
}

09Балансировка, разрешение имён и xDS

gRPC channel — это не одно соединение, а менеджер, который разрешает целевое имя (dns:///orders.internal:50051) в адреса, открывает subchannel к каждому и для каждого вызова выбирает один согласно политике балансировки. По умолчанию pick_first отправляет всё на первый здоровый адрес; round_robin распределяет вызовы по всем. Service config, доставляемый через DNS или заданный в коде, настраивает политики, повторы и таймауты по методам. В большом масштабе gRPC-клиенты могут говорить на xDS — протоколе control plane от Envoy — и получать эндпоинты, веса и правила маршрутизации прямо из control plane, получая поведение proxyless service mesh. Это убирает лишний хоп через sidecar, но привязывает клиентов к control plane mesh.

ts
// Node.js: client-side round robin + retry policy via service config
const serviceConfig = JSON.stringify({
  loadBalancingConfig: [{ round_robin: {} }],
  methodConfig: [{
    name: [{ service: "shop.orders.v1.OrderService", method: "GetOrder" }],
    timeout: "2s",
    retryPolicy: { maxAttempts: 3, initialBackoff: "0.1s", maxBackoff: "1s",
      backoffMultiplier: 2, retryableStatusCodes: ["UNAVAILABLE"] },
  }],
});
const client = new Svc("dns:///orders.internal:50051", creds,
  { "grpc.service_config": serviceConfig });

10Стриминг в масштабе: flow control и backpressure

Flow control в HTTP/2 даёт каждому потоку и соединению окно байтов, которое получатель готов принять. Если клиент медленно читает серверный поток, окно заполняется, и записи сервера блокируются или буферизуются — это backpressure, работающий как задумано. Проблемы появляются, когда серверный код это игнорирует и буферизует неограниченное количество сообщений в памяти. В Node.js учитывайте возвращаемое значение call.write() и ждите события 'drain'. Долгоживущим потокам также нужны keepalive-пинги, чтобы NAT-шлюзы и прокси молча не обрывали простаивающие соединения, но при слишком агрессивных keepalive клиента отклонят с GOAWAY 'too_many_pings'. Проектируйте потоки возобновляемыми: включайте курсор или порядковый номер, чтобы переподключившийся клиент мог продолжить с того места, где остановился.

11Безопасность: mTLS, аутентификация и interceptor-ы

Между сервисами mutual TLS даёт и шифрование, и идентичность: у каждого workload есть сертификат, часто выпускаемый автоматически mesh или SPIFFE/SPIRE, а серверы авторизуют вызывающих по идентичности их сертификата. Идентичность пользователя передаётся отдельно — как bearer-токен в metadata — и проверяется в серверном interceptor-е, который в gRPC играет роль middleware. В interceptor-ах же добавляют логирование, метрики, трассировку, rate limiting и восстановление после паник — единообразно для всех методов. Отключите или защитите server reflection в продакшене, ограничьте максимальный размер сообщений, чтобы не допустить злоупотребления памятью, и явно валидируйте ввод; protovalidate позволяет объявить правила вроде длины строки или формата email прямо в .proto и применять их в сгенерированном коде.

Реальные ситуации

01

Один под бэкенда загружен на 100% CPU, остальные простаивают

Проблема

gRPC-сервис работает в пяти репликах в Kubernetes за ClusterIP Service. Метрики показывают, что один под обрабатывает почти весь трафик от API-шлюза.

Как решить

ClusterIP Service балансирует TCP-соединения, а шлюз держит одно долгоживущее HTTP/2-соединение, поэтому все вызовы попадают в один под. Исправьте это на уровне запросов: либо перейдите на headless Service и настройте клиент с целями dns:/// и политикой round_robin, либо поставьте на пути L7-прокси или service mesh (Envoy, Linkerd, Istio). Также задайте на сервере максимальный возраст соединения (grpc.max_connection_age_ms), чтобы клиенты периодически переподключались и перебалансировались после масштабирования. Проверьте результат по частоте запросов на каждый под после изменения.

02

Переименование поля сломало мобильное приложение

Проблема

Бэкенд-разработчик переименовал и перенумеровал поле в .proto-файле. Старые версии мобильного приложения, которые ещё в ходу, теперь показывают пустые цены.

Как решить

Немедленно верните исходный номер и тип поля: это возвращает работоспособность старым клиентам, потому что wire-формат снова совпадает. Если нужно новое имя, переименуйте поле, но сохраните номер, или добавьте новое поле с новым номером и заполняйте оба на переходный период. Добавьте reserved для всего удалённого. Затем предотвратите повторение: запускайте buf breaking против main-ветки в CI, чтобы wire-несовместимые изменения роняли сборку, и требуйте повышения версии (orders.v2) для намеренных ломающих изменений, которые обслуживаются параллельно, пока старые версии приложения не выведут из оборота.

03

Открыть внутренний gRPC-сервис для веб-дашборда

Проблема

У команды есть внутренние сервисы на gRPC, и она хочет, чтобы React-дашборд вызывал некоторые из них напрямую, включая live-поток прогресса.

Как решить

Вместо написанного вручную REST-слоя используйте Connect: реализуйте или оберните сервис Connect-совместимым сервером, чтобы браузер вызывал его через сгенерированный клиент protobuf-es поверх fetch, а server streaming работал для прогресса. Если сервис должен остаться чистым gRPC, запустите Envoy с фильтром grpc_web и используйте клиент gRPC-Web. В обоих случаях делайте аутентификацию на edge с короткоживущими токенами в metadata, настройте CORS только для origin дашборда и открывайте только методы чтения и прогресса, которые нужны дашборду, а не всю внутреннюю поверхность.

Шпаргалка

Зарезервировать удалённое поле
reserved 4; reserved "old_field";
Линтинг proto
buf lint
Ронять CI при ломающих изменениях
buf breaking --against ".git#branch=main"
Генерация кода из buf.gen.yaml
buf generate
Список сервисов на сервере
grpcurl -plaintext localhost:50051 list
Вызов unary-метода
grpcurl -plaintext -d '{"order_id":"42"}' localhost:50051 pkg.Svc/Method
Health check
grpcurl -plaintext localhost:50051 grpc.health.v1.Health/Check
Клиентский дедлайн (Node.js)
client.getOrder(req, { deadline: Date.now() + 2000 }, cb)
Отладочные логи (grpc-js)
GRPC_TRACE=all GRPC_VERBOSITY=DEBUG node server.js

Вопросы и ответы

Q1gRPC против REST: в чём реальные различия?

REST ориентирован на ресурсы, обычно это JSON поверх HTTP/1.1 или HTTP/2, он человекочитаем и поддерживается любым инструментом, браузером и кэшем. gRPC — это RPC по принципу contract-first: .proto-файл генерирует типизированных клиентов и серверы, payload — бинарный Protobuf, нужен HTTP/2, и нативно поддерживаются четыре режима стриминга. gRPC выигрывает в типобезопасности между языками, размере payload и стриминге между сервисами. REST выигрывает в охвате, удобстве отладки, HTTP-кэшировании и опыте внешних разработчиков. Распространённая архитектура — REST или GraphQL на edge для браузеров и партнёров и gRPC между внутренними сервисами.

Q2Как работает обратная совместимость в Protobuf?

На уровне wire-формата поля идентифицируются номером и типом, а не именем. Декодер пропускает неизвестные номера полей, а отсутствующие поля получают значения по умолчанию. Это значит, что можно безопасно добавлять новые поля с новыми номерами, а старые клиенты их проигнорируют. Можно переименовать поле, не ломая бинарный формат, хотя это ломает сгенерированный код и JSON-маппинг. Совместимость ломает переиспользование номера, несовместимая смена типа поля или в некоторых случаях превращение поля из singular в repeated. При удалении поля добавьте его номер и имя в reserved. Проверка breaking changes в Buf обеспечивает соблюдение этих правил в CI.

Q3Что такое дедлайны и почему они важны?

Дедлайн — это абсолютный момент времени, к которому клиенту нужен ответ. Он передаётся в заголовке grpc-timeout, и когда истекает, клиент получает DEADLINE_EXCEEDED, а сервер видит вызов как отменённый. Дедлайны важны потому, что в цепочке сервисов одна медленная зависимость иначе может держать ресурсы во всех upstream-сервисах и вызывать каскадные отказы. Хорошая практика — задавать дедлайн на каждый вызов, передавать оставшийся бюджет в downstream-вызовы вместо запуска нового таймаута и проверять отмену в долгих серверных обработчиках, чтобы они прекращали работу, которую уже никто не ждёт.

Q4Можно ли использовать gRPC из браузера?

Нативно — нет. Браузеры не дают доступа к HTTP/2-фреймингу и трейлерам, на которые опирается gRPC. Варианты: gRPC-Web, который использует модифицированный протокол, требует прокси вроде Envoy для трансляции в стандартный gRPC и поддерживает только unary и server streaming; или Connect, где сервер сам говорит на gRPC, gRPC-Web и протоколе Connect, так что браузер вызывает его через fetch, а те же обработчики обслуживают бэкенд-клиентов gRPC. Или же оставьте REST или GraphQL API для браузера и транслируйте в gRPC на шлюзе — для публичных продуктов это часто проще всего.

Q5Как балансировать нагрузку для gRPC?

gRPC держит долгоживущие HTTP/2-соединения и мультиплексирует много вызовов по одному. Балансировщик уровня L4 балансирует соединения, а не запросы, поэтому один клиент может отправлять весь трафик на один бэкенд, пока остальные простаивают. Решения — прокси уровня L7, которые понимают HTTP/2 и балансируют по запросам, например Envoy, NGINX с grpc_pass или service mesh вроде Istio или Linkerd; либо клиентская балансировка, когда клиент получает все адреса бэкендов через DNS или xDS и использует политику round_robin. В Kubernetes обычный ответ — headless Service плюс клиентская балансировка или mesh.

Q6Как обрабатывать ошибки в gRPC?

Каждый вызов завершается кодом статуса из фиксированного набора и необязательным сообщением. Я сознательно сопоставляю доменные ошибки: ошибки валидации — INVALID_ARGUMENT, отсутствующие записи — NOT_FOUND, аутентификация — UNAUTHENTICATED или PERMISSION_DENIED, rate limit — RESOURCE_EXHAUSTED, а временные сбои — UNAVAILABLE, чтобы клиенты знали, что могут повторить запрос. Для структурированных деталей расширенная модель ошибок прикрепляет детали google.rpc.Status, например BadRequest с нарушениями по полям или RetryInfo. Я никогда не отдаю стектрейсы в сообщениях, а interceptor-ы логируют ошибки с ID трассировки, чтобы клиентскую ошибку можно было проследить через сервисы.

Вопросы уровня senior

S1Как бы вы спроектировали версионирование API и governance для 50 сервисов на gRPC?

Я бы положил все .proto-файлы в реестр схем — отдельный репозиторий или Buf Schema Registry — с версией в имени пакета (billing.v1). CI запускает buf lint для единообразного стиля и buf breaking против последнего релиза, чтобы wire-несовместимые изменения нельзя было смержить. Добавляющие изменения остаются в v1; настоящие ломающие изменения создают v2, который обслуживается параллельно, с окном депрекации и метриками, показывающими, какие клиенты ещё вызывают v1. Сгенерированные SDK публикуются как версионированные пакеты, чтобы потребители обновлялись осознанно. Владение каждым пакетом явно задано через CODEOWNERS. Общие типы вроде Money или Address живут в небольшом common-пакете и держатся стабильными, потому что любое изменение там расходится повсюду.

S2Sidecar service mesh или proxyless gRPC: в чём компромиссы?

Sidecar mesh (Istio, Linkerd) даёт mTLS, повторы, разделение трафика и телеметрию каждому сервису независимо от языка и без изменений в коде. Цена — лишний сетевой хоп на каждый вызов, CPU и память sidecar в каждом поде и операционная сложность. Proxyless gRPC использует xDS внутри самой gRPC-библиотеки, так что клиенты получают эндпоинты, балансировку и маршрутизацию напрямую из control plane: меньше задержка и потребление ресурсов, но только для gRPC-трафика, только в языках со зрелой поддержкой xDS, а возможности mesh привязаны к версиям библиотек. Ambient или node-level mesh — компромиссный вариант. Я бы начал с sidecar или ambient mesh ради единообразия, а proxyless использовал бы только для горячих путей, критичных к задержке.

S3Как сделать повторы безопасными в цепочке gRPC-сервисов?

Во-первых, ограничьте повторы кодами, которые означают, что запрос, скорее всего, не был обработан или может быть повторён, — в основном UNAVAILABLE, и только для идемпотентных методов. Для изменяющих методов требуйте ключ идемпотентности в запросе и храните результаты по нему на сервере, чтобы повторный CreatePayment вернул исходный результат. Во-вторых, избегайте усиления повторов: если три слоя повторяют по три раза, один запрос пользователя может превратиться в 27 вызовов бэкенда. Повторяйте на одном слое — в идеале на edge или в клиенте, ближайшем к сбою, — используйте экспоненциальный backoff с jitter и retry throttling в gRPC, который отключает повторы при высокой доле ошибок. В-третьих, пробрасывайте дедлайны, чтобы повторы никогда не превышали исходный бюджет.

Глоссарий

RPC
Remote procedure call: вызов функции, которая выполняется на другой машине, так, будто она локальная.
Protocol Buffers
Язык схем и компактная бинарная кодировка от Google, которые gRPC использует для описания и отправки сообщений.
Stub
Сгенерированный клиентский объект, методы которого сериализуют запросы, отправляют их по сети и возвращают ответы.
Deadline
Абсолютный момент времени, к которому вызов должен завершиться; после него вызов отменяется с DEADLINE_EXCEEDED.
Metadata
Пары ключ-значение, отправляемые с вызовом, аналог HTTP-заголовков; используются для auth-токенов и ID трассировки.
Interceptor
Middleware, которое оборачивает каждый вызов на клиенте или сервере для логирования, аутентификации, метрик или повторов.
gRPC-Web
Совместимый с браузерами вариант gRPC, которому нужен прокси для трансляции в стандартный gRPC.
Номер поля
Числовой ID поля Protobuf; он идентифицирует поле в wire-формате и никогда не должен меняться.
xDS
API control plane от Envoy; gRPC-клиенты могут через них получать эндпоинты и маршрутизацию без sidecar.

Есть идея продукта?Давайте создадим его.

Расскажите о своей идее, продукте или команде. Обычно отвечаю в течение одного рабочего дня.