Содержание
  1. Исходные данные и правило выборки
  2. Полный telemetry.proto
  3. OpenAPI paths-фрагмент для панели
  4. Карта выбора A/B
  5. Точные результаты выборки
  6. Конечный поток и прикладной курсор
  7. Эволюция контракта и reserved
  8. Нативный gRPC, gRPC-Web и другие HTTP API
  9. Как читать проверку и что делать при расхождении
  10. Частые вопросы
Подборки и сравнения

gRPC или REST: клиенты, контракт и выгрузка данных

5 ноября 2026 · 9 минут чтения

gRPC или REST: клиенты, контракт и выгрузка данных — это выбор между двумя контрактами для двух разных потребителей одного сервиса телеметрии. Браузерной панели нужен последний замер одного датчика через обычный GET и JSON. Внутреннему обработчику истории нужна ограниченная выборка измерений несколькими сообщениями с явной границей последовательности. Ниже — полный контракт, правило выборки и проверка результата.

Исходные данные и правило выборки

Учебный сервис телеметрии мастерской хранит четыре измерения. Имена, записи и границы сочинены для примера.

seq sensor_id temp_milli_c
41 S-17 22800
42 S-17 22900
43 S-18 25100
44 S-17 23125

temp_milli_c — целые тысячные градуса Цельсия в нашей модели. seq уникален в этом наборе.

Правило выборки для внутреннего обработчика: совпадение sensor_id и строгое неравенство after_seq < seq <= through_seq, порядок по возрастанию seq. Поток заканчивается после последней подходящей записи. Это ограниченная выгрузка.

gRPC сам это правило не реализует: нужен код сервиса и согласованное хранилище. Транспорт передаёт сообщения, а фильтрацию по датчику и границам выполняет приложение.

Два потребителя:

  • A — браузерная панель. Нужен последний замер одного датчика через обычный GET и JSON.
  • B — внутренний Go/Python-обработчик истории. Получает ограниченную выборку измерений несколькими сообщениями, оба конца контролируются, нужна явно заданная граница последовательности.

Требований по гарантированной скорости нет. Выбор опирается на контракт, клиентов и семантику выгрузки.

Полный telemetry.proto

Контракт для B описан в proto-файле. Все входные и выходные типы определены. Схема не является готовой реализацией сервера.

PROTO17 строк
syntax = "proto3";
package workshop.telemetry;
service TelemetryService {
  rpc ReadLatest (SensorRequest) returns (Sample);
  rpc ExportSamples (ExportRequest) returns (stream Sample);
}
message SensorRequest { string sensor_id = 1; }
message ExportRequest {
  string sensor_id = 1;
  uint64 after_seq = 2;
  uint64 through_seq = 3;
}
message Sample {
  string sensor_id = 1;
  uint64 seq = 2;
  sint32 temp_milli_c = 3;
}

Два RPC: ReadLatest возвращает один Sample, ExportSamples возвращает поток Sample. В документации gRPC на grpc.io серверный поток описан так: сервер возвращает поток сообщений в ответ на запрос клиента, после отправки всех сообщений передаёт статус и завершает обработку. Клиент завершается, получив все сообщения сервера.

Protobuf — выбранный формат по умолчанию в этом нативном gRPC-маршруте. В документации gRPC на grpc.io указано, что по умолчанию gRPC использует protocol buffers как язык описания интерфейса и как формат обмена сообщениями. API-данные ниже записаны в нормализованной читательской форме и не изображают Protobuf wire bytes.

Порядок сообщений внутри одного RPC gRPC гарантирует. Это свойство транспорта, и оно не заменяет прикладную фильтрацию.

OpenAPI paths-фрагмент для панели

Для A достаточно одного GET. Ниже — фрагмент paths.

YAML23 строки
paths:
  /v1/sensors/{sensorId}/latest:
    get:
      operationId: readLatestSample
      parameters:
        - name: sensorId
          in: path
          required: true
          schema: {type: string}
      responses:
        '200':
          description: Latest sample in the selected dataset
          content:
            application/json:
              schema:
                type: object
                required: [sensor_id, seq, temp_milli_c]
                properties:
                  sensor_id: {type: string}
                  seq: {type: integer}
                  temp_milli_c: {type: integer}
        '404':
          description: No sample for this sensor

Для GET /v1/sensors/S-17/latest ручной эталон: 200 и {"sensor_id":"S-17","seq":44,"temp_milli_c":23125}. Для неизвестного S-99 в нашем REST-контракте 404.

В спецификации OpenAPI 3.1 на spec.openapis.org параметр пути помечается required: true, а ответы описываются в объекте responses. Схема не является готовым сервером: обработчик, хранилище и политику кэша пишет команда.

Политика HTTP-кэша задаётся отдельно по допустимой давности, заголовкам и доступу. Сам GET не гарантирует актуальность измерения.

Карта выбора A/B

Карта объединяет восемь критериев сравнения и отдельную строку итогового выбора.

Поле A: браузерная панель B: внутренняя ограниченная выгрузка
Контракт Один REST GET и JSON ReadLatest/ExportSamples из telemetry.proto
Выбранный транспорт Обычный HTTP для REST Нативный gRPC HTTP/2 в выбранной архитектуре
Сообщения JSON-объект последнего замера Protobuf Sample, каждое сообщение имеет seq
Ответ Один объект GET в этом сценарии server streaming конечной выборки
Кэш/повтор HTTP-политика отдельно; проверить давность origin Повтор по after_seq только по нашей прикладной семантике, gRPC не хранит курсор
Распределение нагрузки Выбранная инфраструктура HTTP Resolver и поддерживаемая клиентская политика, например round_robin; L7-прокси как вариант
Отладка DevTools/curl и данный контракт Совместимый gRPC-инструмент со схемой; reflection отдельно включается при необходимости
Эволюция Согласовать поля REST-представления Сохранить номера Sample; удалённые номера/имена резервировать
Первый кандидат REST gRPC

Клиентская балансировка gRPC существует. В документации gRPC на grpc.io описано, что политика получает список IP-адресов от resolver и отвечает за соединения к серверам. По умолчанию используется pick_first, который не распределяет обращения по всем адресам, а берёт первый доступный. Политику round_robin можно включить через service config; набор политик зависит от языка.

L7-прокси — вариант. Если B ходит к разным backend, одинаковый запрос должен опираться на согласованный набор данных; балансировщик не создаёт такую согласованность сам.

Точные результаты выборки

Для ExportSamples(sensor_id=S-17, after_seq=41, through_seq=44) ручной эталон — два сообщения: (S-17,42,22900), затем (S-17,44,23125), затем завершение.

Запись 43 не проходит фильтр датчика, хотя попадает в числовой интервал: у неё sensor_id=S-18. Запись 41 исключена строгой нижней границей.

Запрос Результат
after_seq=41, through_seq=44 [42, 44]
after_seq=42, through_seq=44 [44]
after_seq=44, through_seq=44 []

Повтор с after_seq=42 даёт [44], с after_seq=44 даёт пустую завершённую выборку. Если 43 попал в S-17 или 41 повторился, неверна прикладная фильтрация.

through_seq фиксирует верхнюю границу. Автоматическую транзакционную snapshot-изоляцию и надёжный replay из факта наличия gRPC не выводим: сохранение курсора и возобновление после сбоя требуют отдельной реализации.

Конечный поток и прикладной курсор

Серверный конечный поток завершается после последней подходящей записи. Бесконечная подписка — другой сценарий: она держит соединение открытым и ждёт новые измерения. В нашем примере B получает ограниченную выгрузку и завершение.

Прикладной курсор живёт в коде сервиса. gRPC не хранит значение after_seq между вызовами. Клиент сам решает, с какого значения продолжить, и передаёт его в следующем запросе.

В документации gRPC на grpc.io описаны четыре вида методов: unary, server streaming, client streaming и bidirectional streaming. Server streaming подходит для конечной выборки: клиент читает из потока, пока сообщения не закончатся.

Другие HTTP API тоже могут передавать потоки. Streaming — не исключительная возможность gRPC, а общий приём, реализуемый разными транспортами.

Эволюция контракта и reserved

При изменении Sample удаляем temp_milli_c=3 и в следующем варианте того же сообщения резервируем номер 3 и имя temp_milli_c.

PROTO4 строки
reserved 3;
reserved "temp_milli_c";
string sensor_id = 1;
uint64 seq = 2;

Пример относится только к изменяемому телу и хранится отдельно от рабочего файла. Номер 3 новому полю не отдаём. seq=2 остаётся идентификатором порядка.

В руководстве proto3 на protobuf.dev указано: номера полей уникальны внутри сообщения, диапазон 19000–19999 зарезервирован реализацией, а удалённый номер добавляют в список reserved, чтобы будущие разработчики не переиспользовали его. Там же отмечено, что резервирование имени защищает JSON- и TextFormat-кодировки.

Проверка: после правки protoc должен принять файл, а seq=2 сохранить роль идентификатора порядка. Компиляцию в этом примере не заявляем.

Нативный gRPC, gRPC-Web и другие HTTP API

Нативный браузерный API отличается от gRPC-Web. В README проекта gRPC-Web на GitHub описано, что клиенты gRPC-Web подключаются к gRPC-сервисам через специальный прокси, по умолчанию Envoy. Там же указано, что gRPC-Web поддерживает unary и server-side streaming, причём server streaming работает в режиме grpcwebtext; client-side и bidirectional streaming не поддерживаются.

Браузер не объявляем неспособным работать с gRPC-Web. Для A уже задан обычный GET, и это основание первого выбора.

Выбор A соответствует обычному GET/JSON и своей политике кэша. Выбор B — полному контракту, поддерживаемому HTTP/2 gRPC и конечному потоку. Обязательный L7 не требуется, а производительность по имени протокола не обещаем. При нескольких backend отдельно сверяем одинаковый результат по этому набору на каждом backend; общий cursor store gRPC не предоставляет.

Как читать проверку и что делать при расхождении

Проверку удобно вести от ожидаемого результата к причине расхождения. Сначала зафиксируйте, какой именно набор должен вернуться для пары границ, затем сравните с ним фактический ответ и только после этого ищите место, где выборка отклонилась от правила. Такой порядок отделяет ошибку контракта от ошибки данных: если типы и номера полей совпадают со схемой, а набор записей всё равно неверен, причина почти всегда в прикладном фильтре или в хранилище.

Для диапазона after_seq=41, through_seq=44 ожидается последовательность [42, 44]. Если в ответе появилась запись 43, фильтрация по sensor_id дала неверный результат. Проверьте условие совпадения датчика в коде сервиса. Если появилась запись 41, значит нижняя граница трактуется как нестрогая. Оба случая указывают на код сервиса.

Повторный запрос с after_seq=42 служит отдельной проверкой курсора. Ожидается [44]; пустой ответ здесь означает, что курсор сдвинулся дальше, чем нужно, либо верхняя граница была сужена. Запрос с after_seq=44 при той же верхней границе должен вернуть пустую завершённую выборку — это корректный результат.

При нескольких backend сверяйте один и тот же диапазон на каждом из них. При расхождении сравните наборы данных и реализацию фильтра на каждом backend; согласованность хранилища обеспечивает команда.

Если расхождение найдено, действие читателя — вернуться к правилу выборки, проверить порядок условий фильтра и только затем пересматривать контракт. Менять схему сообщений при верном наборе записей не требуется.

Частые вопросы

Чем gRPC отличается от REST в этом примере?
REST обслуживает браузерную панель одним GET и JSON. gRPC обслуживает внутренний обработчик потоком Protobuf-сообщений по контракту telemetry.proto. Различие лежит в контракте и клиентах.

Почему запись 43 не попала в выборку S-17?
У записи 43 sensor_id=S-18, поэтому фильтр по датчику её исключает, хотя seq=43 попадает в числовой интервал 41–44. Проверка границ и фильтра датчика выполняется приложением.

Что даёт повторный запрос с after_seq=42?
Повтор с after_seq=42 и through_seq=44 возвращает только запись 44. Запись 42 исключена строгой нижней границей. Это позволяет продолжить выгрузку с последнего полученного значения.

Зачем резервировать номер 3 при удалении temp_milli_c?
Резервирование номера и имени защищает от переиспользования: будущий разработчик не займёт номер 3 под другое поле. В руководстве proto3 на protobuf.dev это описано как обязательная практика при удалении поля.

Можно ли вызвать gRPC напрямую из браузера?
Нативный браузерный API отличается от gRPC-Web. gRPC-Web работает через прокси, по умолчанию Envoy, и поддерживает unary и server streaming в режиме grpcwebtext. Для панели в этом примере выбран обычный GET.

Читайте также

3 материала