--- title: "Почему gRPC это лучший выбор для back-to-back взаимодействия" tags: ["backend", "go"] categories: ["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`) ```protobuf 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. Генерируем код ```bash # Устанавливаем плагины 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`) ```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`) ```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. Запускаем ```bash # Терминал 1: запускаем сервер go run server/main.go # gRPC-сервер запущен на :50051 # Терминал 2: запускаем клиент go run client/main.go # Создан пользователь: ID=1, Name=Алиса # Получен пользователь: Алиса ``` --- ## 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 стоит рассмотреть как первый выбор для их взаимодействия.