Опубликован: 16.06.2010 | Доступ: свободный | Студентов: 2353 / 93 | Оценка: 4.39 / 4.00 | Длительность: 17:32:00
ISBN: 978-5-9963-0253-6
Лекция 5:

Уменьшение количества запросов

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

4.5. Автоматизация кэширования

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


4.5.1. Кэширование на клиентском уровне

Настройка кэширования для предотвращения дополнительных запросов из браузера к серверу осуществляется достаточно просто: нужно всего лишь знать наиболее характерные случаи использования.

Статические ресурсы без сжатия

Форсирование кэширования для статических ресурсов может выполняться без сжатия. В данном случае мы ничем не рискуем, выставляя не только максимальное время кэширования, но и предлагая кэшировать ресурсы на локальных прокси-серверах (директива Cache-Control: public ). Для PHP у нас получится следующий код (в Expires прописана дата на 10 лет вперед относительно текущего времени на сервере):

<?php
header("Cache-Control: public, max-age=315360000");
header("Expires: Mon, 01 Jul 2019 00:00:00");
?>
Листинг 4.8.

В случае выставления директив для Apache:

<FilesMatch \.(bmp|png|gif|jpe?g|ico|swf|flv|pdf|tiff)$>
Header append Cache-Control public
ExpiresActive On
ExpiresDefault "access plus 10 years"
<FilesMatch>
Листинг 4.9.

И в случае nginx:

location ?* ^.+\.(bmp|gif|jpg|jpeg|png|swf|tiff|swf|flv)$ {
expires 10y;
header set Cache-Control public;
}
Листинг 4.10.

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

Статические ресурсы со сжатием

Все отличие от предыдущего случая заключается в том, что разрешить локальным прокси-серверам кэшировать такие ресурсы (обычно это CSS-, JavaScript- или ICO-файлы) нельзя. Даже больше: нам нужно запретить кэширование сжатых версий файлов, чтобы избежать выдачи сжатого файла тем пользователям, которые сжатия не поддерживают. Код для PHP:

<?php
header("Cache-Control: private, max-age=315360000");
header("Expires: Mon, 01 Jul 2019 00:00:00");
?>
Листинг 4.11.

Для Apache:

<FilesMatch \.(css|js|ico)$>
Header append Cache-Control private
ExpiresActive On
ExpiresDefault "access plus 10 years"
</FilesMatch>
Листинг 4.12.

И для nginx:

location ~* ^.+\.(css|js|ico)$ {
expires 10y;
header set Cache-Control private;
}
Листинг 4.13.

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

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

Запрет кэширования динамических ресурсов

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

Для того чтобы запретить кэширование во всех браузерах HTML-документов, нужно написать с помощью PHP (в Expires прописано текущее время на сервере):

header("Expires: Wed, 01 July 2009 00:00:00");
header("Cache-Control: no-store, no-cache, must-revalidate,
private");
header("Pragma: no-cache");
Листинг 4.14.

Для Apache:

<FilesMatch \.(php|phtml|shtml|html|xml|htm)$>
ExpiresActive Off
Header append Cache-Control "no-store, no-cache,
must-revalidate, private"
Header append Pragma "no-cache"
</FilesMatch>
Листинг 4.15.

Для nginx:

location ?* ^.+\.(php|phtml|shtml|html|xml|htm)$ {
expires 0;
header set Cache-Control "no-store, no-cache, must-revalidate,
private";
header set Pragma "no-cache";
}
Листинг 4.16.

4.5.2. Условное кэширование

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

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

Существует два способа обеспечить условное кэширование: при помощи заголовков Last-Modified + If-Modified-Since и ETag + If-None-Match. Первый заголовок в паре относится к ответу со стороны сервера, второй — к запросу со стороны клиента, который уже получил соответствующий ответ сервера и теперь желает проверить, изменилось ли что-нибудь с той поры.

При помощи пары Last-Modified мы можем установить соответствие ресурса только по времени его изменения. ETag (англ. Entity Tag, метка сущности) предполагает более широкую сферу применения: мы можем для ресурса назначить уникальную строку, зависящую не только от времени изменения, но и от других параметров (например, передается данный файл в сжатом виде или нет).

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

Давайте рассмотрим, как передавать и проверять данную пару заголовков на примере динамического содержимого HTML-документа (предполагается, что документ изменяется относительно редко, поэтому мы можем его закэшировать).

/* получаем содержимое документа, например, из закэшированного
файла */
$content = @file_get_contents($file);
/* вычисляем уникальную метку, зависящую от содержания */
$hash = md5($content);
/* проверяем соответствие вычисленной и переданной с клиента
меток */
if ((isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) == '"' . $hash .
'"') ||
(isset($_SERVER['HTTP_IF_MATCH']) &&
stripslashes($_SERVER['HTTP_IF_MATCH']) == '"' . $hash . '"')) {
/* в случае совпадения меток передаем 304-ответ */
header ("HTTP/1.0 304 Not Modified");
/* и не передаем содержимое документа */
header ("Content-Length: 0");
exit();
}
/* во всех остальных случаях выставляем заголовок ETag */
header("ETag: \"" . $hash . "\"");
/* и отдаем полностью содержимое документа */
echo $content;
Листинг 4.17.

4.5.3. Сброс кэша

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

Строка запроса

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


Рассмотрим следующий вызов CSS-файла:

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

В строке запроса (части адреса, идущей после ?) мы видим метку времени (20090701). Не обязательно вводить здесь именно время, это может быть также и номер ревизии из системы хранения версий, и любая другая метка, которая бы однозначно могла указывать на изменение файла: разные метки должны соответствовать разным файлам.

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

<?php
/* получаем метку времени, равную времени изменения файла */
$timestamp = filemtime($file);
/* выводим ссылку на файл в HTML-документе */
echo '<link rel="stylesheet" href="/css/main.css?". $timestamp .
"' type="text/css"/>';
?>
Листинг 4.18.

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

Физическое имя файла

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

RewriteRule ~(.*)(\.v[0-9]+)?\.(css|js)$ $1.$2 [QSA,L]

Какой оно несет смысл? А тот, что при указании в HTML-документе ссылки на файл

main.layout.v123456.css

сервер отдаст физический файл

main.layout.css

Таким образом мы элегантно обходим проблему прокси-серверов одной строкой в конфигурации сервера. Соответствующий PHP-код будет выглядеть так:

<?php
/* получаем метку времени, равную времени изменения файла */
$timestamp = filemtime($file);
/* выводим ссылку на файл в HTML-документе */
echo '<link rel="stylesheet" href="/css/main.css.v". $timestamp .
"' type="text/css"/>';
?>
Листинг 4.19.

Однако, как и в предыдущем случае, мы все равно обращаемся к файловой системе.

Создание хэша

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

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

$hash = '';
foreach ($this->initial_files as $file) {
$hash .= $file['file_raw'];
}
$new_file_name = md5($hash);
Листинг 4.20.

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

Использование разделенной памяти

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

  • подключение библиотек разделяемой памяти (APC, eAccelerator, memcache);
  • возможность управлять состоянием кэша (редактирование проверяемых файлов через веб-интерфейс, частичный сброс кэша либо полный сброс закэшированных файлов). На примере APC описанный алгоритм выглядит следующим образом:
    <?php
    /* удаляем (ставим время истечения равным 1 с) из APC запись
    относительно */
    /* текущего файла, при изменении каких-либо включенных в него
    файлов */
    if ($changed) {
    apc_store($new_file_name, apc_fetch($new_file_name), 1);
    }
    …
    /* при выдаче закэшированного файла проверяем, нужно ли
    его пересобирать */
    $mtime = apc_fetch($new_file_name);
    if (!$mtime) {
    …
    /* если нужно, то при создании файла записываем текущее время
    в APC */
    $mtime = $_SERVER['REQUEST_TIME'];
    echo '<link rel="stylesheet" href="/css/main.css?". $mtime .
    "' type="text/css"/>';
    apc_store($new_file_name, $mtime);
    } else {
    /* если нет, то у нас уже получено время изменения файла,
    которое можно использовать для метки кэша */
    echo '<link rel="stylesheet" href="/css/main.css?". $mtime .
    "' type="text/css"/>';
    }
    ?>
    Листинг 4.21.

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

Таким образом, кэширование на клиентском уровне может ускорить загрузку последующих страниц (или посещений) вашего сайта на 500-1000% (в 5-10 раз), а правильное управление кэшированием гарантирует вам, что информация,получаемая пользователями, всегда будет актуальной.

< Лекция 4 || Лекция 5: 12345 || Лекция 6 >
Ольга Артёмова
Ольга Артёмова

Доброго времени суток!

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

Сертификация: оптимизация и продвижение web-сайтов.

Наталья Алмаева
Наталья Алмаева
Россия
Вадим Барков
Вадим Барков
Украина, Киев