Опубликован: 16.02.2009 | Доступ: свободный | Студентов: 1429 / 138 | Оценка: 4.26 / 4.17 | Длительность: 16:10:00
ISBN: 978-5-9963-0024-2
Лекция 5:

Параллельные соединения

< Лекция 4 || Лекция 5: 12345 || Лекция 6 >

Редиректы, 404-ошибки и повторяющиеся файлы

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

Редиректы

Редиректы осуществляются посредством отправки клиенту статус-кодов HTTP 301, 302 и 307. В качестве примера можно рассмотреть такой HTTP-заголовок со статус-кодом 301:

HTTP/1.1 301 Moved Permanently
Location: http://example.com/newuri

Браузер автоматически перенаправляет пользователя на новый адрес, указанный в поле Location. Вся информация, необходимая для редиректа, есть в этих заголовках, тело ответа обычно остается пустым. Результаты редиректов (ни с кодом 301, ни с кодом 302) на практике не кэшируются, пока это явно не объявляется заголовком Expires либо Cache-Control.

Также для перенаправления пользователя используется мета-тег refresh и JavaScript ( location.href ), однако, если все же необходимо сделать редирект, предпочтительней применение именно статус-кодов HTTP 301 и 302 со стороны сервера. В этом случае у пользователя будут правильно работать кнопки "Назад" и "Вперед". В случае JavaScript речь пойдет только о тех случаях, когда при загрузке первоначального HTML-файла пользователь сразу отправляется на новую страницу. Если же JavaScript используется только для динамической навигации, то это совершенно нормальная ситуация, и она не является ошибочной.

Главное, что нужно помнить при использовании редиректов — это то, что они отнимают время на свое выполнение, а пользователь должен ждать его завершения. Страница даже не может начать отображаться из-за того, что пользователь еще не получил сам HTML-документ, и браузер не может начать загрузку остальных компонентов страницы.

Одним из бесполезных редиректов, которые часто используются (и веб-разработчики не стремятся избегать этого), — когда пользователь забывает ввести завершающий слэш (/) в адресной строке в тех случаях, когда он там должен быть. Например, если попробовать открыть адрес http://webo.in/articles, то браузер получит ответ с кодом 301, содержащий редирект на http://webo.in/articles/ (в последнем случае содержится завершающий слэш). Это исправляется в Apache использованием Alias или mod_rewrite, или же DirectorySlash, если применяются Apache handlers.

Объединение старого и нового сайтов также часто является причиной использования редиректов. Кое-кто объединяет часть старого и нового сайтов и перенаправляет (или не перенаправляет) пользователей, основываясь на ряде факторов: браузере, типе аккаунта пользователя и т. д. Применение редиректов для объединения двух сайтов является достаточно простым способом и требует минимального программирования, но усложняет поддержку проекта для разработчиков и ухудшает восприятие страницы пользователями. Альтернативой редиректу является использование модулей mod_alias и mod_rewrite в случае, если оба URI находятся в пределах одного сервера. Если же причиной появления редиректов является перенаправление пользователя между разными хостами, как альтернативу можно рассматривать создание DNS-записей типа CNAME (такие записи создают псевдонимы для доменов) в комбинации с Alias или mod_rewrite.

Повторяющиеся файлы

Включение одного скрипта дважды на одну страницу снижает производительность. Это не так редко встречается, как можно подумать. Два из десяти наиболее посещаемых сайтов содержат повторяющийся JavaScript-код (обращения к одинаковым JavaScript-файлам). Два основных фактора, которые могут повлиять на возникновение повторяющихся скриптов,— это количество скриптов на странице и количество разработчиков. Если происходит описанная ситуация, повторение скриптов замедляет работу сайта ненужными HTTP-запросами и вычислениями.

Повторяющиеся запросы возникают в Internet Explorer (если версия IE меньше 7, то для загрузки одинаковых картинок на одной странице также может отправляться соответствующее число запросов). Internet Explorer дважды загружает один и тот же скрипт, если он включен в страницу два раза и не кэшируется. Но даже если скрипт закэширован, все равно возникает дополнительный HTTP-запрос, когда пользователь перезагружает страницу.

В дополнение к ненужным HTTP-запросам тратится время на выполнение кода. Повторное исполнение кода происходит во всех браузерах, вне зависимости от того, был ли закэширован скрипт или нет.

Единственным способом избежать повторного включения одного и того же скрипта является разработка системы управления скриптами в виде модуля системы шаблонов. Обычным способом включения скрипта в страницу является использование тега script:

<script type="text/javascript" src="menu_1.0.17.js"></script>

Альтернативой в PHP можно считать создание функции insertScript:

<?php insertScript("menu.js") ?>

Кроме простого предотвращения включения одного скрипта на страницу дважды, такая функция может выполнять и другие задачи, к примеру, отслеживать зависимости между скриптами, добавлять номер версии в название файла скрипта для поддержки HTTP-заголовков Expires и пр.

404-ошибки

Если сервер не может удовлетворить запрос браузера по причине того, что ни один файл не соответствует запрошенному, то он отвечает со статус-кодом 404 ( File Not Found ). Таким образом, браузер понимает, что не может получить соответствующий ресурс, и стандартным образом обрабатывает эту ошибку.

Вроде все хорошо: процесс уже давно описан в спецификации, и браузеры действуют строго согласно ей. Однако в данном случае проблема заключается в том, что если браузер получит 404-ответ от сервера при запросе какого-либо файла, который нужен для данной страницы, то этот запрос можно вообще не осуществлять. Это расходует время загрузки, расходует ресурсы сервера, расходует канал. Обычно при ответе с кодом 404 сервер пересылает еще и HTML-документ, который браузер может отобразить (это та самая 404-страница).

Теперь представим, что браузер запросил небольшую фоновую картинку в 500 байтов. Вместо этого он получает 10Кб HTML-кода, который не может отобразить (потому что это не картинка). Это совсем плохо.

К счастью, все такие запросы легко отследить и устранить при своевременном анализе сайта на "битые" ссылки.

Асинхронные HTTP-запросы

Для большинства сайтов загрузка страницы затрагивает десятки внешних объектов, основное время загрузки тратится на различные HTTP-запросы картинок, JavaScript-файлов и файлов стилей. При работе над оптимизацией времени загрузки страницы в сложном AJAX-приложении было исследовано, насколько можно уменьшить задержку за счет внешних объектов. Особый интерес при этом был вызван конкретной реализацией HTTP-клиента в известных браузерах и параметрами распространенных интернет-соединений, а также тем, как они влияют на загрузку страниц, содержащих большое количество маленьких объектов.

Можно отметить несколько интересных фактов.

  • В IE, Firefox и Safari по умолчанию выключена техника HTTP-конвейера (англ. HTTP pipelining). Opera является единственным браузером, где она включена. Отсутствие конвейера при обработке запросов означает, что после каждого ответа на запрос его соединение освобождается прежде, чем отправлять новый запрос. Не следует путать конвейерную обработку с Connection: keep-alive, когда браузер может использовать одно соединение с сервером, чтобы загружать через него достаточно большое количество ресурсов. В случае конвейера браузер может послать несколько GET-запросов в одном соединении, не дожидаясь ответа от сервера. Сервер в таком случае должен ответить на все запросы последовательно. Это влечет дополнительные задержки на прохождение запроса туда-обратно, что, в общем случае, примерно равно времени ping (отнесенному к разрешенному числу одновременных соединений). Если же на сервере нет элементов поддержки активных HTTP-соединений, то это повлечет еще одно трехступенчатое TCP "рукоп ожатие", которое в лучшем случае удваивает задержку.
  • По умолчанию в IE (а с ним работают сейчас 50-70% пользователей) можно установить только два внешних соединения на один хост при запросе на сервер, поддерживающий HTTP/1.1, или всего 8 исходящих соединений. Использование 4 хостов вместо одного может обеспечить большее число одновременных соединений. IP-адрес в таком случае не играет роли: все хосты могут указывать на один адрес.
  • У большинства DSL- или выделенных Интернет-соединений несимметричная полоса пропускания, она варьируется от 1,5 Мб входящего / 128 Кб исходящего до 6 Мб входящего / 512 Кб исходящего и т. д. Отношение входящего к исходящему каналу в основном находится в пределах от 5:1 до 20:1. Это означает для ваших пользователей, что отправка запроса занимает столько же времени, как и принятие ответа, который в 5–20 раз больше самого запроса. Средний запрос занимает около 500 байтов, поэтому больше всего влияния ощущают объекты, которые меньше, чем, может быть, 2,5–10 Кб. Это означает, что доставка небольших объектов может существенно снизить скорость загрузки страницы в силу ограничения на исходящий канал, как это ни странно.

Моделируем параллельные запросы

На основе заявленных предпосылок можно смоделировать эффективную ширину канала для пользователей, учитывая некоторые сетевые особенности при загрузке объектов различных размеров. Предположим, что каждый HTTP-запрос занимает 500 байтов и что HTTP-ответ содержит дополнительно к размеру запрошенного объекта еще 500 байтов заголовков. Это наиболее простая модель, которая рассматривает только ограничения на канал и его асимметрию, но не учитывает задержки на открытие TCP-соединения при первом запросе для активного соединения, которые, однако, сходят на нет при большом количестве объектов, передаваемых за один раз.

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

Чтобы выявить эффект от активных соединений и введения нескольких хостов, давайте возьмем пользователя с интернет-соединением с 1,5 Мб входящим / 384 Кб исходящим каналом, находящегося на расстоянии 100 мс без потери пакетов. Это в очень грубом приближении соответствует среднему ADSL-соединению на другом краю России с серверами, расположенными в Москве. Ниже показана эффективная пропускная способность канала при загрузке страницы с множеством объектов определенного размера. Эффективная пропускная способность определялась как отношение общего числа полученных байтов ко времени их получения.

 Влияние HTTP-конвейера и постоянного соединения на скорость передачи данных. Источник:www.die.net

Рис. 5.4. Влияние HTTP-конвейера и постоянного соединения на скорость передачи данных. Источник:www.die.net

Предварительные выводы

Следует отметить следующее:

  • Для относительно небольших объектов (левая часть графика) можно сказать, судя по большому пустому месту над линиями, что пользователь очень слабо использует свой канал; при этом браузер запрашивает объекты так быстро, насколько может. Для такого пользователя эффективное использование входящего канала наступает при загрузке объектов размером 100 Кб и более.
  • Для объектов размером примерно в 8 Кб можно удвоить эффективную пропускную способность канала, включив постоянные соединения на сервере и распределив запросы по 4 серверам. Это значительное преимущество.
  • Если пользователь включит конвейерную передачу запросов в своем браузере (для Firefox это будет network.http.pipelining в about:config ), число используемых хостов перестанет играть значительную роль, и он будет задействовать свой канал еще более эффективно, однако мы не сможем контролировать это на стороне сервера.

Возможно, более прозрачным будет следующий график, на котором изображено несколько различных интернет-соединений и выведено относительное ускорение для запроса страницы с множеством мелких объектов для случая использования 4 хостов и включения активного соединения на сервере. Ускорение измеряется относительно случая 1 хоста с выключенным keep-alive (0%).

 Выигрыш при включении постоянного соединения и нескольких хостов для различных пользователей. Источник:www.die.net

Рис. 5.5. Выигрыш при включении постоянного соединения и нескольких хостов для различных пользователей. Источник:www.die.net

Что тут интересного?

  • Если вы загружаете много мелких объектов, меньших, чем 10 Кб, оба пользователя — и тот, что находится локально, и тот, что на другом конце континента, —почувствуют значительное ускорение от включения активного соединения и введения 4 хостов вместо одного.
  • Чем дальше находится пользователь, тем значительнее выигрыш.
  • Чем больше скорость соединения, тем больше выигрыш. Пользователь с Ethernet-каналом в 100 Мб на расстоянии всего 20 мс от сервера почувствует наибольшее ускорение.
< Лекция 4 || Лекция 5: 12345 || Лекция 6 >