Ускоряем загрузку сайта, минимизируя количество HTTP-запросов::Журнал СА 7.2009
www.samag.ru
     
Поиск   
              
 www.samag.ru    Web  0 товаров , сумма 0 руб.
E-mail
Пароль  
 Запомнить меня
Регистрация | Забыли пароль?
О журнале
Журнал «БИТ»
Подписка
Где купить
Авторам
Рекламодателям
Магазин
Архив номеров
Вакансии
Контакты
   

ЭКСПЕРТНАЯ СЕССИЯ 2019


  Опросы

Какие курсы вы бы выбрали для себя?  

Очные
Онлайновые
Платные
Бесплатные
Я и так все знаю

 Читать далее...

1001 и 1 книга  
28.05.2019г.
Просмотров: 305
Комментарии: 1
Анализ вредоносных программ

 Читать далее...

28.05.2019г.
Просмотров: 402
Комментарии: 1
Микросервисы и контейнеры Docker

 Читать далее...

28.05.2019г.
Просмотров: 329
Комментарии: 0
Django 2 в примерах

 Читать далее...

28.05.2019г.
Просмотров: 255
Комментарии: 0
Введение в анализ алгоритмов

 Читать далее...

27.03.2019г.
Просмотров: 823
Комментарии: 0
Arduino Uno и Raspberry Pi 3: от схемотехники к интернету вещей

 Читать далее...

Друзья сайта  

Форум системных администраторов  

sysadmins.ru

 Ускоряем загрузку сайта, минимизируя количество HTTP-запросов

Архив номеров / 2009 / Выпуск №7 (80) / Ускоряем загрузку сайта, минимизируя количество HTTP-запросов

Рубрика: Веб /  Веб

АНТОН ГРИШАН, ведущий программист крупного регистратора доменных имен

Ускоряем загрузку сайта,
минимизируя количество HTTP-запросов

Загрузка результатов работы веб-приложения занимает около 80% от общего времени обработки пользовательского запроса. Почему это так, и как с этим бороться?

Быстрая доставка результатов работы интернет-приложения пользователю (обычно HTML-документ) существенно повышает общую производительность приложения. Один из основных факторов, замедляющих процесс загрузки страницы – избыточное количество HTTP-запросов. Рассмотрим подробнее причины падения скорости загрузки.

Причина №1: Увеличение объёма данных

Для загрузки данных с сервера клиент должен отправить HTTP-запрос и дождаться ответа, содержащего HTTP-заголовок и данные. Таким образом, количество байт, которые необходимо передать по сети для загрузки объекта (изображения, скрипта, таблицы стилей, флеш-ролика), складывается из:

  •  длины HTTP-запроса в байтах;
  •  длины HTTP-заголовка ответа;
  •  длины передаваемых данных.

 Необходимость в передаче и приёме HTTP-заголовков ведет к увеличению объёма данных, передаваемых по сети. Не стоит думать, что это незначительное увеличение, которым можно пренебречь. При посылке запроса браузер отправляет серверу:

  •  тип и версию браузера (User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.0.10) Gecko/2009042316 Firefox/3.0.10);
  •  поддерживаемые форматы данных (Accept: text/html, application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8);
  •  пользовательские предпочтения языков отображения содержимого (Accept-Language: ru,en?us;q=0.7,en; q=0.3);
  •  поддерживаемые алгоритмы сжатия (Accept-Encoding: gzip,deflate);
  •  данные о предпочтительной кодировке (Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7);
  •  cookies, объём которых зависит от содержащихся в них данных;
  •  POST/GET-данные и множество других параметров.

Например, для загрузки моей страницы в социальной сети vkontakte.ru браузер выполнил 40 запросов, суммарный размер переданных и принятых HTTP-заголовков составил 43176 байт (43 Кб), т.е. для загрузки одного объекта потребовалось дополнительно около 1076 байт. Особенно ощутимы накладные расходы при загрузке небольших файлов.

Причина №2: Скорость передачи данных ниже скорости приёма

В последнее время широкой популярностью пользуются технологии доступа к Интернету с асимметричной скоростью передачи данных (например, ADSL). Это означает, что скорость приёма и передачи данных при таком соединении различна, обычно скорость передачи исходящего трафика (посылка запроса) значительно ниже скорости приёма данных (загрузка ответа сервера). Конкретные величины зависят от оборудования пользователя и тарифного плана провайдера, но суть остается прежней. Соотношение скоростей приема/передачи колеблется в диапазоне от 3:1 до 20:1, это означает, что за время, необходимое для передачи одного байта запроса, пользователь может получить от 3 до 20 байт ответа.

Рассмотрим наиболее эффективные способы борьбы с избыточным количеством HTTP-запросов.

Способ №1: объединение объектов

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

 

Для правильной визуализации документа браузеру необходимо загрузить все таблицы стилей и JavaScript-библиотеки, подключаемые внутри документа. Например, если осуществляется подключение трех таблиц стилей и четырёх сценариев JavaScript, то для загрузки документа браузеру необходимо выполнить восемь запросов – по одному запросу для каждого JS/CSS-файла плюс один запрос для получения HTML-кода.

Сократить количество HTTP-запросов можно, объединив все загружаемые таблицы стилей в один файл, а все библиотеки JavaScript – в другой. Таким образом, в приведенном выше примере для загрузки страницы потребуется не 8, а всего 3 запроса, что значительно лучше.

Сложно объединять файлы вручную, особенно если разные страницы требуют разный набор JS/CSS-файлов. Для решения этой проблемы существует множество утилит, наиболее известные из которых minify (http://code.google.com/p/minify) и Combo Handler Service (http://yuiblog.com/blog/2008/07/16/combohandler). Оба сервиса работают по одинаковому принципу, поэтому здесь и далее примеры будут приводиться для minify.

Minify – приложение, написанное на PHP5, основная функции которого – объединение и минификация JS/CSS-файлов с целью повышения скорости загрузки документа за счет сокращения количества HTTP-запросов и уменьшения размера JS/CSS-кода. Рассмотрим принцип работы на примере подключения одинакового количества JS/CSS-файлов с использованием minify и без оного.

1. Подключение JS/CSS-файлов в теле HTML-документа без minify (7 HTTP-запросов):

<link type="text/css" rel="stylesheet" href="/css/main.css" />

<link type="text/css" rel="stylesheet" href="/css/forms.css" />

<link type="text/css" rel="stylesheet" href="/css/menu.css" />

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

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

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

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

2. Подключение JS/CSS-файлов в теле HTML-документа с использованием minify (2 HTTP-запроса):

<link type="text/css" rel="stylesheet" href="/min/b=css&amp;f=main.css,forms.css,menu.css" />

<script type="text/javascript" src="/min/b=js&amp;f=main.js,form.js,calendar.js,menu.js"></script>

 Во втором случае список подключаемых файлов передаётся скрипту minify, который:

  •  объединяет JS/CSS-файлы;
  •  минифицирует объединенный JS/CSS-код (с помощью библиотеки JSMin);
  • сжимает полученный результат с помощью gzip (если браузер пользователя поддерживает сжатие).

В результате сокращается количество запросов и уменьшается размер JS/CSS-кода, что заметно повышает скорость загрузки HTML-страницы. Minify поддерживает систему кэширования, позволяющую избежать повторного выполнения работы по минификации одних и тех же файлов.

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

Объединение изображений

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

Решить эту проблему можно, объединив графические элементы страницы в один файл (ресурсная картинка), который будет загружен браузером за один запрос. Таблица стилей определяет, где и какие части этого графического файла необходимо использовать. Данная техника известна под названием «CSS-спрайты» (CSS sprites).

В качестве примера использования CSS-спрайтов рассмотрим реализацию кнопки «Удалить» на страницах поисковой выдачи системы Google (см. рис. 1).

Рисунок 1. Реализация кнопки «Удалить» на страницах поисковой выдачи системы Google

Рисунок 1. Реализация кнопки «Удалить» на страницах поисковой выдачи системы Google

Ресурсная картинка, содержащая графические элементы пользовательского интерфейса поисковой системы Google (см. рис. 2), располагается по адресу (на момент написания данного текста) http://www.google.ru/images/nav_logo4.png.

Рисунок 2. Графические элементы пользовательского интерфейса поисковой системы Google

Рисунок 2. Графические элементы пользовательского интерфейса поисковой системы Google

HTML-код, отвечающий за отображение кнопки «удалить», выглядит следующим образом:

<button class=w20 title="Remove"></button>

Как видно из кода, кнопка реализована в виде button-элемента, использующего для отображения класс w20. C помощью CSS определяется правило отображения элементов класса w20:

.w10, .w11, .w20, .w21, .w24, .wci, .wpb {

background:transparent url(/images/nav_logo4.png) no-repeat scroll 0 0;

border:0 none;

cursor:pointer;

height:16px;

margin-left:8px;

vertical-align:bottom;

width:16px;

}

.w20, .w21 {

margin-left:3px;

}

.w20 {

background-position:-152px -32px;

}

В первом блоке фигурных скобок определяются общие правила отображения элементов, подобных w20. Класс задаёт параметры ширины, высоты, отступов и указывает фоновое изображение (/images/nav_logo4.png). Так как для разных элементов используются разные части ресурсной картинки, конкретные координаты указываются только для класса w20, в последнем блоке фигурных скобок (background-position:-152px -32px;).

Создавать ресурсные картинки вручную – довольно утомительное занятие, поэтому для автоматизации процесса существуют специальные online-сервисы, такие как http://spritegen.website-performance.org.

Техники CSS-спрайтов позволяют существенно увеличить производительность сайта, но прежде чем принимать решение об использовании данной технологии, необходимо ознакомиться со списком достоинств и недостатков:

Достоинства:

Сокращение количества HTTP-запросов. Благодаря ресурсной картинке все графические элементы могут быть загружены за один HTTP-запрос.

Уменьшение объёма файлов. Размер ресурсной картинки меньше (в среднем на 30%) суммарного размера графических файлов, в неё входящих. Этот факт объясняется особенностями графических форматов и алгоритмов сжатия.

Недостатки:

Усложнение процесса разработки. Использование техники CSS-спрайтов требует дополнительных навыков верстки с учетом возможных отличий при отображении в разных браузерах.

Сложность сопровождения и обновления. Добавление, удаление или модификация графического элемента, отображаемого с помощью CSS-спрайтов, требует редактирования объединенного графического файла. Значительно больше проблем возникает, когда модификация графического элемента приводит к изменению его размера, в этом случае придется редактировать таблицы стилей.

Замедленное отображение страницы. Неправильное использование спрайтов может привести к визуальному замедлению загрузки сайта. Дело в том, что объединенный файл, содержащий все графические элементы пользовательского интерфейса, будет загружаться медленнее, чем половина картинок, в него входящих. Браузер может использовать картинку сразу после загрузки, поэтому в случае множества мелких изображений отдельные графические элементы начнут отображаться скорее, хотя полная загрузка сайта займет большее количество времени.

CSS-спрайты не имеют собственного URL, alt и title. Картинка, отображаемая с помощью CSS-спрайтов, не имеет собственного URL-адреса, а также атрибутов alt (альтернативный текст) и title (заголовок). Если пользователь отключит в браузере отображение графических элементов, то на месте изображения, выводимого через спрайты, ничего не будут отображено. То есть не будет ни альтернативного текста, ни пиктограммы, говорящей о том, что здесь должно быть изображение.

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

Ресурсная картинка не должна быть большой. Старайтесь создавать ресурсные картинки, размер которых не превосходит 10-15 Кб. При необходимости создайте несколько ресурсных картинок, содержащих различный набор графических элементов.

Объединяйте только совместно используемые изображения. Объединить в одну ресурсную картинку можно все графические элементы сайта, но это не значит, что так стоит поступать. Некоторые картинки используются для отображения на определенных страницах. Если иконка с изображением телефона используется только на странице «Контакты», то её не обязательно загружать на главной странице. Создавайте ресурсные картинки таким образом, чтобы загружались только реально используемые графические элементы.

Использование CSS-спрайтов не всегда оправдано. В случаях, когда для изображения наличие URL, alt- и title-атрибутов важно с точки зрения функциональности и/или поискового продвижения (SEO), не стоит использовать спрайты. В качестве примера можно привести логотип сайта или изображение товара в online-магазине.

Объединение текстовых и графических данных

Обычно графические изображения в HTML/CSS-коде подключаются с помощью указания пути до нужного файла:

<img src="icon.png" alt="подключение картинки" />

.test {background:url(icon.png);}

В соответствии со схемой data:URL (см. RFC 2397), URL можно использовать не только для указания места нахождения данных, но и для размещения самих данных внутри URL. Формат data:URL предельно прост:

data:[<MIME-type>][;charset="<encoding>"][;base64],<data>

где:

[<MIME-type>] – тип хранимых данных, например, для картинок в формате PNG данный параметр имеет вид image/png. Если тип данных не указан, то предполагается text/plain.
[;charset=”<encoding>”] – кодировка текстовых данных в случае с картинкой не имеет смысла и поэтому не указывается.
[;base64] – метод кодирования данных чаще всего используется base64, но если этот параметр опущен, то данные представляются, с использованием кодировки ASCII в диапазоне безопасных символов URL. Для символов вне этого диапазона применяется стандартное %xx шестнадцатеричное кодирование URL.

Например, так выглядит тег img, содержащий в поле src не путь до картинки, а саму картинку в формате data:URL:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC" alt="картинка внутри HTML" />

Аналогичным образом можно использовать картинки в CSS-классах:

.test {

background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC);

}

Для преобразования графических файлов в формат data:URL существуют прикладные программы и online-сервисы, например, http://www.sveinbjorn.org/dataurlmaker.

Как и любая другая технология, data:URL обладает рядом преимуществ и недостатков, которые необходимо знать для максимально эффективного использования.

Достоинства:

  • Для загрузки data:URL-данных не требуются дополнительные HTTP-запросы, что позволяет уменьшить нагрузку на сеть в случаях, когда встроенное содержимое сопоставимо по размеру с заголовком HTTP-запроса/ответа.
  • Браузеры имеют ограниченное количество одновременных подключений к серверу (обычно 4, данный параметр зависит от браузера), таким образом, использование data:URL освобождает подключения для загрузки остального содержимого страницы.
  •  Встроенные изображения позволяют иметь HTML-до-кумент с графическими изображениями в виде одного файла.

 Недостатки:

  • Встроенные в тело HTML-документа изображения не кэшируются браузером, если сам HTML-документ не кэшируется.
  • Не все браузеры поддерживают встроенные изображения, как всегда, проблемы возникают с Internet Explorer, поддержка схемы data:URL появилась только в восьмой версии браузера.
  • Размер изображения, закодированного в base64, больше бинарного аналога примерно на 30-40%.
  • Максимальная длина URL, которую обязан поддерживать браузер, – 1024 байта (в соответствии со стандартом RFC), поэтому данная техника подходит только для небольших изображений.
  • Картинки в формате data:URL, расположенные в CSS/HTML-файлах не могут загружаться в параллельном режиме, так как для загрузки одного CSS/HTML-файла, содержащего внедренные с помощью data:URL изображения, браузер использует одно соединение.
  • Размещение в HTML-коде страницы двух одинаковых картинок в формате data:URL, потребует дублирования кода, что приведет к увеличению размера страницы и времени загрузки.
  • Для модификации изображения потребуется редактировать все файлы, в которых оно использовано в формате data:URL.

Несмотря на внушительный список недостатков, при правильном использовании данная технология крайне полезна в таблицах стилей для хранения небольших изображений (фоны, пиктограммы, элементы дизайна). В теле HTML-до-кумента встроенные изображения не должны встречаться, единственным исключением может быть иконка страницы (favicon.ico). Пример отображения иконки сайта с использованием data:URL:

<?php

function getFaviconHtml($faviconPath = 'favicon.ico') {

  $isBrowserSupportDataUrl = !isset($_SERVER['HTTP_USER_AGENT']) || (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') === false);

return '<LINK rel="icon" href="'.($isBrowserSupportDataUrl ? convertImgToDataUrlFormat($faviconPath, 'image/x-icon'): $faviconPath).'" type="image/x-icon">';

}

function convertImgToDataUrlFormat($imgFilePath, $mimeType)  {

  $contents = file_get_contents($imgFilePath);

  $base64   = base64_encode($contents);

  return ('data:' . $mimeType . ';base64,' . $base64);

}

?>

<html>

<head>

  <?=getFaviconHtml('favicon.ico')?>

    <title>Favicon with data:URL</title>

</head>

<body>

HELLO data:URL

</body>

</html>

В приведенном примере HTML-код, содержащий инструкции по отображению иконки сайта (favicon.ico), генерируется с помощью функции getFaviconHtml. Внутри функции осуществляется проверка типа браузера. Для Internet Explorer (т.е. предположительно не поддерживает data:URL) отображается путь до картинки, для остальных изображение преобразуется в формат data:URL.

В данном примере осуществляется очень примитивная проверка браузера, в реальных проектах необходимо учитывать не только тип, но и версию браузера.

Способ №2: загрузка объектов из кэша браузера

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

Контроль актуальности данных

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

Каким образом браузер узнает о том, что файл устарел и требуется повторная загрузка? Для этого существуют три методики.

1. Дата последней модификации. При первой загрузке файла сервер отправляет клиенту дату последней модификации файла в поле Last-Modified заголовка HTTP-ответа:

HTTP/1.x 200 OK

Date: Thu, 04 Jun 2009 06:41:11 GMT

Server: Apache/2.2.4 (Win32) mod_ssl/2.2.4

OpenSSL/0.9.8d PHP/5.2.4

Last-Modified: Wed, 03 Jun 2009 05:24:27 GMT

Accept-Ranges: bytes

Content-Length: 9973

Keep-Alive: timeout=5, max=94

Connection: Keep-Alive

Content-Type: image/png

После первой загрузки файл попадает в кэш браузера (иногда и прокси-сервера). При повторном использовании браузер, чтобы убедиться в том, что в кэше хранится последняя версия файла, – повторно отправляет HTTP-запрос на загрузку файла, но в этот раз, указывает дату последней модификации запрашиваемого файла (поле If-Modified-Since):

GET /logo.png HTTP/1.1

Host: test.localhost

User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ru;

rv:1.9.0.10) Gecko/2009042316 Firefox/3.0.10

Accept: image/png,image/*;q=0.8,*/*;q=0.5

Accept-Language: ru,en-us;q=0.7,en;q=0.3

Accept-Encoding: gzip,deflate

Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7

Keep-Alive: 300

Connection: keep-alive

Referer: http://test.localhost/

If-Modified-Since: Wed, 03 Jun 2009 05:24:27 GMT

Cache-Control: max-age=0

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

HTTP/1.x 304 Not Modified

Date: Thu, 04 Jun 2009 06:46:01 GMT

Server: Apache/2.2.4 (Win32) mod_ssl/2.2.4

OpenSSL/0.9.8d PHP/5.2.4

Connection: Keep-Alive

Keep-Alive: timeout=5, max=100

Если даты не совпадают, то сервер передает обновленный файл браузеру, который в свою очередь обновляет данные кэша.

2. Идентификатор версии сущности (ETag). ETag – строка, однозначно идентифицирующая версию файла, вычисляется на стороне сервера. Если файл модифицируется, то изменится и значение ETag. Алгоритм построения идентификатора не имеет принципиального значения, например, может быть использована MD5-сумма файла (хотя это и не самый лучший вариант). При загрузке файла, сервер передает соответствующее значение ETag в поле Etag HTTP-заголовка:

HTTP/1.x 200 OK

Date: Thu, 04 Jun 2009 06:41:11 GMT

Server: Apache/2.2.4 (Win32) mod_ssl/2.2.4

OpenSSL/0.9.8d PHP/5.2.4

Etag: "1eb78-170e-de8f95e0"

Accept-Ranges: bytes

Content-Length: 9973

Keep-Alive: timeout=5, max=94

Connection: Keep-Alive

Content-Type: image/png

Логика работы данного метода полностью совпадает и с проверкой актуальности данных по дате последней модификации, за исключением той разницы, что вместо даты используется ETag.

3. Декларативный срок актуальности данных. В отличие от двух предыдущих способов, мы сами объявляем дату, после которой необходимо загрузить новую версию файла. При запросе на загрузку объекта дата предполагаемой модификации (т.е. дата из будущего) файла, устанавливается в поле Expires:

HTTP/1.x 200 OK

Date: Fri, 05 Jun 2009 06:36:26 GMT

Server: Apache/2.2.4 (Win32) mod_ssl/2.2.4

OpenSSL/0.9.8d PHP/5.2.4

Accept-Ranges: bytes

Content-Length: 11298

Expires: Thu, 15 Apr 2034 20:00:00 GMT

Keep-Alive: timeout=5, max=98

Connection: Keep-Alive

Content-Type: image/jpeg

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

Если заголовок Expires не задан, то для проверки актуальности данных используется дата последней модификации и/или ETag (в зависимости от настроек сервера), что приводит к ситуации, при которой загрузка данных из кэша требует выполнения HTTP-запросов.

Наша цель – минимизировать количество HTTP-запросов, поэтому для кэширования статичных данных подходит только третий вариант (декларативный срок актуальности данных).

Кэширование статичных объектов

Большинство загружаемых объектов, таких как изображения, таблицы стилей и JS-код, – статичны. Для статичных объектов наиболее эффективный метод кэширования – декларативный срок актуальности данных.

Предположим, существует сайт www.domain.tld под управлением сервера Apache. Статичные объекты сайта располагаются в папках:

  • www.domain.tld/res/img/ – изображения;
  • www.domain.tld/res/css/ – таблицы стилей;
  • www.domain.tld/res/js/ – JavaScript-библиотеки.

Есть множество способов, позволяющих включить режим декларативного срока актуальности данных. Один из способов – поместить в папку www.domain.tld/res/ файл .htaccess, содержащий следующие инструкции:

FileETag None

Header unset Last-Modified

Header set Expires "Thu, 15 Apr 2030 20:00:00 GMT"

Первая строка – убирает из HTTP-заголовка ETag, вторая – дату последней модификации, третья добавляет поле Expires. До наступления указанной в Expires даты, файл не будет перезагружен с сервера.

В нашем случае указана дата из далёкого будущего (Thu, 15 Apr 2030 20:00:00 GMT). На практике это означает, что файлы будут загружаться единожды и в дальнейшем всегда использоваться версии, хранящиеся в кэше. Данная мера помогает избавиться от огромного количества HTTP-запросов и существенно повышает скорость повторной загрузки страниц.

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

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

Если на сайте существует изображение с адресом www.domain.tld/res01/img/logo.png, то после изменения файла, должен измениться и его URL www.domain.tld/res02/img/logo.png. Здесь версия файла включена в URL-адрес в виде имени подпапки res02, но это не единственный способ, также версию файла можно передавать в виде параметра, например, www.domain.tld/res/img/logo.png?ver=03.

После очередного обновления сайта изменятся URL адреса даже тех объектов, которые в действительности не были модифицированы, что приведет к необходимости загружать все объекты заново, даже если часть из них уже присутствует в кэше.

Решить проблему можно путем присвоения индивидуальной версии каждому объекту. Идентификатор версии объекта должен быть привязан к данным, хранящимся внутри файла. Для вычисления идентификатора версии можно использовать crc32, md5 или дату последней модификации файла в формате UnixTime. Таким образом мы создаем свой аналог ETag, передаваемый посредством URL:

www.domain.tld/res/img/logo.png?etag=342345334

www.domain.tld/res/img/map.png?etag=983234521

Вычисление индивидуального идентификатора версии объекта позволяет эффективнее использовать систему кэширования. Однако налагает обязательство всякий раз при генерации ссылки на объект вычислять его версию, что даёт дополнительную нагрузку на сервер. Для большинства проектов разумным компромиссом будет использование в виде версии единого значения (определяемого константой) для всех объектов.

Способ вычисления идентификатора версии объекта и внедрения его в URL-адрес не влияет на принцип работы данного метода кэширования.

Для правильной работы сайта необходимо, чтобы все страницы использовали корректные URL-объекты. Редактирование всех мест, где осуществляется подключение таблиц стилей, скриптов и изображений после каждого обновления сайта, достаточно утомительное и малопродуктивное занятие. Лучшее решение проблемы – создание метода, отвечающего за построение URL-адреса объекта с учетом текущей версии, например, так:

<?

class View {

    const resVersion = '04';

 

    public static function includeImg($fileName)   {

    return '/res'.self::resVersion.'/img/'.$fileName;

    }

}

 

echo '<img src="'.View::includeImg('logo.png').'">';

?>

Проверка теории практикой

Для проверки эффективности данного метода кэширования проведем эксперимент. Создадим простую веб-страницу вида:

<html>

<head>

<link rel="stylesheet" href="/res_v1/css/style.css" type="text/css">

    <title>Test cache system</title>

</head>

<body>

<h1>Try to load some</h1>

<p>Load some imgs, load some aditional CSS and JS code</p>

 

<img src="/res_v1/img/1.jpg">

<img src="/res_v1/img/2.jpg">

<img src="/res_v1/img/3.jpg">

<img src="/res_v1/img/4.jpg">

<img src="/res_v1/img/5.jpg">

 

<script type="text/javascript" src="/res_v1/js/test.js"></script>

</body>

</html>

Сконфигурируем веб-сервер таким образом, чтобы для кэширования объектов из папки res_v1 использовалась метка сущности (ETag). С помощью Firebug (расширение для Firefox) измерим скорость повторной загрузки страницы, т.е. в момент загрузки все объекты присутствуют в кэше браузера (см. рис. 3).

Рисунок 3. Результат: 8 HTTP-запросов, 47ms

Рисунок 3. Результат: 8 HTTP-запросов, 47ms

Теперь отключим ETag и в качестве алгоритма кэширования воспользуемся датой из далёкого будущего с включением версии объекта в URL строку (см. рис. 4).

Рисунок 4. Результат: 1 HTTP-запрос, 20ms

Рисунок 4. Результат: 1 HTTP-запрос, 20ms

Из результатов эксперимента видно, что во втором случаи страница была загружена на 27ms быстрее (т.е. ускорение на 57%).

Достоинства и недостатки

Достоинства:

  • Максимальное ускорение повторной загрузки кэшируемых объектов. Снижение нагрузки на сервер.

 Недостатки:

  • Необходимость контролировать версии файлов и актуальность URL-адресов.
  • Кэшируемые подобным образом объекты не должны иметь внешних ссылок. Если потребуется установить ссылку на картинку, содержащую версию сайта http://www.domain.tld/res04/img/logo.png, то после обновления сайта, URL картинки изменится, предыдущая ссылка перестанет работать. Сервер можно настроить таким образом, чтобы ссылки вида http://www.domain.tld/res[любое число]/img/logo.png указывали на существующий файл, но в этом случае пользователь не сможет получать обновления.

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

Способ №3: распределение объектов между серверами

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

Стандарт, описывающий протокол HTTP/1.1, рекомендует не более 2 параллельных соединений. С тех пор, когда был написан стандарт, прошло много времени, средняя скорость доступа к сети возросла, поэтому современные браузеры позволяют осуществлять большее количество соединений (от 4 до 8, в зависимости от типа и версии браузера).

Если браузер пользователя имеет ограничение на количество одновременных загрузок равное 4, то при отображении страницы, требующей подключения 20 различных объектов, параллельная загрузка начнётся для первых 4 ресурсов, остальные 16 будут ожидать появления свободного соединения.

Обойти это ограничение можно. Идея заключается в том, чтобы распределить загружаемые ресурсы между несколькими хостами. Например, для сайта www.domain.tld картинки можно разместить с использованием хостов img1.domain.tld, img2.domain.tld,…, imgN.domain.tld (IP-адреса хостов могут совпадать).

Суммарное количество HTTP-запросов останется прежним, однако они будут распределены между несколькими хостами, что позволит одновременно загружать большее число объектов.

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

<?php

class View {

    const resVersion = '02';

    const baseDomain = 'domain.tld';

    const hostAmount = 4;

    public static function includeImg($fileName)   {

    $hostN = abs(crc32($fileName)) % self::hostAmount;

    return http://img'.$hostN.'.'.self::baseDomain.'/res'.self::resVersion.'/img/'.$fileName;

    }

}

echo '<img src="'.View::includeImg('logo.png').'">';

echo '<img src="'.View::includeImg('logo8.png').'">';

echo '<img src="'.View::includeImg('user.gif').'">';

echo '<img src="'.View::includeImg('foto.png').'">';

?>

Результатом работы скрипта будет следующий HTML-код:

<img src="http://img0.domain.tld/res02/img/logo.png">

<img src="http://img1.domain.tld/res02/img/logo8.png">

<img src="http://img2.domain.tld/res02/img/user.gif">

<img src="http://img3.domain.tld/res02/img/foto.png">

 В алгоритме распределения важны два фактора:

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

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

Чтобы браузер мог загрузить файл с хоста, ему необходимо знать IP-адрес. Определение IP-адреса по имени хоста занимает время, если эту операцию придется выполнять для каждого загружаемого объекта, то это существенно понизит скорость загрузки страницы.

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

Какое количество хостов оптимально для загрузки объектов? Для ответа на этот вопрос специалисты компании Yahoo провели исследование (http://yuiblog.com/blog/2007/04/11/performance-research-part-4). Исследователи пришли к выводу, что нужно использовать минимум два хоста для загрузки объектов, при увеличении числа хостов свыше четырёх наблюдается падение производительности.

С моей точки зрения, разумно остановиться на небольшом числе хостов, зависящем от количества загружаемых объектов, например один хост для 10-15 объектов. Этот параметр индивидуален для каждого проекта и должен подбираться экспериментальным путём.

Подводим итоги

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

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

  • отсутствие кэширования;
  • все данные загружаются последовательно, в один поток, что существенно замедляет процесс визуализации страницы.

Важно помнить, что снижение количества HTTP-запросов не конечная цель, а средство повышения скорости загрузки сайта. Секрет высокой производительности – поиск оптимального количества HTTP-запросов, позволяющих максимально эффективно использовать канал передачи данных и систему кэширования.

  1. Библиотека для минификации JS/CSS-кода и минимизации HTTP-запросов – http://code.google.com/p/minify и http://yuiblog.com/blog/2008/07/16/combohandler.
  2. Передача изображения внутри HTML-документа – http://ru.wikipedia.org/wiki/Data:_URL.
  3. Подробное объяснение техники CSS Sprite – http://www.alistapart.com/articles/sprites и http://css-tricks.com/css-sprites.
  4. Online-сервис для генерации CSS-спрайтов – http://spritegen.website-performance.org.
  5. Online-сервис по преобразованию изображений в data:URL-формат – http://www.sveinbjorn.org/dataurlmaker.
  6. Правила, регулирующие количество одновременных соединений для протокола HTTP/1.1: http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.1.4.
  7. Использование нескольких доменов для загрузки объектов – http://www.stevesouders.com/blog/2009/05/12/sharding-dominant-domains и http://yuiblog.com/blog/2007/04/11/performance-research-part-4.

Комментарии отсутствуют

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

Комментарии могут оставлять только зарегистрированные пользователи

               Copyright © Системный администратор

Яндекс.Метрика
Tel.: (499) 277-12-41
Fax: (499) 277-12-45
E-mail: sa@samag.ru