HTTP/3: плохо забытое старое

С 1990-х мы пользуемся протоколом обмена текстовой информацией HTTP. Многие где-то такое слышали или видели, но мало кто задаёт себе вопрос: “Как он работает и что это вообще?” Есть, конечно, и специалисты, для кого HTTP — это то, под чем TLS, TCP, IP и так далее. 

Вот для части спецов TCP — это устаревшая и слабая технология, и они ищут новый идеальный протокол. И интересно тут то, что ищут они новое, но вернулись к технологиям из 1980-х.

Первый рабочий. HTTP/1.1

Первым HTTP со своим RFC была версия 1.1. RFC — это документация для интернета, и, если грубо, то в 1997 году с её помощью HTTP легализовали. Уже тогда браузеры поддерживали протокол, который работал в формате “запрос-ответ” и, в основном, передавал текстовую информацию.

Работал протокол поверх TCP. Этот протокол обеспечивал надёжную доставку пакетов до нужного адресата. Принцип работы TCP: установка соединения между точкой А и B, его поддержание, и разбиение трафика на отдельные сегменты. У каждого сегмента есть номер и контрольная сумма, поэтому при неполадках передача останавливается, пока потерянный  потерянный сегмент восстановится.

В HTTP/1.0 TCP-соединение 3-Way-Handshake закрывалось после каждого запроса и открывалось для нового. Такой типа соединения — медленный, поэтому по такой схеме это было затратно. В версии 1.1 появилась технология keep-alive, где одно соединение использовалось для нескольких запросов. keep-alive легко может стать ботлнэком, поэтому сейчас в HTTP/1.1 возможно открыть к одному хосту несколько TCP-коннектов. В Хроме и Фаерфокс их может быть 6.

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

Иллюстрация Cloudflare

На выходе HTTP/1.1 имел проблемы:

  • Медленный коннект
  • Один TCP-коннект — один запрос. Другие запросы — другое соединение. Или ждём, пока запрос освободит коннект.
  • Поддерживается только pull-модель. server-push? Не, не слышал.
  • Заголовки передаются текстом.

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

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

Google начали работать над новым протоколо SPDY в 2012 году. Он должен был решить проблемы HTTP/1.1 и не потерять обратную совместимость. И в 2015 году IETF показала спецификацию HTTP/2 на основе протокола SPDY.

В HTTP/2:

  • была бинарная сериализация;
  • несколько HTTP-запросов вместили в одно TCP-соединение;
  • server-push без WebSocket по умолчанию.

Вроде бы немного, но протокол прям сильно лучше предыдущего. HTTP/2 сильно быстрее и запросы к одному хосту мультиплексируются в один. Но из этого появились новые проблемы.

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

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

Разработчики HTTP/2 почти полностью выжали протокол на прикладном уровне OSI. Осталось только спускаться к транспортному уровню и делать новый протокол там.

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

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

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

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

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

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

Что такое QUIC?

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

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

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

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

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

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

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

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

В 2017-м году группа исследователей во главе с Arash Molavi Kakhki опубликовала большую работу по изучению производительности gQUIC по сравнению с TCP.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Выводы

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

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

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

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