Почему использование Protobuf поверх HTTP/1.1 считается неэффективным?

Ответ

Утверждение, что Protobuf не может работать поверх HTTP/1.1, неверно. Он может, но такое сочетание неэффективно и сводит на нет многие преимущества как Protobuf, так и современных сетевых протоколов.

Основные проблемы:

  1. Отсутствие мультиплексирования. HTTP/1.1 обрабатывает запросы строго последовательно (проблема Head-of-line blocking). Это мешает эффективной потоковой передаче данных (стримингу), для которой хорошо подходит Protobuf.

  2. Текстовая природа протокола. HTTP/1.1 — текстовый протокол. Для передачи бинарных данных Protobuf их нужно кодировать (например, в Base64), что увеличивает размер запроса на ~33% и требует дополнительных затрат CPU на кодирование/декодирование.

  3. Большие заголовки. Заголовки в HTTP/1.1 не сжимаются и отправляются с каждым запросом, что создает значительные накладные расходы, особенно для маленьких и частых сообщений Protobuf.

Пример такой реализации:

// Клиент отправляет Protobuf в теле POST-запроса
reqBody, _ := proto.Marshal(&pb.MyRequest{Data: "hello"})
req, _ := http.NewRequest("POST", url, bytes.NewReader(reqBody))
// Указываем Content-Type, чтобы сервер знал, как парсить тело
req.Header.Set("Content-Type", "application/x-protobuf")
client.Do(req)

// Сервер читает тело и десериализует его
body, _ := io.ReadAll(r.Body)
var req pb.MyRequest
proto.Unmarshal(body, &req)

Решение: gRPC и HTTP/2

Именно для решения этих проблем был создан gRPC, который использует Protobuf поверх HTTP/2.

  • HTTP/2 предлагает бинарные фреймы, мультиплексирование и сжатие заголовков, что идеально подходит для передачи бинарных сообщений Protobuf.
  • gRPC добавляет к этому строгую типизацию API, автоматическую генерацию кода клиента и сервера и нативную поддержку стриминга.