# gRPC - HTTP/2 + Protobuf RPC framework _Сеть: L4 и выше · LinuxLab Knowledge Base_ **TL;DR:** gRPC = HTTP/2 + Protocol Buffers + кодогенерация. Четыре типа RPC: unary (как REST), server-stream, client-stream, bidirectional. Сильная типизация, бинарный wire format, multi-language. grpcurl как curl для gRPC. ## Зачем 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 - язык контрактов ```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 ```proto rpc GetUser(GetUserRequest) returns (User); ``` Клиент шлёт один request, получает один response. 99% gRPC-вызовов в microservices - именно unary. ```go resp, err := client.GetUser(ctx, &pb.GetUserRequest{UserId: "u123"}) ``` ### 2. Server streaming ```proto rpc ListUsers(ListUsersRequest) returns (stream User); ``` Клиент шлёт один request, сервер отправляет **несколько** ответов через тот же stream. Завершается когда сервер закрывает. ```go 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 ```proto rpc CreateUsers(stream CreateUserRequest) returns (CreateUsersResponse); ``` Клиент шлёт **несколько** request'ов, сервер отвечает один раз когда client закрывает stream. Применение: bulk-upload, file streaming. ### 4. Bidirectional streaming ```proto rpc Chat(stream ChatMessage) returns (stream ChatMessage); ``` Обе стороны шлют независимо. **Похоже на [websocket](/kb/websocket.md)**, но с 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] [] │ │ │ │ │ └── 36 байт payload │ └── length (big-endian uint32) └── compressed-flag (0=no, 1=yes) HEADERS (trailers) grpc-status = 0 grpc-message = ``` - **Path** = `/./` - **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|клиент-сертификаты]]: ```go 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: ```go ctx, cancel := context.WithTimeout(ctx, 2*time.Second) defer cancel() client.GetUser(ctx, ...) ``` Deadline **передаётся серверу** через header `grpc-timeout`. Сервер знает - если deadline прошёл, сразу вернёт DEADLINE_EXCEEDED, не тратя ресурсы. Каскадно передаётся дальше в downstream calls. ### Metadata - кастомные headers ```go md := metadata.Pairs("auth-token", "secret123", "x-request-id", "abc") ctx = metadata.NewOutgoingContext(ctx, md) ``` На сервере: ```go md, _ := metadata.FromIncomingContext(ctx) token := md.Get("auth-token") ``` ### Retry config Через service config (JSON в connection options): ```json { "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: ```yaml 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](/kb/http2-internals.md), часто 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 ## Команды ```bash grpcurl -plaintext localhost:50051 list ``` Список services через server-reflection (если включён в server) ```bash grpcurl -plaintext -d '{"user_id":"u1"}' localhost:50051 myservice.UserService/GetUser ``` Сделать unary RPC - JSON автоматически конвертится в protobuf ```bash grpcurl -import-path . -proto user.proto -d '{}' localhost:50051 myservice.UserService/ListUsers ``` Без reflection - указать .proto явно ```bash protoc --go_out=. --go-grpc_out=. -I=proto user.proto ``` Сгенерить Go-код из .proto (нужны protoc-gen-go и protoc-gen-go-grpc) ```bash buf generate ``` Современная альтернатива protoc - buf использует buf.yaml + buf.gen.yaml ```bash ghz --proto user.proto --call myservice.UserService.GetUser -d '{}' -c 100 -n 10000 host:50051 ``` Load test 100 concurrent, 10K total RPC ```bash tshark -Y 'http2 && http2.headers.path contains grpc' -i eth0 ``` Перехват gRPC-трафика в Wireshark CLI ```bash openssl s_client -alpn h2 -connect grpc.example.com:443 < /dev/null 2>&1 | grep -i alpn ``` Проверить, что endpoint advertise'ит HTTP/2 через ALPN (нужно для gRPC) ## См. также - [HTTP/2 internals - binary framing, HPACK, stream multiplexing](/kb/http2-internals.md) - [HTTP/1.1, HTTP/2, HTTP/3](/kb/http-protocol.md) - [TLS-сертификаты - X.509, цепочка доверия, Let's Encrypt](/kb/tls-certificates.md) - [Kubernetes Service и Ingress - сетевая публикация подов](/kb/kubernetes-services-and-ingress.md) - [WebSocket - bidirectional поверх HTTP](/kb/websocket.md) - [OpenTelemetry: signals, OTLP, Collector pipeline](/kb/opentelemetry.md) - [Distributed tracing: span, context propagation, sampling](/kb/tracing-basics.md)