Сети и протоколы
HTTP/2: потоки, фреймы и HPACK
Браузер загружает страницу с 50 подресурсами: открывает 6 TCP-соединений к одному origin, заполняет их запросами, ставит остальные в очередь. Каждое соединение платит свой хендшейк. HTTP/2 сворачивает всё это в одно соединение с 50 параллельными потоками — но у исправления есть собственный режим отказа.
HTTP/1.1: pipelining и шесть соединений
HTTP/1.1 (RFC 9112) отправляет один запрос на TCP-соединение и ждёт полного ответа перед следующим. Для параллелизации браузеры открывают 6–8 соединений к одному origin. При загрузке 10 JS-файлов открывается 6 соединений, по одному запросу на каждое, остальные 4 в очереди. Когда ответ приходит — соединение освобождается и берёт следующий файл.
Pipelining (RFC 7230) позволял отправлять несколько запросов не дожидаясь ответов — но редко работал: прокси ломали его, и если запрос #1 ждал медленного сервера, запрос #2 блокировался за ним на том же соединении — HOL-блокировка на стороне ответа. Почти все браузеры отключили pipelining по умолчанию.
HTTP/2: слой фреймирования
HTTP/2 (RFC 9113) отказывается от модели одного запроса на соединение. Одно TCP-соединение несёт много параллельных потоков — каждый поток независимая пара запрос-ответ.
Фреймы — единица передачи:
- HEADERS — открывает поток, несёт метаданные запроса или ответа. Поток создаётся когда клиент отправляет HEADERS с новым stream ID.
- DATA — несёт тело потока.
- RST_STREAM — прерывает поток без закрытия соединения.
- SETTINGS — согласует параметры соединения (максимум потоков, начальный размер окна, размер таблицы заголовков).
- WINDOW_UPDATE — управление потоком: сообщает отправителю сколько байт получатель готов буферизировать.
Сервер может перемежать ответы разных потоков: 100 байт ответа потока #1, затем 100 байт потока #3, затем снова #1 — всё на одном TCP. Порядок внутри одного потока сохраняется; порядок между потоками — на усмотрение сервера.
- Размер заголовка фрейма HTTP/2
- 9 байт (тип, флаги, длина, stream ID)
- Пространство stream ID
- 31 бит (нечётные — клиент, чётные — server push)
- Окно управления потоком по умолчанию
- 65 535 байт на поток
- Типичный сервер: макс. параллельных потоков
- 100 (рекомендация RFC 9113)
- SETTINGS фрейм согласуется
- при старте соединения, до первого запроса
- Стоимость RST_STREAM
- один фрейм — без разрыва TCP
HPACK: сжатие заголовков
HTTP/1.1 отправляет полные заголовки в каждом запросе — обычно 400–800 байт даже если меняется только путь. HTTP/2 использует HPACK (RFC 7541):
- Статическая таблица — 61 предопределённая пара (имя, значение):
":method": "GET",":status": "200","content-type": "text/html"и т.д. Один байт индекса заменяет полный заголовок. - Динамическая таблица — записи добавляются per-connection по мере появления новых заголовков. Если первый запрос отправляет
user-agent: Mozilla/5.0, второй может ссылаться на него 1–2 байтами индекса, передавая только дельту.
Результат: после первого запроса к домену, последующие запросы уменьшают накладные расходы на заголовки с ~400 байт до ~20–50 байт. На странице с 50 подресурсами это экономит ~18 КБ заголовочных данных.
Ограничение: динамическая таблица упорядочена и общая для всех потоков. Если HEADERS-фрейм, добавляющий индекс 60, потерян, последующие HEADERS не могут декодировать ссылки на индекс 60 до повторной доставки. На TCP это незаметно. На QUIC это сломает независимость потоков — поэтому HTTP/3 заменил HPACK на QPACK.
Управление потоком HTTP/2
Каждое HTTP/2-соединение имеет connection-level и stream-level управление потоком через WINDOW_UPDATE — получатель сообщает отправителю сколько байт готов принять. По умолчанию: 65 535 байт на поток, иногда повышается до 16 МиБ на высокопропускных API.
SETTINGS_MAX_CONCURRENT_STREAMS ограничивает число параллельных потоков (обычно 100). При достижении лимита клиент ставит запросы в очередь — частично теряя выгоду мультиплексирования.
Трассировка загрузки страницы браузером по HTTP/2 с нуля.
Server Push: функция, которая провалилась
HTTP/2 ввёл Server Push — сервер мог отправлять ресурсы которые браузер ещё не запрашивал. Получив GET /index.html, сервер мог послать PUSH_PROMISE для /style.css, предполагая что он понадобится.
В теории — экономия одного round-trip. На практике — три причины провала:
- Сервер не знает что браузер уже закешировал. Слепо пушит /style.css даже если у браузера свежая копия, тратя трафик.
- Push помогает только при первой навигации — при тёплых загрузках браузер агрессивно кеширует и пушнутые ресурсы приходят незваными.
- Middleboxes и CDN не обрабатывали push корректно, приводя к дублированным передачам.
Chrome убрал Server Push в 2022. RFC 9113 депрекейтит PUSH_PROMISE. Современная замена: 103 Early Hints + <link rel="preload"> — сервер отправляет 103 информационный ответ с критическими подресурсами, браузер сам решает что загружать (проверяя кеш первым).
Почему Server Push фактически мёртв в 2026?
HTTP/2 мультиплексирование устраняет HOL-блокировку — верно или нет?
Расставьте события при загрузке https://example.com по HTTP/2:
- 1 DNS-резолвинг example.com
- 2 TCP-хендшейк
- 3 TLS 1.3 хендшейк (ALPN выбирает h2)
- 4 Обмен SETTINGS-фреймами HTTP/2
- 5 HEADERS-фрейм с запросом страницы
- 6 DATA-фреймы с HTML-телом
- 7 50 HEADERS-фреймов для подресурсов
- 8 Перемежающиеся DATA-фреймы всех 50 потоков
Диагностика: почему HTTP/2 API-сервис имеет большую задержку на мобильных сетях, чем HTTP/1.1?
- 01Объясните почему HTTP/2 мультиплексирование НЕ решает полностью HOL-блокировку.
- 02Что такое HPACK и каково его главное ограничение?
- 03Что заменило Server Push HTTP/2 и почему это работает лучше?
HTTP/2 ввёл мультиплексирование потоков на одном TCP-соединении, заменив шесть-соединений HTTP/1.1. Фреймы (HEADERS, DATA, RST_STREAM, SETTINGS, WINDOW_UPDATE) — единица передачи; потоки свободно чередуются на одном соединении. HPACK сокращает размер заголовков на 80–90% через статическую и per-connection динамическую таблицу. Управление потоком через WINDOW_UPDATE предотвращает переполнение буферов. Server Push удалён из Chrome в 2022 — сервер не знает что браузер закешировал; 103 Early Hints заменил его. Оставшаяся открытая проблема: один потерянный TCP-пакет тормозит каждый поток — HOL-блокировка TCP, которую HTTP/3 решает с QUIC.
встречается в5
- MVCC: как Postgres раздаёт согласованные снимкиjunior
- Акт 7 в глубину: шардинг, co-location и семиуровневый каскад трейдоффовmiddle
- Наблюдаемость, антипаттерны и производственный триажsenior
- Raft в реальном мире: partition, медленный диск и клиентская маршрутизацияmiddle
- Raft в production: membership change, Multi-Raft и observabilitysenior