gRPC: обзор

gRPC — высокопроизводительный RPC-фреймворк от Google, использующий HTTP/2 и Protocol Buffers для бинарной передачи данных между сервисами.

Зачем нужно

В микросервисной архитектуре сервисы часто общаются через REST + JSON. gRPC заменяет это решение более эффективным: HTTP/2 мультиплексирует запросы, protobuf сериализует данные компактнее JSON, а схема .proto генерирует типизированный код на любом языке. Это снижает латентность и потребление ресурсов в высоконагруженных системах.

Где используется

  • Коммуникация между микросервисами (Google, Netflix, Uber)
  • gRPC-Web — в браузере через gRPC-Web прокси
  • Мобильные клиенты с жёсткими требованиями к трафику
  • Стриминг: потоковая передача данных между сервисами

gRPC vs REST

Критерий REST gRPC
Протокол HTTP/1.1 HTTP/2
Формат JSON (текст) Protobuf (бинарный)
Схема OpenAPI (опционально) .proto (обязательно)
Стриминг SSE/WebSocket (отдельно) Встроен (4 типа)
Браузер Нативно Через gRPC-Web прокси
Читаемость Высокая Требует инструментов

Четыре типа вызовов gRPC

service UserService {
  // Unary — один запрос, один ответ (как REST)
  rpc GetUser(GetUserRequest) returns (User);

  // Server Streaming — один запрос, поток ответов
  rpc ListUsers(ListUsersRequest) returns (stream User);

  // Client Streaming — поток запросов, один ответ
  rpc UploadUsers(stream CreateUserRequest) returns (UploadResult);

  // Bidirectional Streaming — поток в обе стороны
  rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}

Пример на Node.js (@grpc/grpc-js)

Сервер

// npm install @grpc/grpc-js @grpc/proto-loader
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');

const packageDef = protoLoader.loadSync('user.proto', {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
});
const proto = grpc.loadPackageDefinition(packageDef).myapp;

// Реализация сервиса
const server = new grpc.Server;
server.addService(proto.UserService.service, {
  // Unary
  getUser: (call, callback) => {
    const user = db.users.find(u => u.id === call.request.id);
    if (!user) {
      return callback({ code: grpc.status.NOT_FOUND, message: 'User not found' });
    }
    callback(null, user);
  },

  // Server Streaming
  listUsers: (call) => {
    const users = db.users.getAll;
    users.forEach(user => call.write(user));
    call.end();
  },
});

server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure, () => {
  console.log('gRPC server running on port 50051');
});

Клиент

const client = new proto.UserService(
  'localhost:50051',
  grpc.credentials.createInsecure
);

// Unary вызов
client.getUser({ id: 42 }, (err, user) => {
  if (err) console.error(err);
  else console.log(user.name);
});

// Server Streaming
const stream = client.listUsers({ page: 1, limit: 100 });
stream.on('data', user => console.log(user.name));
stream.on('end', () => console.log('Стриминг завершён'));
stream.on('error', err => console.error(err));

Частые ошибки

  • gRPC недоступен в браузере напрямую — нужен Envoy или grpc-web прокси
  • Изменение номеров полей в .proto — ломает обратную совместимость
  • Не обрабатывают gRPC status codes (NOT_FOUND, UNAUTHENTICATED, etc.) на клиенте
  • Не настраивают TLS в продакшне — createInsecure только для разработки

Связанные темы

Ресурсы