gRPC и Protocol Buffers
Быстрые типизированные вызовы между сервисами со сгенерированными клиентами и стримингом поверх HTTP/2.
За 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, и держите по одному сообщению запроса и ответа на каждый метод, чтобы потом добавлять поля, не ломая сигнатуры.
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 и только для идемпотентных методов.
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.
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.
# 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)
Асинхронные события, когда вызывающей стороне не нужен немедленный ответ; развязывают сервисы во времени.
Лучшие практики
- Никогда не меняйте и не переиспользуйте номера полей; помечайте удалённые поля как reserved, чтобы никто не занял их случайно.
- Давайте каждому RPC своё сообщение Request и Response, даже если сегодня они крошечные.
- Всегда задавайте дедлайны на клиенте и передавайте оставшийся дедлайн в downstream-вызовы.
- Запускайте buf lint и buf breaking в CI, чтобы несовместимые изменения схемы роняли сборку.
- Возвращайте точные коды статусов gRPC с полезными деталями; для ошибок по полям используйте rich error model (google.rpc.Status).
- Используйте TLS или mTLS везде за пределами localhost и передавайте auth-токены в metadata, а не в сообщениях.
- Публикуйте стандартный 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).
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.
// 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 и применять их в сгенерированном коде.
Реальные ситуации
Один под бэкенда загружен на 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), чтобы клиенты периодически переподключались и перебалансировались после масштабирования. Проверьте результат по частоте запросов на каждый под после изменения.
Переименование поля сломало мобильное приложение
Проблема
Бэкенд-разработчик переименовал и перенумеровал поле в .proto-файле. Старые версии мобильного приложения, которые ещё в ходу, теперь показывают пустые цены.
Как решить
Немедленно верните исходный номер и тип поля: это возвращает работоспособность старым клиентам, потому что wire-формат снова совпадает. Если нужно новое имя, переименуйте поле, но сохраните номер, или добавьте новое поле с новым номером и заполняйте оба на переходный период. Добавьте reserved для всего удалённого. Затем предотвратите повторение: запускайте buf breaking против main-ветки в CI, чтобы wire-несовместимые изменения роняли сборку, и требуйте повышения версии (orders.v2) для намеренных ломающих изменений, которые обслуживаются параллельно, пока старые версии приложения не выведут из оборота.
Открыть внутренний 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.
Что изучить дальше
Есть идея продукта?Давайте создадим его.
Расскажите о своей идее, продукте или команде. Обычно отвечаю в течение одного рабочего дня.