Какие существуют типы RPC в gRPC и в чем разница между Unary и Server-streaming?

Ответ

В gRPC существует четыре основных типа удаленного вызова процедур (RPC), которые определяют, как клиент и сервер обмениваются сообщениями:

  1. Unary RPC (Унарный вызов)

    • Принцип: Классическая модель "запрос-ответ". Клиент отправляет один запрос и ждет один ответ от сервера.
    • Аналогия: Похож на обычный вызов функции или стандартный HTTP GET/POST запрос.
    • .proto синтаксис:
      rpc GetUser(UserRequest) returns (UserResponse);
    • Применение: Получение данных по ID, создание или обновление записи, любая простая операция.
  2. Server-streaming RPC (Серверная потоковая передача)

    • Принцип: Клиент отправляет один запрос, а сервер в ответ присылает поток (stream) сообщений. Клиент читает из потока, пока он не будет закрыт сервером.
    • Аналогия: Подписка на новостную ленту или получение видео по частям.
    • .proto синтаксис:
      rpc ListUsers(ListRequest) returns (stream UserResponse);
    • Применение: Передача большого объема данных (например, строк из базы данных), которые можно обрабатывать по частям; рассылка уведомлений или обновлений в реальном времени.
  3. Client-streaming RPC (Клиентская потоковая передача)

    • Принцип: Клиент отправляет на сервер поток сообщений. После того как клиент завершил отправку, сервер обрабатывает весь поток и возвращает один ответ.
    • Применение: Загрузка больших файлов, отправка телеметрии или логов.
  4. Bidirectional-streaming RPC (Двунаправленная потоковая передача)

    • Принцип: Клиент и сервер могут отправлять друг другу сообщения в виде потоков независимо друг от друга.
    • Применение: Создание интерактивных чатов, онлайн-игры, системы совместного редактирования документов.

Сравнительная таблица (Unary vs Server-streaming)

Характеристика Unary RPC Server-streaming RPC
Запросы клиента 1 1
Ответы сервера 1 Много (поток)
Направление потока Нет Сервер → Клиент
Основное применение Простые, атомарные операции Передача больших объемов данных, нотификации, подписки

Пример Server-streaming на Go (серверная часть):

// ListUsers отправляет поток пользователей в ответ на один запрос
func (s *Server) ListUsers(req *pb.ListRequest, stream pb.UserService_ListUsersServer) error {
    // Предположим, users - это срез с данными
    for _, user := range users {
        // Отправляем каждого пользователя как отдельное сообщение в потоке
        if err := stream.Send(&pb.UserResponse{User: user}); err != nil {
            return err // Ошибка при отправке, например, если клиент отключился
        }
    }
    return nil // Успешное завершение потока
}

Ответ 18+ 🔞

Так, слушай, разбираем эту вашу gRPC-хуйню. Четыре типа вызовов, как четыре сорта пива в баре — каждый для своего случая, а если перепутаешь, будет пиздец.

1. Unary RPC (Прямой вызов, как в лоб) Тут всё просто, как три копейки. Клиент пингует сервер одним сообщением и ждёт, пока тот не плюнет одним ответом обратно. Как в старом добром HTTP, только быстрее. Идеально, когда нужно просто спросить «как дела?» и получить «нормально».

rpc GetUser(UserRequest) returns (UserResponse);

2. Server-streaming RPC (Сервер — болтун) Клиент один раз пишет «привет», а сервер в ответ начинает нести хуйню непрерывным потоком, пока не закончится или клиент не скажет «хватит, заебал». Представь, что ты спросил у друга «как дела?», а он начал рассказывать про работу, тёщу и пробки — вот это оно.

rpc ListUsers(ListRequest) returns (stream UserResponse);

3. Client-streaming RPC (Клиент — зануда) Полная противоположность. Теперь клиент долбит сервер потоком данных, а сервер терпеливо слушает, кивает и в конце выдаёт один финальный вердикт. Как если бы ты час жаловался на жизнь психологу, а он под конец сказал бы «ну, держись».

4. Bidirectional-streaming RPC (Полный бардак) А это уже ёперный театр. Обе стороны могут нести ахинею одновременно и независимо. Как разговор двух пьяных философов в баре — оба говорят, никто не слушает, но вроде как диалог. Используется для чатов или игр, где все друг другу должны срочно сообщить какую-то хуйню.


Unary vs Server-streaming: кто на ком стоял?

Параметр Unary (Простой) Server-streaming (Болтливый)
Клиент стучит Один раз и ждёт Один раз и ждёт (но потом жалеет)
Сервер палит Один ответ и свободен Поток ответов, пока не кончатся аргументы
Куда течёт Никуда, статика Из сервера в клиента, как из ведра
Зачем это Для простых вопросов вроде «сколько время?» Чтобы завалить клиента данными или спамить уведомлениями

Пример Server-streaming на Go (смотри, не обосрись)

Вот как сервер превращается в болтуна:

// ListUsers — функция, где сервер начинает трепаться без остановки
func (s *Server) ListUsers(req *pb.ListRequest, stream pb.UserService_ListUsersServer) error {
    // Допустим, users — это куча пользователей, которых надо вывалить на клиента
    for _, user := range users {
        // Шлём каждого юзера как отдельное сообщение. По одному, без спешки.
        if err := stream.Send(&pb.UserResponse{User: user}); err != nil {
            return err // Если клиент сдох или отключился — закругляемся
        }
    }
    return nil // Всё, поток кончился, можно идти пить чай
}

Вот и вся магия. Главное — не перепутай типы, а то вместо одного ответа получишь поток охуенных подробностей, которые тебе нахуй не сдались.