Files
returntozer0.ru-blog/content/posts/gRPS.md
2026-04-06 16:46:37 +00:00

12 KiB
Raw Blame History

title, tags, categories
title tags categories
Почему gRPC это лучший выбор для back-to-back взаимодействия
backend
go
tech

gRPC - это высокопроизводительный фреймворк удаленных вызовов процедур от Google, построенный на HTTP/2 и Protocol Buffers. Он быстрее REST, строго типизирован и отлично масштабируется в микросервисных архитектурах.


Что такое gRPC?

Google Remote Procedure Call - это современный open-source RPC-фреймворк, позволяющий сервисам вызывать методы друг друга так, как если бы они были локальными функциями. Вместо того чтобы думать о HTTP-запросах и JSON-сериализации, разработчик просто вызывает метод на удаленном сервисе.

gRPC был разработан и открыт компанией Google в 2015 году. Он вырос из внутренней системы Stubby, которую Google использовал более десяти лет для связи между своими микросервисами, обрабатывающими миллиарды запросов в секунду.

Сегодня проект находится под управлением Cloud Native Computing Foundation (CNCF) - той же организации, что курирует Kibernetes и Prometheus. Это гарантирует его нейтральное развитие и широкую поддержку индустрии.


Ключевые особенности

Protobuf вместо JSON

gRPC использует Protobuf - бинарный формат сериализации данных. В отличие от текстового JSON:

  • Данные занимают сильно меньше места
  • Сериализация и десериализация происходят также быстрее
  • Схема данных строго типизирована и версионируется через .proto-файлы
  • Автоматическая кодогенерация клиентов и серверов на 10+ языках

HTTP/2 под капотом

Это дает следующие возможности:

  • Мультиплексирование
  • Двунаправленный стриминг
  • Сжатие заголовков
  • Server Push

Четыре режима взаимодействия

Unary RPC           → клиент шлёт запрос, сервер возвращает ответ (как REST)
Server Streaming    → сервер стримит поток ответов на один запрос
Client Streaming    → клиент стримит данные, сервер возвращает один ответ
Bidirectional       → оба шлют потоки данных друг другу одновременно

Строгий контракт через .proto

.proto-файл — это единственный источник правды для всей коммуникации. Если один сервис меняет API, компилятор Protobuf немедленно сломает несовместимых клиентов ещё на этапе сборки.

Встроенные инструменты

  • Deadline / Timeout — встроенный контроль времени ожидания
  • Cancellation — отмена запросов по цепочке вызовов
  • Load Balancing — клиентская балансировка нагрузки из коробки
  • Health Checking — стандартный протокол проверки готовности сервиса
  • Interceptors — аналог middleware для аутентификации, логирования, трассировки

Пример: сервис пользователей на gRPC

Разберём полный цикл: от .proto-контракта до рабочего клиента и сервера на Go.

Шаг 1. Определяем контракт (user.proto)

syntax = "proto3";
 
package user;
option go_package = "github.com/example/user-service/proto";
 
// Запрос на получение пользователя
message GetUserRequest {
  int64 id = 1;
}
 
// Ответ с данными пользователя
message GetUserResponse {
  int64  id         = 1;
  string name       = 2;
  string email      = 3;
  string created_at = 4;
}
 
// Запрос на создание пользователя
message CreateUserRequest {
  string name  = 1;
  string email = 2;
}
 
// Сервис с двумя методами
service UserService {
  rpc GetUser    (GetUserRequest)    returns (GetUserResponse);
  rpc CreateUser (CreateUserRequest) returns (GetUserResponse);
}

Шаг 2. Генерируем код

# Устанавливаем плагины
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
 
# Генерируем Go-код из .proto
protoc --go_out=. --go-grpc_out=. proto/user.proto

После этого компилятор создаст два файла:

  • user.pb.go — структуры данных (GetUserRequest, GetUserResponse и т.д.)
  • user_grpc.pb.go — интерфейсы сервера и заглушки клиента

Шаг 3. Реализуем сервер (server/main.go)

package main
 
import (
    "context"
    "fmt"
    "log"
    "net"
    "time"
 
    pb "github.com/example/user-service/proto"
    "google.golang.org/grpc"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
)
 
// userServer реализует интерфейс pb.UserServiceServer
type userServer struct {
    pb.UnimplementedUserServiceServer
    // В реальности здесь была бы БД
    users map[int64]*pb.GetUserResponse
}
 
// GetUser — обработчик метода GetUser
func (s *userServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
    user, ok := s.users[req.Id]
    if !ok {
        // gRPC-статус коды вместо HTTP-кодов
        return nil, status.Errorf(codes.NotFound, "пользователь с id=%d не найден", req.Id)
    }
    return user, nil
}
 
// CreateUser — обработчик метода CreateUser
func (s *userServer) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.GetUserResponse, error) {
    if req.Email == "" {
        return nil, status.Error(codes.InvalidArgument, "email обязателен")
    }
 
    newID := int64(len(s.users) + 1)
    user := &pb.GetUserResponse{
        Id:        newID,
        Name:      req.Name,
        Email:     req.Email,
        CreatedAt: time.Now().Format(time.RFC3339),
    }
    s.users[newID] = user
 
    return user, nil
}
 
func main() {
    // Слушаем порт
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("не удалось запустить listener: %v", err)
    }
 
    // Создаём gRPC-сервер
    grpcServer := grpc.NewServer()
 
    // Регистрируем наш сервис
    pb.RegisterUserServiceServer(grpcServer, &userServer{
        users: make(map[int64]*pb.GetUserResponse),
    })
 
    fmt.Println("gRPC-сервер запущен на :50051")
    if err := grpcServer.Serve(lis); err != nil {
        log.Fatalf("ошибка сервера: %v", err)
    }
}

Шаг 4. Пишем клиент (client/main.go)

package main
 
import (
    "context"
    "fmt"
    "log"
    "time"
 
    pb "github.com/example/user-service/proto"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
)
 
func main() {
    // Подключаемся к серверу
    conn, err := grpc.Dial(
        "localhost:50051",
        grpc.WithTransportCredentials(insecure.NewCredentials()),
    )
    if err != nil {
        log.Fatalf("не удалось подключиться: %v", err)
    }
    defer conn.Close()
 
    // Создаём клиента — это та самая "магия" gRPC:
    // вызов метода выглядит как локальная функция
    client := pb.NewUserServiceClient(conn)
 
    // Контекст с таймаутом — хорошая практика для каждого запроса
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
 
    // Создаём пользователя
    created, err := client.CreateUser(ctx, &pb.CreateUserRequest{
        Name:  "Алиса",
        Email: "alice@example.com",
    })
    if err != nil {
        log.Fatalf("ошибка CreateUser: %v", err)
    }
    fmt.Printf("Создан пользователь: ID=%d, Name=%s\n", created.Id, created.Name)
 
    // Получаем пользователя по ID
    user, err := client.GetUser(ctx, &pb.GetUserRequest{Id: created.Id})
    if err != nil {
        log.Fatalf("ошибка GetUser: %v", err)
    }
    fmt.Printf("Получен пользователь: %s <%s>\n", user.Name, user.Email)
}

Шаг 5. Запускаем

# Терминал 1: запускаем сервер
go run server/main.go
# gRPC-сервер запущен на :50051
 
# Терминал 2: запускаем клиент
go run client/main.go
# Создан пользователь: ID=1, Name=Алиса
# Получен пользователь: Алиса <alice@example.com>

gRPC vs REST: когда что выбирать?

Критерий REST gRPC
Скорость Хорошая Отличная (бинарный протокол)
Типизация Слабая (JSON) Строгая (Protobuf)
Стриминг Ограниченный (SSE) Полноценный (4 режима)
Браузерная поддержка Нативная Нужен gRPC-Web
Отладка Легко (curl, Postman) Сложнее (нужен grpcurl)
Контракт API OpenAPI/Swagger .proto (строже)
Кодогенерация Опционально Встроена

Выбирайте gRPC, когда:

  • Сервисы общаются только между собой (backend-to-backend)
  • Важна производительность и объём трафика
  • Нужен стриминг данных в реальном времени
  • Команда большая и строгий контракт критичен

Выбирайте REST, когда:

  • API потребляется браузером напрямую
  • Важна простота и человекочитаемость
  • Команда небольшая и overhead Protobuf не оправдан

Итог

gRPC — это не просто «быстрый REST». Это принципиально другой подход к межсервисному взаимодействию, где строгий контракт, бинарная эффективность и встроенный стриминг делают его идеальным фундаментом для микросервисных архитектур. Google, Netflix, Cloudflare и десятки других компаний используют его в продакшне под нагрузками, которые REST с JSON попросту не выдержал бы.

Если вы строите систему из нескольких сервисов — gRPC стоит рассмотреть как первый выбор для их взаимодействия.