gRPC: современный RPC framework для микросервисов

Protocol Buffers, streaming, и high-performance коммуникация

gRPC: современный RPC-фреймворк для микросервисной архитектуры По мере роста микросервисных систем REST-архитектура начинает обнаруживать свои пределы. Десятки сервисов обмениваются сотнями запросов в секунду, схемы эволюционируют независимо, команды договариваются о контрактах в Slack, а не в коде. JSON парсится снова и снова, типизация теряется на границе HTTP, overfetching и underfetching становятся нормой. В этот момент инженеры обращаются к gRPC — высокопроизводительному RPC-фреймворку от Google, который решает большинство этих проблем системно. В этой статье разберём, что такое gRPC, как он работает, в чём его преимущества перед REST и когда его действительно стоит использовать в production-системах. Что такое gRPC и Protocol Buffers gRPC (Google Remote Procedure Call) — это открытый высокопроизводительный фреймворк для вызова удалённых процедур, разработанный Google и открытый в 2015 году. Сегодня он является частью экосистемы CNCF (Cloud Native Computing Foundation) и используется в крупнейших распределённых системах мира. gRPC построен на двух ключевых технологиях: Protocol Buffers (protobuf) — язык описания интерфейсов и бинарный формат сериализации данных от Google. Вместо JSON-текста данные кодируются в компактный бинарный формат, что в 3-10 раз быстрее парсинга и в 2-5 раз меньше по размеру. HTTP/2 — транспортный протокол, обеспечивающий мультиплексирование запросов, двунаправленную потоковую передачу, сжатие заголовков и постоянное соединение. Разработчик описывает контракт сервиса в .proto-файле: методы, типы входных и выходных данных. Из этого файла автоматически генерируется клиентский и серверный код на любом поддерживаемом языке: Go, Java, C++, Python, Node.js, Ruby, C#, Swift, Kotlin, Dart и других. gRPC vs REST: детальное сравнение Выбор между gRPC и REST — это не вопрос «что лучше», а вопрос «что подходит для конкретной задачи». Рассмотрим ключевые различия: Протокол: REST использует HTTP/1.1 (или HTTP/2 опционально), gRPC требует HTTP/2 обязательно. Формат данных: REST — текстовый JSON (или XML), gRPC — бинарный Protocol Buffers. Типизация: REST не имеет встроенной типизации (OpenAPI/Swagger — дополнительный слой), gRPC — строгая схема в .proto-файле, генерация кода. Производительность: gRPC значительно быстрее за счёт бинарной сериализации и HTTP/2 мультиплексирования — в 5-10 раз по некоторым бенчмаркам. Streaming: REST поддерживает Server-Sent Events и WebSocket как отдельные протоколы, gRPC имеет встроенные 4 типа потоковой передачи. Браузерная поддержка: REST нативно поддерживается в браузерах, gRPC требует grpc-web прокси для работы в браузере. Читаемость: REST-запросы читаемы в DevTools и curl, gRPC — бинарный протокол, для дебаггинга нужны специальные инструменты (grpcurl, Postman). Контракт: REST — документация и конвенции, gRPC — строгий .proto-файл, нарушение которого не скомпилируется. Экосистема: REST — огромная, десятилетиями развиваемая. gRPC — активно растёт, но меньше. Пример .proto файла Вот как выглядит описание сервиса управления пользователями в Protocol Buffers: syntax = "proto3"; package user.v1; option go_package = "github.com/company/service/gen/user/v1"; // Сообщение пользователя message User { string id = 1; string email = 2; string full_name = 3; int64 created_at = 4; UserRole role = 5; } // Enum ролей enum UserRole { USER_ROLE_UNSPECIFIED = 0; USER_ROLE_ADMIN = 1; USER_ROLE_EDITOR = 2; USER_ROLE_VIEWER = 3; } message GetUserRequest { string id = 1; } message CreateUserRequest { string email = 1; string full_name = 2; UserRole role = 3; } message ListUsersRequest { int32 page = 1; int32 page_size = 2; string filter = 3; } message ListUsersResponse { repeated User users = 1; int32 total = 2; int32 page = 3; } service UserService { rpc GetUser(GetUserRequest) returns (User); rpc CreateUser(CreateUserRequest) returns (User); rpc WatchUsers(ListUsersRequest) returns (stream User); rpc BatchCreateUsers(stream CreateUserRequest) returns (ListUsersResponse); rpc SyncUsers(stream GetUserRequest) returns (stream User); } Из этого файла командой protoc или buf generate генерируется типизированный код клиента и сервера на нужном языке. Никакой ручной сериализации, никаких строковых имён полей — всё строго типизировано на уровне компилятора. Четыре типа вызовов в gRPC gRPC поддерживает четыре паттерна взаимодействия, что делает его значительно гибче классического REST: 1. Unary RPC (одиночный вызов) Классический запрос-ответ, аналог REST. Клиент отправляет один запрос, сервер возвращает один ответ. Используется для большинства CRUD-операций. const user = await client.getUser({ id: "user-123" }); console.log(user.fullName); 2. Server Streaming RPC (серверный стриминг) Клиент отправляет один запрос, сервер возвращает поток ответов. Идеально для: экспорта больших наборов данных, real-time обновлений котировок или метрик, прогресса длительных операций. const stream = client.watchUsers({ filter: "role:admin" }); for await (const user of stream) { console.log("Обновлён пользователь:", user.email); } 3. Client Streaming RPC (клиентский стриминг) Клиент отправляет поток запросов, сервер возвращает один ответ. Применяется для: батч-загрузки данных, аггрегации телеметрии, загрузки файлов по частям. const call = client.batchCreateUsers(); for (const userData of newUsersArray) { call.write(userData); } const result = await call.end(); console.log("Создано: " + result.total + " пользователей"); 4. Bidirectional Streaming RPC (двунаправленный стриминг) Обе стороны обмениваются потоками независимо. Это основа для: чат-приложений, real-time игр, синхронизации состояния между сервисами, систем с обратной связью. Пример gRPC-сервера на Node.js Вот минимальный пример реализации UserService на Node.js с использованием библиотеки @grpc/grpc-js : const grpc = require("@grpc/grpc-js"); const protoLoader = require("@grpc/proto-loader"); const path = require("path"); const packageDefinition = protoLoader.loadSync( path.join(__dirname, "user.proto"), { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true } ); const { user: { v1: { UserService } } } = grpc.loadPackageDefinition(packageDefinition); const users = new Map(); const serviceImpl = { getUser(call, callback) { const user = users.get(call.request.id); if (!user) { return callback({ code: grpc.status.NOT_FOUND, message: "Пользователь не найден" }); } callback(null, user); }, createUser(call, callback) { const id = crypto.randomUUID(); const user = { id, email: call.request.email, full_name: call.request.full_name, role: call.request.role, created_at: Date.now() }; users.set(id, user); callback(null, user); }, watchUsers(call) { const filter = call.request.filter; const interval = setInterval(() = { for (const user of users.values()) { if (!filter || user.role === filter) { call.write(user); } } }, 1000); call.on("cancelled", () = clearInterval(interval)); } }; const server = new grpc.Server(); server.addService(UserService.service, serviceImpl); server.bindAsync( "0.0.0.0:50051", grpc.ServerCredentials.createInsecure(), (error, port) = { if (error) throw error; console.log("gRPC сервер запущен на порту " + port); } ); Аутентификация в gRPC: TLS и JWT gRPC предоставляет встроенные механизмы для безопасного взаимодействия между сервисами. TLS / mTLS Для шифрования транспортного уровня gRPC поддерживает TLS из коробки. В production-системах рекомендуется mutual TLS (mTLS) — когда оба сервиса предъявляют сертификаты друг другу, что исключает подмену сервисов внутри кластера. const credentials = grpc.ServerCredentials.createSsl( fs.readFileSync("ca.crt"), [{ cert_chain: fs.readFileSync("server.crt"), private_key: fs.readFileSync("server.key") }], true ); server.bindAsync("0.0.0.0:50051", credentials, callback); JWT через metadata Для авторизации пользовательских запросов используются gRPC metadata — аналог HTTP-заголовков. JWT-токен передаётся в metadata и проверяется на сервере через interceptor: const metadata = new grpc.Metadata(); metadata.add("authorization", "Bearer " + jwtToken); client.getUser({ id: "123" }, metadata, callback); function authInterceptor(call, callback, next) { const token = call.metadata.get("authorization")[0]; if (!token || !verifyJWT(token)) { return callback({ code: grpc.status.UNAUTHENTICATED }); } next(call, callback); } Когда gRPC НЕ нужен gRPC — мощный инструмент, но не серебряная пуля. Есть ситуации, где он будет излишним или проблематичным: Публичные API для сторонних разработчиков: REST и JSON — де-факто стандарт для публичных API. Требовать от партнёров генерировать grpc-клиент из вашего .proto — высокий порог входа. Простые CRUD-сервисы с небольшой нагрузкой: Если у вас 100 RPS и простая схема данных, overhead на настройку gRPC не оправдан. REST справится. Команды без опыта в protobuf: Кривая обучения существует. Если команда маленькая и нет времени на онбординг, REST будет продуктивнее. Браузерные клиенты без grpc-web: Нативный gRPC не работает в браузере. Нужна настройка Envoy или grpc-web прокси. Среды с HTTP/1.1-only инфраструктурой: Некоторые корпоративные прокси и файрволы не пропускают HTTP/2. Перед внедрением нужно проверить инфраструктуру. grpc-web: gRPC для браузеров Нативный gRPC требует HTTP/2 с поддержкой trailers — возможности, которой нет в браузерных XMLHttpRequest и fetch API. Решение — grpc-web , протокол, транслирующий gRPC-запросы через HTTP/1.1 или HTTP/2 без трейлеров. Архитектура с grpc-web: Браузер отправляет grpc-web запрос (HTTP/1.1 с Content-Type: application/grpc-web) Прокси (обычно Envoy или nginx с модулем) транслирует его в настоящий gRPC Backend-сервис получает стандартный gRPC запрос Для TypeScript-проектов есть официальный пакет grpc-web и плагин для protoc, генерирующий типизированный браузерный клиент. Это позволяет использовать единый .proto-файл как source of truth для backend, мобильных и веб-клиентов одновременно. grpc-web поддерживает только Unary и Server Streaming вызовы — Client Streaming и Bidirectional Streaming в браузере недоступны из-за ограничений HTTP/1.1. Кто использует gRPC в production gRPC применяется в системах с самыми высокими требованиями к производительности и масштабируемости: Google: gRPC изначально создан для внутренней инфраструктуры Google, где он обрабатывает миллиарды вызовов в секунду между сервисами. Netflix: использует gRPC для взаимодействия между микросервисами, обеспечивающими стриминг для 270+ миллионов подписчиков. Square: перевёл большую часть internal API на gRPC, сократив задержки и упростив контрактную разработку между командами. Cisco: применяет gRPC в сетевом оборудовании для телеметрии и управления конфигурацией в реальном времени. Uber: использует gRPC как транспорт в собственном фреймворке Yarpc для тысяч микросервисов. Cloudflare, Dropbox, CoreOS: активно применяют gRPC в критической инфраструктуре. Заключение gRPC — это зрелый, battle-tested фреймворк, который решает реальные проблемы растущих микросервисных систем: строгие контракты между командами, высокая производительность при большом количестве вызовов, встроенная поддержка стриминга и кодогенерация для всех языков. Его стоит рассматривать, если у вас: более 5-7 микросервисов, которые активно общаются между собой; нагрузка, при которой латентность REST становится узким местом; несколько команд или языков, которым нужен единый строгий контракт; real-time требования (стриминг данных, события). Для простых CRUD-приложений и публичных API REST остаётся отличным выбором. Но для высоконагруженной внутренней инфраструктуры gRPC даёт принципиально другой уровень производительности и надёжности контрактов. В 404gen мы используем gRPC в проектах с микросервисной архитектурой и высокими требованиями к latency. Если вам нужна консультация по выбору архитектуры или разработка backend-системы — напишите нам .