Сброс кэша. Автоматизация кэширования. Уменьшение количества запросов
Условное кэширование
Сброс кэша
Кэширование на серверном уровне
Кэширование XHR-запросов Довольно часто от кэширования на клиентском уровне отказываются в силу того, что трудно бывает корректно контролировать поведение всех браузеров в случае частичного или полного изменения ресурсов, которые находятся в кэше.
Строка запроса
На самом деле эта проблема обходится достаточно просто, и в книге «Разгони свой сайт» уже было приведено несколько способов для обеспечения этого механизма. Наиболее простой путь заключается в том, что мы можем добавлять специальный 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"/>';
?>
Данное решение будет неэффективным для высоконагруженных систем, ибо для каждого просмотра 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"/>';
?>
Однако, как и в предыдущем случае, мы все равно обращаемся к файловой системе.
Создание хэша
Как уже было указано выше, для автоматизации процесса сброса кэша на клиенте нам нужно каждый раз запрашивать файловую систему на предмет изменения файла. Если файлов у нас несколько, то дополнительная нагрузка многократно возрастает. Но принимая во внимание то, что мы собираемся все файла объединить в один, мы можем каким-либо образом контролировать изменение итогового файла, не проверяя при этом всех, входящих в него.
Давайте рассмотрим пример кода, приведенного в начале главы. Мы можем собирать все известные нам вхождения файлов в документе в одну строку, а затем вычислять от полученной строки хэш:
$hash = ?‘;
foreach ($this->initial_files as $file) {
$hash .= $file[?file_raw‘];
}
$new_file_name = md5($hash);
Таким образом мы будем обновлять клиентский кэш каждый раз, когда у нас изменится хотя бы один вызов тех файлов, которые формируют хэш. Это не решает проблемы, затронутой в начале главы (нам все равно нужно проверять сам файл, в котором объединено несколько, на дату изменения), но помогает решить схожую проблему, которая возникает по ходу обобщения решения на большой проект.
Использование разделенной памяти
Собственно решение проблемы проверки физических файлов на изменение лежит на поверхности. Для этого нам нужно обеспечить: Подключение библиотек разделяемой памяти (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"/>';
}
?>
Как можно видеть, предложенный алгоритм весьма прост (и может сводиться к простой процедуре очистки кэша, когда мы для всех созданных комбинированных файлов форсируем пересборку), но позволяет полностью избежать обращения к файловой системе (или ее кэшу) при действия по клиентской оптимизации страницы.
Таким образом кэширование на клиентском уровне может ускорить загрузку последующих страниц (или посещений) вашего сайта на 500-1000% (в 5-10 раз), а правильное управление кэшированием гарантирует вам, что информация, получаемая пользователями, всегда будет актуальной.