Перейти к основному содержимому

gRPC в Go

Разработчику Архитектору

gRPC — RPC-фреймворк от Google поверх HTTP/2 и Protocol Buffers. Контракт описывают в .proto, код генерируют protoc и плагины Go. Статья дополняет обзор REST, GraphQL и gRPC и справочник по gRPC.

См. также: Веб на stdlib · Gin · Дженерики · Асинхронность.


gRPC и REST — когда что выбирать

КритерийgRPCREST (JSON)
ФорматБинарный ProtobufТекст JSON
ТранспортHTTP/2, multiplexingHTTP/1.1 или HTTP/2
Контракт.proto, строгая схемаOpenAPI, часто «по факту»
БраузерНужен grpc-web / proxyНативно
СтримингUnary, server/client/bidi streamSSE, WebSocket отдельно
Типичное применениеМикросервис ↔ микросервисПубличное API, BFF

В Go один процесс часто поднимает REST для внешних клиентов и gRPC для внутренних вызовов — см. пример микросервиса.


Инструменты

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

Разбор:

  • protoc (компилятор protobuf) ставят отдельно с protobuf.dev.
  • protoc-gen-go генерирует Go-структуры сообщений.
  • protoc-gen-go-grpc генерирует интерфейсы сервиса и регистрацию в gRPC.

В go.mod обычно появляются google.golang.org/grpc и google.golang.org/protobuf.


Файл .proto

Минимальный контракт телефонной книги (упрощённый учебный пример):

syntax = "proto3";

package phonebook.v1;
option go_package = "example.com/phonebook/pb;pb";

message Contact {
string name = 1;
string phone = 2;
}

message GetRequest {
string name = 1;
}

message GetResponse {
Contact contact = 1;
}

service PhoneBook {
rpc Get(GetRequest) returns (GetResponse);
}

Разбор:

  • proto3 — актуальная версия синтаксиса; поля нумеруются и не меняют номер после релиза.
  • go_package задаёт import path и имя Go-пакета (pb).
  • service PhoneBook объявляет RPC-метод Get с типами запроса и ответа.

Генерация:

protoc --go_out=. --go-grpc_out=. \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
phonebook/v1/phonebook.proto

Разбор:

  • paths=source_relative кладёт .pb.go рядом с .proto — удобно в монорепо.
  • В репозитории часто коммитят сгенерированный код или генерируют в CI через go:generate.

Сервер

type server struct {
pb.UnimplementedPhoneBookServer
db map[string]string
}

func (s *server) Get(ctx context.Context, req *pb.GetRequest) (*pb.GetResponse, error) {
phone, ok := s.db[req.GetName()]
if !ok {
return nil, status.Errorf(codes.NotFound, "contact %q", req.GetName())
}
return &pb.GetResponse{
Contact: &pb.Contact{Name: req.GetName(), Phone: phone},
}, nil
}

func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatal(err)
}
s := grpc.NewServer()
pb.RegisterPhoneBookServer(s, &server{db: map[string]string{"Ann": "+1-555-0100"}})
log.Fatal(s.Serve(lis))
}

Разбор:

  • Встраивание UnimplementedPhoneBookServer — forward compatibility: новые методы в .proto не ломают старый бинарник до реализации.
  • status.Errorf(codes.NotFound, ...) переводит доменную ошибку в gRPC-код; клиент читает status.Code(err).
  • Порт :50051 — распространённый convention для gRPC (не стандарт, а привычка).

Клиент

conn, err := grpc.Dial("localhost:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal(err)
}
defer conn.Close()

client := pb.NewPhoneBookClient(conn)
resp, err := client.Get(context.Background(), &pb.GetRequest{Name: "Ann"})
if err != nil {
log.Fatal(err)
}
fmt.Println(resp.GetContact().GetPhone())

Разбор:

  • grpc.Dial создаёт долгоживущее HTTP/2-соединение; клиент переиспользует его для многих RPC.
  • insecure.NewCredentials() — только для локальной разработки; в проде — TLS (credentials.NewClientTLSFromFile или mTLS).
  • Первый аргумент RPC — всегда context.Context для таймаутов и отмены — см. асинхронность.
Безопасность

В production включайте TLS или mTLS между сервисами. Незашифрованный gRPC допустим лишь в доверенной сети или на localhost.


Streaming (кратко)

gRPC поддерживает четыре вида вызовов:

ВидНаправление
Unaryодин запрос — один ответ
Server streamingклиент шлёт один запрос, сервер — поток ответов
Client streamingклиент шлёт поток, сервер — один ответ
Bidirectionalоба потока одновременно

В .proto это задаёт ключевое слово stream у аргумента или возвращаемого типа. На сервере реализуют Send/Recv на stream-объекте; на клиенте — аналогично. Для больших выборок из БД server streaming снижает задержку первого байта по сравнению с одним огромным JSON.


Ошибки и метаданные

  • Коды: codes.NotFound, InvalidArgument, DeadlineExceeded, Unavailable.
  • Проверка на клиенте:
st, ok := status.FromError(err)
if ok && st.Code() == codes.NotFound {
// ...
}
  • Metadata — аналог HTTP-заголовков (trace-id, auth token); передаётся через metadata.NewOutgoingContext.

Тестирование

  • Unit: мок интерфейса PhoneBookServer или тестовый in-memory сервер.
  • Интegration: bufconn / локальный listener без сети:
lis := bufconn.Listen(1024 * 1024)
// Register server on lis, dial через grpc.WithContextDialer
  • gRPC совместим с table-driven тестами; для HTTP-gateway поверх gRPC используют grpc-gateway (отдельная тема).

Связка с REST

Паттерн gRPC + grpc-gateway: один .proto, gRPC внутри кластера, JSON REST снаружи. Альтернатива — два контракта (OpenAPI + proto) с риском рассинхронизации.

Типичный путь сервиса контактов: CLIвеб на stdlibREST на Gin → gRPC (эта статья).


Ключевые тезисы

  • Контракт в .proto — источник правды для сообщений и RPC.
  • Сервер и клиент генерируются; ручной JSON не нужен между Go-сервисами.
  • context, коды status и TLS — обязательная часть production-разработки.
  • Для публичного API чаще оставляют REST; gRPC — для низкой задержки и строгих контрактов между сервисами.

Мини-практикум

  1. Опишите .proto с методами List (server stream) и Add (unary).
  2. Поднимите сервер и клиент на localhost; добавьте context.WithTimeout на вызов.
  3. Сравните размер payload Protobuf и JSON для одной структуры Contact.

Типичные ошибки

ОшибкаЧто делать
Менять номера полей в .protoТолько добавлять поля с новыми номерами
Забыть Unimplemented*ServerВстроить заглушку из generated-кода
grpc.Dial без TLS в продеНастроить credentials
Игнорировать ctx на сервереПроверять ctx.Done() в долгих операциях

Связанные материалы


Основа по протоколу

Базовый разбор HTTP и HTTPS — HTTP как основа веб-интеграций.

См. также

Другие статьи этого же раздела в боковом меню (как на странице "О разделе").