1. 1
  2. 2
  3. 3
  4. 4
  5. 5

# теория · шаг 1 из 5

Что HTTP/2 меняет на самом деле

Коротко

HTTP/2 - это мультиплексирование в одном соединении плюс сжатие заголовков. Он снимает лимит на шесть соединений к домену.

  • Выигрыш появляется не всегда: на чистой сети замер даёт 814 мс против 821 у шести соединений HTTP/1.1 - то есть поровну.
  • Зато на сети с потерями шесть соединений начинают проигрывать: одна потеря тормозит только одно из них.
  • Приёмы времён HTTP/1.1 - шардинг, спрайты, склейка бандлов - после включения мешают.
  • Чего он не чинит: свой собственный head-of-line blocking на уровне TCP. Замер показывает его прямо.

Если знаешь, что мультиплексирование не отменяет очередь в TCP, - листай до «Теперь сам».

Сначала ответь сам

Страница тянет 30 файлов. Включаем HTTP/2 и ничего больше не трогаем. Насколько быстрее станет?

Стенд, задержка 100 мс, семь прогонов, медиана:

↳  вывод30 файлов по 20 КБ, сеть без потерь
HTTP/1.1, шесть соединений   821 мс
HTTP/2,   одно соединение    814 мс

Поровну. Мультиплексирование не ускоряет само по себе: шести соединений хватает, чтобы вытянуть тридцать файлов за то же время. Выигрыш HTTP/2 в другом - и проявится он на плохой сети, а не на хорошей.

На что это похоже

Шесть кассиров против одного конвейера. Пока покупателей тридцать, а очередь движется ровно, разницы почти нет: шесть касс справляются.

Разница появляется, когда у кого-то заминка. У шести касс встанет одна из шести - остальные пять работают. У конвейера заминка в начале останавливает всё, что за ней.

Так вот HTTP/2 - это конвейер. И именно поэтому дальше в главе речь пойдёт о том, чего он не чинит.

Механизм: фреймы вместо очереди

HTTP/1.1: шесть соединений, в каждом по очереди запрос 1 запрос 7 запрос 13 остальные 24 ждут своей очереди в шести трубах HTTP/2: одно соединение, фреймы вперемешку 1 7 2 13 7 1 30 2 13 30 каждый фрейм помечен номером потока, на другом конце их разбирают ниже всё равно один TCP - и это то, что ломается на потерях
Внутри протокола потоки независимы. Ниже протокола - один поток байтов.

Второе, что даёт HTTP/2, - сжатие заголовков. В HTTP/1.1 каждый из тридцати запросов тащит одинаковые User-Agent, Accept, Cookie: легко килобайт на запрос. HTTP/2 передаёт повторы ссылкой на таблицу, которую обе стороны ведут синхронно.

Правило

Включив HTTP/2, разбери костыли, придуманные ради обхода лимита в шесть соединений, - теперь они работают против тебя:

Браузер держит к одному домену не больше шести одновременных соединений - отсюда и костыли, придуманные, чтобы обойти этот предел:

  • доменный шардинг (img1.shop.ru, img2.shop.ru) - каждый лишний домен это отдельное TLS-рукопожатие и отдельная таблица заголовков;
  • спрайты (много мелких картинок, собранных в одну) и склейка бандлов (весь JavaScript в один файл) - правка одной строки инвалидирует в кеше весь склеенный файл;
  • инлайн base64-картинок в CSS - раздувает файл, который иначе лежал бы в кеше отдельно, и плохо сжимается.

Порядок работ ровно такой: включил протокол, разобрал костыли, измерил. Если включить HTTP/2 поверх сборки под HTTP/1.1, выигрыш будет скромным - протокол честно ускорит то, что ему дали, а дали ему один двухмегабайтный бандл.

Разбор: где HTTP/2 начинает выигрывать

Тот же стенд, но с потерями на пути запросов:

↳  вывод30 файлов, задержка 100 мс, потери 5%
HTTP/1.1, шесть соединений   1146 мс
HTTP/2,   одно соединение     813 мс

Вот теперь разница есть, и взялась она не из мультиплексирования как такового. У шести соединений каждое ведёт свой контроль перегрузки (механизм TCP, который сам замедляет отправку, нащупывая скорость канала) и своё восстановление после потерь; потерянный пакет тормозит одно из шести, но соединений всего шесть, а файлов тридцать - и очередь наверстать не успевает.

Разбор: чего HTTP/2 не чинит

Теперь потери переносим на сторону сервера - туда, где идут сами данные. Семь прогонов, отсортированные времена:

протокол прогоны, мс
HTTP/1.1, шесть соединений 1017 1120 1214 1237 1341 1420 1767
HTTP/2, одно соединение 1313 1413 1714 2021 5221 5268 5486

Медиана HTTP/2 хуже почти вдвое, а хвост уходит за пять секунд. Причина - TCP: он отдаёт данные приложению строго по порядку, и пока потерянный сегмент не доехал повторно, ядро держит всё, что пришло после него. Встают все тридцать потоков разом, включая те, чьи данные уже лежат в буфере.

Это head-of-line blocking на уровне транспорта. Внутри TCP его не починить - очерёдность часть его контракта. Отсюда и вырос QUICРазбирается в разделе 10, глава «HTTP/3 и QUIC»: Почему понадобился QUIC, следующая глава.

Что ломается без этого

Server push умер. Идея «отправим файлы до того, как их попросят» не пережила столкновение с кешем браузера: сервер не знает, что у клиента уже есть, и push регулярно слал байты в никуда. Chrome выключил поддержку в 2022 году, а nginx отвечает на директиву прямо:

↳  выводnginx -t
[warn] the "http2_push" directive is obsolete, ignored

Замена - 103 Early Hints: бэкенд, зная, что ответ будет долгим, заранее отдаёт промежуточный ответ со ссылками, а браузер сам решает, что предзагрузить. nginx проксирует такие ответы с версии 1.29.0 (в стабильной ветке с 1.30.0):

/etc/nginx/conf.d/shop.local.confhttp server location /
early_hints on;

Приоритеты потоков практически не используются. Сложная схема зависимостей из первой редакции протокола оказалась неработоспособной и была выкинута; пришедшая ей на смену поддержана далеко не везде. Считай, что порядок отдачи решает браузер.

Зачем это в работе

Из замеров следует практический вывод, который редко пишут в статьях: включение HTTP/2 само по себе почти ничего не даёт на хорошей сети. Оно даёт две вещи: устойчивость к плохой сети до определённого предела и возможность разобрать костыли.

Поэтому оценивать эффект надо не «стало ли быстрее у меня в офисе», а по метрикам с реальных клиентов - и отдельно смотреть на мобильных, где потери есть всегда.

Теперь сам

Сайт перевели на HTTP/2, разобрали шардинг и разбили бандл на двадцать файлов. Метрики у десктопных клиентов не изменились, у мобильных стало хуже. Что произошло?

Ровно то, что на графике замера: у мобильных потери, и одно соединение с двадцатью потоками страдает от head-of-line blocking сильнее, чем прежние шесть. Разбиение бандла усилило эффект - файлов стало больше, и каждая потеря задерживает их все. Лечится это не откатом, а HTTP/3 рядом с HTTP/2: следующая глава.

Главное

HTTP/2 - мультиплексирование в одном соединении плюс сжатие заголовков. На чистой сети выигрыша против шести соединений HTTP/1.1 почти нет (замер: 814 против 821 мс), на сети с потерями он появляется. Костыли времён HTTP/1.1 - шардинг, спрайты, склейка - после включения мешают и разбираются следом. Чего протокол не умеет - победить очерёдность самого TCP: при потерях на стороне данных медиана HTTP/2 оказалась вдвое хуже шести соединений, а хвост ушёл за пять секунд. Server push мёртв, вместо него 103 Early Hints.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.