Почему использование 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, автоматическую генерацию кода клиента и сервера и нативную поддержку стриминга.

Ответ 18+ 🔞

Ну, слушай, тут один умник задвинул, что Protobuf через HTTP/1.1 вообще не работает. Да ну нахуй, конечно работает! Как телега на квадратных колёсах — едет же, вроде, но все вокруг плачут от боли и неэффективности.

Вот смотри, в чём подвох, ёпта:

  1. Мультиплексирование? Не, не слышал. HTTP/1.1 — это как очередь в совковой столовой. Пока первый в очереди ковыряется с своим борщом, все остальные стоят и ждут, даже если им только хлеба кусок. Это убивает на корню всю идею стриминга, ради которой Protobuf и затачивали.
  2. Текстовый протокол для бинарных данных. Это как пытаться отправить по почте кусок говядины, завернув его в стихи Пушкина. Чтобы твои бинарные Protobuf-сообщения прошли через текстовый HTTP/1.1, их надо в Base64 превратить. Объём вырастает на треть, а процессор на сервере и клиенте начинает ебашить как проклятый, чтобы это всё закодировать-раскодировать.
  3. Заголовки — отдельная песня. Они не сжимаются и летят с каждым чихом. А если твоё Protobuf-сообщение размером с напёрсток, а заголовки — как одеяло, то это просто пиздец какой-то перерасход трафика.

Технически-то прикрутить можно, да. Вот, смотри, как это выглядит, если ты совсем еблан:

// Клиентская сторона: пакуем и шлём
reqBody, _ := proto.Marshal(&pb.MyRequest{Data: "hello"})
req, _ := http.NewRequest("POST", url, bytes.NewReader(reqBody))
// Кричим серверу: "Держи, мудила, бинарь!"
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 поверх HTTP/2. И это, блядь, как пересесть с той телеги на квадратных колёсах на гиперлуп.

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

Короче, использовать Protobuf с HTTP/1.1 — это как пытаться приготовить стейк на паяльнике. Сделать можно, но зачем, если есть нормальная плита?