Транспорт бэкенда

HTTP/2 и HTTP/3: мультиплексирование и QUIC

2012 год. SPDY-эксперимент Google показывает: 40% латентности веб-страниц - это не сервер и не сеть, это сам протокол. HTTP/1.1 работает как касса с одной полосой при очереди из 80 ресурсов. Следующие 10 лет ушли на то, чтобы превратить этот диагноз в два новых стандарта.

  • **gRPC в Kubernetes**: все watch-стримы (`kubectl get pods -w`) и bidirectional RPC работают через HTTP/2 streams - мультиплексирование без открытия отдельных TCP-соединений
  • **YouTube на мобильном**: connection migration QUIC позволяет не прерывать воспроизведение при переключении с WiFi на LTE - соединение переживает смену IP
  • **Cloudflare CDN**: 30% трафика Cloudflare идёт через HTTP/3, экономия 12-37% на latency в зависимости от качества сети
  • **Envoy/Nginx upstream**: HTTP/2 connection pooling к upstream-сервисам - один persistent TCP-коннект вместо flood of short-lived connections

HTTP/2: мультиплексирование и потоки

2012 год. Команда Google публикует результаты эксперимента со SPDY. Вывод ошеломляет: 40% латентности обычной веб-страницы - не медленный сервер, не длинная сеть. Это сам протокол. HTTP/1.1 дожидается ответа на каждый запрос перед следующим - касса с одной полосой при очереди из 80 ресурсов. SPDY становится основой HTTP/2, принятого в 2015.

HTTP/1.1 страдает **head-of-line blocking** на уровне протокола: один TCP-коннект = одна активная пара запрос/ответ. Браузер обходит это через 6-8 параллельных TCP-соединений на домен - дорогое решение с множеством handshake'ов. Domain sharding (cdn1.example.com, cdn2.example.com) - костыль, который HTTP/2 сделал антипаттерном.

HTTP/2 меняет модель: одно TCP-соединение несёт **множество параллельных потоков (streams)**. Каждый поток - независимая двунаправленная последовательность фреймов с уникальным stream ID. Запрос к `/api/users` (stream 1) и `/api/posts` (stream 3) летят одновременно - без блокировки друг друга.

Единица обмена в HTTP/2 - **фрейм (frame)**, бинарный, а не текстовый. Типы: `DATA` (тело), `HEADERS` (заголовки), `PRIORITY`, `RST_STREAM` (отмена), `SETTINGS`, `PING`, `GOAWAY`, `WINDOW_UPDATE`. Каждый фрейм содержит длину, тип, флаги и 31-битный stream ID.

**Stream приоритизация** позволяет сигнализировать серверу: CSS и JS критичны для рендера, изображения - нет. Nginx, h2o и Envoy соблюдают веса приоритетов. На практике это значит, что gRPC bidirectional streaming (health checks Kubernetes) и обычные HTTP-ответы сосуществуют на одном соединении без голодания.

HTTP/2 решает HOL blocking на уровне **приложения**, но не TCP. Один потерянный TCP-пакет останавливает все streams - это HOL blocking транспортного уровня. HTTP/3 устраняет и его.

Почему HTTP/1.1 требует несколько TCP-соединений для параллельных запросов?

HPACK и Server Push

HTTP/1.1 отправляет заголовки текстом при каждом запросе. `User-Agent`, `Accept`, `Cookie`, `Authorization` - одинаковые строки снова и снова. Сотни байт на каждый запрос. На мобильных сетях с высоким RTT это заметный overhead при загрузке страницы с 80+ ресурсами.

**HPACK** сжимает заголовки HTTP/2 через две таблицы: статическую (61 предопределённая пара типа `:method GET`) и динамическую (пополняется в ходе соединения). Повторный заголовок передаётся как 1-байтный индекс вместо полной строки. Инженеры Google измерили: HPACK сокращает объём заголовков на 85-88% для типичных веб-запросов.

HPACK: первый vs повторный запрос

Первый запрос: полная сериализация всех заголовков (~400 байт). Второй запрос к тому же хосту: только изменившиеся заголовки + 1-байтные индексы для остальных (~50 байт). Экономия: 87.5% трафика заголовков на странице с 80+ ресурсами.

**Server Push** - превентивная отправка ресурсов. Сервер видит запрос к `index.html` и без дополнительных запросов клиента отправляет `styles.css` и `app.js` в отдельных streams. Теоретически элегантно. Практически - браузеры научились игнорировать push-фреймы для уже закешированных ресурсов, Chrome отключил Server Push по умолчанию в 2022 из-за edge cases с кешированием.

Server Push - одна из наиболее переоценённых фич HTTP/2. Правильная реализация требует точного знания кеш-состояния клиента. Большинство команд отключают его и используют `<link rel=preload>` вместо этого.

HPACK и мультиплексирование активно используются в современных системах: Kubernetes API-сервер отдаёт watch-стримы (`kubectl get pods -w`) по HTTP/2 streams, Envoy и Nginx реализуют HTTP/2 connection pooling в upstream-запросах. Весь gRPC трафик идёт через эти механизмы.

Как HPACK добивается сжатия HTTP/2 заголовков?

HTTP/3 и QUIC: UDP как новый TCP

HTTP/2 решил HOL blocking на уровне приложения - но оставил его на уровне TCP. Один потерянный пакет блокирует все 100 параллельных HTTP/2 streams, пока TCP не восстановит его. На мобильных сетях с packet loss 1-2% это чувствительно. Google снова взялся за инструменты.

**QUIC** (RFC 9000, 2021) - транспортный протокол поверх UDP. Реализует всё что делает TCP: надёжная доставка, управление перегрузкой, flow control - но как пользовательский код, а не в ядре ОС. Ключевое следствие: новые алгоритмы (BBR v3, CUBIC++) деплоятся как обновление приложения без ожидания обновления ядра на миллиардах устройств.

**0-RTT handshake** - ключевое преимущество для повторных соединений. TLS 1.3 поверх TCP требует 1-RTT handshake. QUIC с 0-RTT отправляет данные первым пакетом при повторном соединении - клиент использует ключи от предыдущей сессии. Измерение Google: медианная латентность открытия страницы снижается на 8% только за счёт 0-RTT.

**Connection migration** меняет мобильный UX. TCP-соединение привязано к четырёхтуплу (src IP, dst IP, src port, dst port). Переключение с WiFi на LTE = новый IP = разрыв всех соединений. QUIC использует **Connection ID** вместо четырёхтупла. При смене IP клиент отправляет пакет с тем же Connection ID - сервер продолжает без переподключения. YouTube на телефоне не буферизуется при выходе из зоны WiFi именно поэтому.

HOL blocking транспортного уровня устранён: в QUIC каждый stream независим. Потеря пакета stream 5 не влияет на streams 1, 3, 7. Cloudflare измерила: на сетях с 2% packet loss HTTP/3 быстрее HTTP/2 на 12%, на 10% потерь - на 37%.

По данным W3Techs (2024), HTTP/3 поддерживает около 30% веб-сайтов. Cloudflare, Google, Meta включили его по умолчанию. nginx поддерживает через quiche. Есть нюанс: корпоративные firewall'ы часто блокируют UDP - QUIC предусматривает автоматический TCP fallback, если QUIC не проходит за 300ms.

HTTP/3 ненадёжен, потому что UDP не гарантирует доставку

QUIC реализует надёжность поверх UDP самостоятельно

UDP - просто низкоуровневый транспорт без встроенной логики. QUIC добавляет ACK, повторную передачу, управление перегрузкой и flow control - всё как в TCP, но без ограничений ядра ОС.

Почему HTTP/3 построен поверх UDP, а не поверх TCP?

Связанные концепции

HTTP/2 и HTTP/3 - транспортная основа для gRPC, WebSocket и современных CDN.

  • gRPC и HTTP/2 — gRPC использует HTTP/2 streams как транспортный уровень для bidirectional streaming
  • TLS 1.3 handshake — 0-RTT в QUIC строится поверх механизма session resumption TLS 1.3
  • WebSocket над HTTP/2 — RFC 8441 расширяет WebSocket для работы через HTTP/2 streams вместо upgrade-соединений

Итог

  • HTTP/2: одно TCP-соединение, множество параллельных streams с приоритетами - ликвидирует HOL blocking на уровне приложения
  • HPACK сжимает повторяющиеся заголовки до 1-байтных индексов - экономия 85-88% трафика заголовков
  • Server Push элегантен в теории, проблематичен на практике - Chrome отключил по умолчанию в 2022
  • HTTP/3 поверх QUIC (UDP): независимые streams без TCP HOL, 0-RTT handshake, connection migration при смене IP
  • QUIC реализует TCP-надёжность на уровне приложения - новые алгоритмы без обновления ядра ОС

Вопросы для размышления

  • В чём принципиальное отличие HOL blocking в HTTP/2 и HTTP/3? На каком уровне каждый из них решает проблему?
  • Почему Server Push в HTTP/2 оказался менее полезным на практике, чем ожидалось при проектировании?
  • Как Connection migration в QUIC меняет UX для мобильных приложений с постоянными соединениями?

Связанные уроки

  • bt-05-http-fundamentals — HTTP/1.1 head-of-line - отправная точка проблемы
  • bt-04-dns-tls — TLS 1.3 и 0-RTT handshake прямо в QUIC
  • bt-08-websocket — WebSocket над HTTP/2 - полнодуплекс через RFC 8441
  • bt-09-grpc — gRPC использует HTTP/2 streams как транспорт
  • net-24-http2-http3 — Тот же материал с сетевой перспективы (OSI L7)
HTTP/2 и HTTP/3: мультиплексирование и QUIC

0

1

Войти