Хабрахабр

HTTP/3: разрушение основ и дивный новый мир

Вот уже больше 20 лет мы смотрим веб-странички по протоколу HTTP. Большинство пользователей вообще не задумывается о том, что это такое и как оно работает. Другие знают, что где-то под HTTP есть TLS, а под ним TCP, под которым IP и так далее. А третьи – еретики считают, что TCP – это прошлый век, им хочется чего-то более быстрого, надёжного и защищённого. Но в своих попытках изобрести новый идеальный протокол они вернулись к технологиям 80-х годов и пытаются построить на них свой дивный новый мир.

Немного истории: HTTP/1.1

В 1997 году протокол обмена текстовой информацией HTTP версии 1.1 обрёл свой RFC. На тот момент протокол использовался браузерами уже несколько лет, а новый стандарт продержался ещё пятнадцать. Протокол работал только по принципу запрос-ответ и предназначался, главным образом, для передачи текстовой информации.

Работа TCP основана на установлении и поддержании надежного соединения между конечными точками и разбиении трафика на сегменты. HTTP был спроектирован для работы поверх протокола TCP, гарантирующего надежную доставку пакетов до адресата. Если вдруг какой-то из сегментов не придёт или придёт с неверной контрольной суммой, то передача остановится, пока не будет восстановлен потерянный сегмент. Сегменты имеют свой последовательный номер и контрольную сумму.

0 TCP-соединение закрывалось после каждого запроса. В HTTP/1. установление TCP-соединения (3-Way-Handshake) это небыстрый процесс. Это было крайне расточительно, т.к. 1 представили механизм keep-alive, который позволяет переиспользовать одно соединение для нескольких запросов. В HTTP/1. 1 допускается открытие нескольких TCP-соединений к одному хосту. Однако поскольку оно может легко стать бутылочным горлышком, в разных имплементациях HTTP/1. В итоге процесс рукопожатия стал выглядеть так:

Иллюстрация Cloudflare Например, в Chrome и в последних версиях Firefox допускается до шести соединений.

Шифрование предполагалось также отдать на откуп другим протоколам, и для этого поверх TCP стал использоваться протокол TLS, который достаточно надёжно защищал данные, но ещё больше увеличивал время, необходимое на установление соединения.

1 обладал рядом проблем: Таким образом HTTP/1.

  • Медленная установка соединения.
  • Данные передаются в текстовом виде, а значит передача картинок, видео и прочей нетекстовой информации неэффективна.
  • Одно TCP-соединение используется для одного запроса, а значит остальные запросы должны либо найти себе другое соединение, либо ждать, пока текущий запрос его отпустит.
  • Поддерживается только pull-модель. В стандарте нет ничего о server-push.
  • Заголовки передаются текстом.

Если server-push худо-бедно реализуется с помощью протокола WebSocket, то с остальными проблемами предстояло разбираться более радикально.

Немного современности: HTTP/2

В 2012-м году в недрах Google началась работа над протоколом SPDY (произносится «спиди»). Протокол был призван решить основные проблемы HTTP/1.1 и при этом должен был сохранить обратную совместимость. В 2015 году рабочая группа IETF представила спецификацию HTTP/2, основанную на протоколе SPDY. Вот какие отличия были в HTTP/2:

  • Бинарная сериализация.
  • Мультиплексирование нескольких HTTP-запросов в одно TCP-соединение.
  • Server-push из коробки (без WebSocket).

Протокол стал большим шагом вперед. Он сильно выигрывает у первой версии по скорости и не требует создания нескольких TCP-соединений: все запросы к одному хосту мультиплексируются в одно. То есть в одном соединении есть несколько так называемых стримов, каждый из которых имеет свой ID. Бонусом идет коробочный server-push.

Представьте, что мы асинхронно выполняем 5 запросов к одному серверу. Однако мультиплексация ведёт к другой краеугольной проблеме. Очевидно, что чем хуже качество соединения, тем медленнее работает HTTP/2. При использовании HTTP/2 все эти запросы будут выполняться в рамках одного TCP-соединения, а значит, если один из сегментов любого запроса потеряется или придёт неверно, передача всех запросов и ответов остановится, пока не будет восстановлен потерявшийся сегмент. 1 в браузере показывает себя лучше, чем HTTP/2 за счёт того, что открывает 6 соединений, а не одно. По оценке Дениела Стенберга, в условиях, когда потерянные пакеты составляют 2% от всех, HTTP/1.

Эта проблема называется «head-of-line blocking» и, к сожалению, решить её при использовании TCP не представляется возможным.

Иллюстрация Daniel Steinberg

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

Нам нужен новый протокол: UDP vs TCP

Довольно быстро стало понятно, что внедрить совсем новый протокол транспортного уровня – задача в сегодняшних реалиях нерешаемая. Дело в том, что о транспортном уровне знают железки или middle-boxes (роутеры, файрволы, NAT-серверы...), а научить их чему-то новому крайне непростая задача. Кроме того, поддержка транспортных протоколов зашита в ядро операционных систем, а ядра тоже меняются не то чтобы очень охотно.

Да-да, тот самый протокол, по которому мы кидались файликами по локалке в конце девяностых-начале нулевых. И тут можно было бы опустить руки и сказать «Мы, конечно, изобретём новый HTTP/3 с преферансом и куртизанками, но внедряться он будет 10-15 лет (примерно через такое время будет заменено большинство железок)», но есть ещё один не самый очевидный вариант: использовать протокол UDP. Практически все сегодняшние железки умеют с ним работать.

В первую очередь в том, что у нас нет сессии транспортного уровня, о которой знает железо. В чём преимущества UDP по сравнению с TCP? То есть мы не ограничены одной или несколькими сессиями (как в TCP), а можем насоздавать их столько, сколько нам нужно. Это позволяет нам самим определять сессию на конечных точках и там же разруливать возникающие конфликты. Таким образом, в теории, мы можем пробить сегодняшний потолок скорости, достигнутый в HTTP/2. Во-вторых, передача данных по UDP происходит быстрее, чем по TCP.

Фактически мы просто посылаем пакеты, надеясь, что на другом конце их получат. Однако UDP не гарантирует надёжность передачи данных. Ну не повезло… Этого было достаточно для передачи видео для взрослых, но для более серьёзных вещей нужна надёжность, а значит придётся накрутить что-то ещё поверх UDP. Не получили?

В 2013-м Джим Роскинд представил широкой общественности протокол QUIC (Quick UDP Internet Connections), а уже в 2015-м был внесен Internet Draft для стандартизации в IETF. Как и в случае с HTTP/2, работа по созданию нового протокола началась в Google в 2012-м году, то есть примерно в одно время с началом работы над SPDY. Уже на тот момент протокол, разработанный Роскиндом в Google сильно отличался от вынесенного на стандарт, поэтому гугловскую версию стали называть gQUIC.

Что такое QUIC

Во-первых, как уже было сказано, это обёртка над UDP. Поверх UDP поднимается QUIC-connection, в котором по аналогии с HTTP/2 могут существовать несколько стримов. Эти стримы существуют только на конечных точках и обслуживаются независимо. Если потеря пакета произошла в одном стриме, другие это никак не затронет.

Иллюстрация Daniel Steinberg

Это позволяет устанавливать соединение и обмениваться публичными ключами за одно рукопожатие, а также позволяет использовать хитрый механизм 0-RTT handshake и вообще избежать задержек при рукопожатии. Во-вторых, шифрование теперь реализовано не отдельным уровнем, а включено в протокол. Это позволяет не ждать завершения приёма данных из стрима, а расшифровывать полученные пакеты независимо. Кроме того, теперь можно шифровать отдельные пакеты данных. TLS и TCP работали независимо друг от друга, и TLS не мог знать, на какие куски будет рубить данные TCP. Такой режим работы был вообще невозможен в TCP, т.к. Все эти улучшения позволяют QUIC снизить latency по сравнению с TCP.

В-третьих, концепция лёгких стримов позволяет отвязать соединение от IP-адреса клиента. А следовательно, не мог подготовить свои сегменты так, чтобы они укладывались в сегменты TCP один к одному и могли быть расшифрованы независимо. В этом случае при использовании TCP происходит длительный процесс, в ходе которого существующие TCP-соединения отваливаются по таймауту и создаются новые соединения с нового IP. Это важно, например, когда клиент переключается с одной Wi-Fi точки доступа на другую, изменяя свой IP. Т.к. В случае с QUIC, клиент просто продолжает посылать серверу пакеты с нового IP со старым ID стрима. ID стрима теперь уникален и не переиспользуется, сервер понимает, что клиент сменил IP, досылает потерянные пакеты и продолжает коммуникацию по новому адресу.

Это, с одной стороны, позволяет быстрее вносить изменения в протокол, т.к. В-четвертых, QUIC реализуется на уровне приложения, а не операционной системы. С другой стороны, это ведёт к сильному увеличению потребления процессора. чтобы получить обновление достаточно просто обновить библиотеку, а не ждать новую версию ОС.

Компрессия заголовков как раз относится к моментам, которые отличаются в QUIC и gQUIC. Ну и напоследок, заголовки. Подробнее можно прочитать здесь. Не вижу смысла посвящать этому много времени, скажу только, что в версии, поданной на стандартизацию, компрессию заголовков сделали максимально похожей на компрессию заголовков в HTTP/2.

Насколько оно быстрее?

Это сложный вопрос. Дело в том, что пока у нас нет стандарта, особо нечего измерять. Пожалуй, единственные статистические данные, которыми мы располагаем – статистика Гугла, который использует gQUIC с 2013 года и в 2016 отчитался перед IETF, что около 90% трафика, идущего к их серверам от браузера Chrome, теперь использует QUIC. В этой же презентации они сообщают, что через gQUIC страницы загружаются примерно на 5% быстрее, а в потоковом видео на 30% меньше подвисаний по сравнению с TCP.

Последнее, впрочем, удается компенсировать использованием 0-RTT. В 2017-м году группа исследователей во главе с Arash Molavi Kakhki опубликовала большую работу по изучению производительности gQUIC по сравнению с TCP.
Исследование выявило несколько слабых сторон gQUIC, таких как неустойчивость к перемешиванию сетевых пакетов, жадность (unfairness) к пропускной способности канала и более медленная передача небольших (до 10 кб) объектов. О конкретных цифрах тут говорить сложно. Во всех остальных исследованных случаях gQUIC показал рост скорости по сравнению с TCP. Лучше почитать само исследование или короткий пост.

Что будет для QUIC: пока тайна за семью печатями, но есть надежда, что слабые стороны, выявленные у gQUIC, будут учтены и исправлены. Здесь нужно сказать, что это данные именно о gQUIC, и они неактуальны для разрабатываемого стандарта.

Немного будущности: а что с HTTP/3?

А вот тут всё кристально ясно: API никак не изменится. Всё останется ровно так же, как и было в HTTP/2. Ну а если API остаётся прежним, переход на HTTP/3 должен будет решаться использованием на бэкенде свежей версии библиотеки, поддерживающей транспорт по QUIC. Правда, довольно долго ещё придётся держать фоллбэк на старые версии HTTP, т.к. интернет сейчас не готов к полному переходу на UDP.

Кто уже поддерживает

Вот список существующих реализаций QUIC. Несмотря на отсутствие стандарта, список неплохой.

Недавно была информация, что в Chrome включили поддержку HTTP/3, но пока только в Canary. Ни один браузер сейчас не поддерживает QUIC в прод-релизе.

NGINX в конце весны 2019-го объявили, что начали работу над поддержкой HTTP/3, но пока не закончили. Из бэкендов HTTP/3 поддерживает только Caddy и Cloudflare, но пока экспериментально.

Какие есть проблемы

Мы с вами живём в реальном мире, где ни одна большая технология не может пройти в массы, не встретив сопротивления, и QUIC тут не исключение.

Там вообще может не быть TCP. Самое важное, нужно как-то объяснить браузеру, что “https://” теперь не факт, что ведёт на 443-ий TCP-порт. Он позволяет сообщить браузеру, что этот веб-сайт также доступен на таком-то протоколе по такому-то адресу. Для этого используется заголовок Alt-Svc. В теории это должно работать как часы, но на практике мы наткнёмся на то, что UDP может быть, например, запрещён на файрволе во избежание DDoS атак.

мы используем UDP, в котором нет аппаратной сессии, NAT не будет удерживать соединение, и QUIC-сессия будет постоянно обрываться. Но даже если UDP не запрещён, клиент может находиться за NAT-роутером, который настроен на удержание TCP-сессии по IP адресу, а т.к.

Точно так же админы пока не очень понимают, как правильно настроить свои сети для работы QUIC. Все эти проблемы связаны с тем, что UDP раньше не использовался для передачи интернет-контента, и производители железок не могли предвидеть, что такое когда-то случится. Эта ситуация будет потихоньку меняться, и, в любом случае, подобные изменения займут меньше времени, чем внедрение нового протокола транспортного уровня.

Daniel Stenberg оценил рост по процессору до трёх раз. Кроме того, как уже было описано, QUIC сильно увеличивает использование процессора.

Когда наступит HTTP/3

Стандарт хотят принять к маю 2020 года, но, учитывая, что на текущий момент недоделанными остаются документы, запланированные на июль 2019, можно сказать, что дату скорее всего отодвинут.

Если посмотреть HTTP-запрос, который отправляется гугловому поисковику, можно увидеть вот это:
Ну а Гугл использует свою реализацию gQUIC с 2013-го года.

Выводы

QUIC сейчас выглядит как довольно сырая, но очень перспективная технология. Учитывая, что последние 20 лет все оптимизации протоколов транспортного уровня касались в основном TCP, QUIC, в большинстве случаев выигрывающий по производительности, выглядит уже сейчас крайне неплохо.

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

Будущее не за горами!

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть