← Назад к вопросам
В чем разница между server-streaming и unary в gRPC?
2.0 Middle🔥 241 комментариев
#Сетевые протоколы и API
Комментарии (1)
🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Разница между Unary и Server-Streaming в gRPC
Основные концепции
gRPC использует различные типы RPC для взаимодействия между клиентом и сервером. Два наиболее распространённых типа — это Unary RPC (односторонний) и Server-Streaming RPC (серверный поток).
Unary RPC (Односторонний)
Это классический подход "запрос-ответ", аналогичный традиционным HTTP REST API:
- Клиент отправляет один запрос
- Сервер возвращает один ответ
- Взаимодействие завершается после получения ответа
// Пример Unary RPC в протокольном файле .proto
service UserService {
rpc GetUser(UserRequest) returns (UserResponse);
}
// Go реализация метода
func (s *Server) GetUser(ctx context.Context, req *pb.UserRequest) (*pb.UserResponse, error) {
// Обработка одного запроса
user := s.findUser(req.UserId)
// Возврат одного ответа
return &pb.UserResponse{
Id: user.Id,
Name: user.Name,
Email: user.Email,
}, nil
}
Server-Streaming RPC (Серверный поток)
В этом подходе:
- Клиент отправляет один запрос
- Сервер возвращает поток (sequence) ответов
- Сервер продолжает отправлять данные до завершения потока
// Пример Server-Streaming RPC в .proto
service LogService {
rpc StreamLogs(LogRequest) returns (stream LogMessage);
}
// Go реализация метода
func (s *Server) StreamLogs(req *pb.LogRequest, stream pb.LogService_StreamLogsServer) error {
// Получение логов из источника
logs := s.logSource.GetLogs(req.Filter)
// Отправка каждого лога как отдельного сообщения в потоке
for _, log := range logs {
// Отправка через поток
if err := stream.Send(&pb.LogMessage{
Timestamp: log.Timestamp,
Content: log.Content,
Level: log.Level,
}); err != nil {
return err
}
// Можно добавить задержку для контроля потока
time.Sleep(100 * time.Millisecond)
}
// Завершение потока (return nil)
return nil
}
Ключевые различия
Структура взаимодействия
| Характеристика | Unary RPC | Server-Streaming RPC |
|---|---|---|
| Количество запросов | 1 | 1 |
| Количество ответов | 1 | Множество (поток) |
| Продолжительность | Кратковременное | Длительное (до завершения потока) |
| Состояние соединения | Закрывается после ответа | Остается открытым |
Использование в Go
Клиентская сторона для Unary
// Обычный Unary вызов
resp, err := client.GetUser(ctx, &pb.UserRequest{UserId: "123"})
if err != nil {
log.Fatal(err)
}
fmt.Printf("User: %v\n", resp.Name)
Клиентская сторона для Server-Streaming
// Server-Streaming вызов
stream, err := client.StreamLogs(ctx, &pb.LogRequest{Filter: "error"})
if err != nil {
log.Fatal(err)
}
// Обработка потока ответов
for {
msg, err := stream.Recv()
if err == io.EOF {
// Поток завершен
break
}
if err != nil {
log.Fatal(err)
}
fmt.Printf("Log: %s - %s\n", msg.Timestamp, msg.Content)
}
Преимущества каждого подхода
Unary RPC:
- Простота реализации и использования
- Низкие накладные расходы для коротких операций
- Прямой возврат результата без управления потоком
- Идеально для операций типа CRUD: создание, чтение, обновление, удаление
Server-Streaming RPC:
- Эффективная передача больших данных без разбиения на множественные запросы
- Реальная потоковая передача для живых данных (логи, мониторинг, события)
- Снижение нагрузки на клиент — клиент обрабатывает данные по мере поступления
- Подходит для: потоковой передачи файлов, живых данных, длительных процессов
Сравнение производительности
Latency (Время ответа)
- Unary: Клиент получает ответ сразу после обработки сервером
- Server-Streaming: Первый элемент потока может быть получен быстро, но полный поток требует времени
Memory Usage (Использование памяти)
- Unary: Сервер формирует полный ответ перед отправкой
- Server-Streaming: Сервер может отправлять данные по мере их готовности, снижая пиковое использование памяти
Network Efficiency (Эффективность сети)
- Unary: Множественные вызовы могут создать overhead при передаче связанных данных
- Server-Streaming: Одное соединение используется для передачи последовательности данных, что может быть более эффективно
Практические примеры применения
Когда использовать Unary:
- Авторизация пользователя (один запрос → один ответ с токеном)
- Получение конфигурации системы
- Вычисление результата (калькуляция, преобразование данных)
- Транзакции баз данных (создание, обновление записи)
// Unary для аутентификации
rpc Authenticate(AuthRequest) returns (AuthResponse);
// Использование в middleware
func authenticate(ctx context.Context, req *pb.AuthRequest) (*pb.AuthResponse, error) {
token, err := authService.GenerateToken(req.Username, req.Password)
return &pb.AuthResponse{Token: token, ExpiresIn: 3600}, err
}
Когда использовать Server-Streaming:
- Передача логов системы в реальном времени
- Мониторинг метрик (CPU, память, сеть)
- Потоковая передача больших файлов (видео, базы данных)
- Подписка на события (уведомления, изменения состояния)
// Server-Streaming для мониторинга
rpc MonitorSystem(MonitorRequest) returns (stream MetricData);
// Реализация мониторинга
func monitorSystem(req *pb.MonitorRequest, stream pb.MonitoringService_MonitorSystemServer) error {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for i := 0; i < req.DurationSeconds; i++ {
<-ticker.C
metrics := collectSystemMetrics()
if err := stream.Send(metrics); err != nil {
return err
}
}
return nil
}
Итоговые рекомендации
Выбор между Unary и Server-Streaming зависит от:
- Характера данных — единичный объект vs последовательность
- Требований к времени — мгновенный ответ vs продолжительная передача
- Эффективности ресурсов — память и сетевые затраты
- Семантики операции — действие vs наблюдение
В Go разработке важно правильно выбирать тип RPC, учитывая эти факторы, чтобы обеспечить оптимальную производительность и удобство использования вашего gRPC API.