Зачем gRPC
REST + JSON хорош для public API, но проблематичен для internal microservices:
- Schema-less: контракт неявный, ломается на refactor
- Текстовый JSON - дольше парсить, больше bytes
- Нет streaming - real-time события через polling/SSE/WebSocket
- Нет кодогенерации - руками писать клиента под каждый язык
gRPC (Google RPC, 2015, open source) решает:
- Контракт через
.proto- сильная типизация, schema-driven - Protobuf wire format - бинарный, компактный, быстрый
- HTTP/2 transport - multiplexing, binary framing, [[http2-internals|streaming]]
- Кодогенерация - один
.proto→ клиенты на 11 языках - Built-in deadlines, retries, load balancing
Применения:
- Internal microservices (Google, Netflix, Square, Lyft)
- Service mesh (Istio Pilot ↔ Envoy через gRPC)
- Kubernetes (kubelet ↔ CRI-runtime через gRPC)
- CockroachDB, etcd, dgraph - internal RPC
Не для public web - браузеры HTTP/2 поддерживают, но сырой gRPC
требует библиотеку (нет встроенной поддержки в fetch()).
gRPC-Web через прокси решает (Envoy translates).
.proto - язык контрактов
syntax = "proto3";
package myservice;
option go_package = "github.com/me/myservice/pb";
service UserService {rpc GetUser(GetUserRequest) returns (User);
rpc ListUsers(ListUsersRequest) returns (stream User);
rpc CreateUsers(stream CreateUserRequest) returns (CreateUsersResponse);
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}
message GetUserRequest {string user_id = 1;
}
message User {string id = 1;
string email = 2;
int64 created_at = 3;
repeated string roles = 4;
}
Каждое поле имеет:
- Тип (string/int32/int64/bool/bytes/message/enum/repeated/map)
- Tag (1, 2, 3 - wire-формат использует tag, не имя)
- Опции (optional, deprecated, json_name)
Backward compatibility: добавление поля с новым tag - safe; переиспользование старого tag - ломает старых клиентов.
Кодогенерация
Для каждого языка есть protoc plugin:
# Go
protoc --go_out=. --go-grpc_out=. user.proto
# Python
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. user.proto
# TypeScript
protoc --plugin=protoc-gen-ts_proto --ts_proto_out=. user.proto
Генерится:
- Структуры данных для message'й (GetUserRequest, User, ...)
- Stub для клиента (UserServiceClient)
- Interface для сервера (UserServiceServer - implement)
Идиоматичный код, не reflection-based - быстрый.
Четыре типа RPC
1. Unary - как REST
rpc GetUser(GetUserRequest) returns (User);
Клиент шлёт один request, получает один response. 99% gRPC-вызовов в microservices - именно unary.
resp, err := client.GetUser(ctx, &pb.GetUserRequest{UserId: "u123"})2. Server streaming
rpc ListUsers(ListUsersRequest) returns (stream User);
Клиент шлёт один request, сервер отправляет несколько ответов через тот же stream. Завершается когда сервер закрывает.
stream, _ := client.ListUsers(ctx, &pb.ListUsersRequest{})for {user, err := stream.Recv()
if err == io.EOF { break }handle(user)
}
Применение: pagination, exports, server-sent events.
3. Client streaming
rpc CreateUsers(stream CreateUserRequest) returns (CreateUsersResponse);
Клиент шлёт несколько request'ов, сервер отвечает один раз когда client закрывает stream. Применение: bulk-upload, file streaming.
4. Bidirectional streaming
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
Обе стороны шлют независимо. Похоже на websocket, но с schema-typed messages. Применение: chat, real-time collab, Envoy xDS streaming config updates.
Wire format - HTTP/2 + protobuf
gRPC-вызов на проводе:
HEADERS
:method = POST
:scheme = https
:path = /myservice.UserService/GetUser
:authority = api.example.com
content-type = application/grpc
grpc-encoding = gzip
grpc-accept-encoding = identity, gzip
user-agent = grpc-go/1.50.0
DATA
[00] [00 00 00 24] [<protobuf-encoded GetUserRequest>]
│ │ │
│ │ └── 36 байт payload
│ └── length (big-endian uint32)
└── compressed-flag (0=no, 1=yes)
HEADERS (trailers)
grpc-status = 0
grpc-message =
- Path =
/<package>.<Service>/<Method> - content-type =
application/grpc(илиapplication/grpc+proto) - DATA frame содержит gRPC message frame: 1 байт compressed-flag
- 4 байта длины + payload (protobuf bytes)
- trailers (HEADERS после DATA) несут
grpc-statusиgrpc-message
Status codes (grpc-status) отдельные от HTTP:
- 0 = OK
- 1 = CANCELLED
- 2 = UNKNOWN
- 3 = INVALID_ARGUMENT
- 4 = DEADLINE_EXCEEDED
- 5 = NOT_FOUND
- 13 = INTERNAL
- 14 = UNAVAILABLE (retry-able)
HTTP/2 :status всегда 200 (даже если gRPC-call провалился).
TLS - mutual auth и cert
gRPC обычно с TLS (grpc.WithTransportCredentials). Для микросервисов
- mTLS через [[tls-certificates|клиент-сертификаты]]:
creds := credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{clientCert},RootCAs: caPool,
})
conn, _ := grpc.Dial("api:443", grpc.WithTransportCredentials(creds))В service mesh (Istio, Linkerd) - mTLS делается sidecar'ом, app шлёт plaintext в localhost:5500.
Deadlines, метаданные, retry
Deadlines
Каждый client RPC имеет deadline (абсолютное время cancellation). Через context:
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
client.GetUser(ctx, ...)
Deadline передаётся серверу через header grpc-timeout. Сервер
знает - если deadline прошёл, сразу вернёт DEADLINE_EXCEEDED, не
тратя ресурсы. Каскадно передаётся дальше в downstream calls.
Metadata - кастомные headers
md := metadata.Pairs("auth-token", "secret123", "x-request-id", "abc")ctx = metadata.NewOutgoingContext(ctx, md)
На сервере:
md, _ := metadata.FromIncomingContext(ctx)
token := md.Get("auth-token")Retry config
Через service config (JSON в connection options):
{ "methodConfig": [{ "name": [{"service": "myservice.UserService"}], "retryPolicy": {"maxAttempts": 3,
"initialBackoff": "0.1s",
"maxBackoff": "1s",
"backoffMultiplier": 2,
"retryableStatusCodes": ["UNAVAILABLE"]
}
}]
}
Load balancing
gRPC поддерживает client-side LB через resolver + LB policy:
- Resolver получает список backend'ов (DNS, etcd, xDS)
- LB policy выбирает backend для каждого RPC (round_robin, pick_first)
В Kubernetes стандартный паттерн - headless Service + DNS resolver. ClusterIP-Service не работает: gRPC использует один long-lived HTTP/2 connection - kube-proxy балансирует только на TCP-уровне → все запросы идут на один pod.
Альтернатива - xDS-based balancing (через Envoy/gRPC xDS API), proxyless service mesh.
gRPC vs REST/JSON - таблица
| Свойство | REST/JSON | gRPC |
|---|---|---|
| Контракт | OpenAPI (опц) | .proto (обяз) |
| Wire format | text JSON | binary protobuf |
| Размер на проводе | базовый | 30-60% меньше |
| Скорость parse | медленнее (~5x) | быстрее |
| Streaming | через SSE/WebSocket | первоклассно |
| Browser support | native | через gRPC-Web proxy |
| Schema evolution | руками | контролируется через tag |
| Tooling | curl, Postman | grpcurl, BloomRPC |
| Use case | public API | internal microservices |
Часто рядом - REST для внешнего API, gRPC для внутреннего.
Gateway транслирует (grpc-gateway).
gRPC и Kubernetes Ingress
Дефолт Ingress NGINX поддерживает gRPC через annotation:
metadata:
annotations:
nginx.ingress.kubernetes.io/backend-protocol: GRPC
Envoy/Istio - native поддержка. Гибче в:
- Путях RPC (
/myservice.UserService/*) - Per-method route и rate-limit
- Retries по конкретным status codes
См. [[kubernetes-services-and-ingress|k8s services and ingress]] для основ.
Когда что-то пошло не так
UNAVAILABLE: connection refused- server не слушает или network. Проверьkubectl exec ... grpcurl host:port list.- Все запросы на один pod - ClusterIP-сервис ломает gRPC LB. Перейти на headless service + DNS resolver.
UNIMPLEMENTED- метод не зарегистрирован на сервере. Проверь что использует тот же.proto.- Deadline immediately exceeded - context.WithTimeout уже прошёл к моменту вызова. Проверь chain контекстов.
INTERNAL: stream terminated by RST_STREAM- HTTP/2-уровень ошибка, обычно proxy timeout (Envoy idle timeout 1h, NGINX 60s).tcpdump+tshark -Y http2.max-message size exceeded- default gRPC msg limit 4 MB.grpc.MaxRecvMsgSize(64<<20)поднять.- HPACK ошибки - см. http2-internals, часто bug в server library.
Полезные tools
- grpcurl - curl для gRPC, использует server-reflection или .proto
- buf - современный Protobuf builder/linter (заменяет protoc)
- BloomRPC, Postman - GUI клиенты для interactive testing
- ghz - load testing (как hey/wrk для gRPC)
- server-reflection - сервер описывает свои RPC, grpcurl без .proto