Кэширование php скриптов. Кэширование PHP. Использование буферизации вывода для кэширования на стороне сервера

  • Перевод

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

Существует множество библиотек для такого кэширования, например, APC , XCache , eAccelerator и Zend Platform .

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

/**
* Compile Files for APC
* The function runs through each directory and
* compiles each *.php file through apc_compile_file
* string $dir start directory
* void
*/
function compile_files($dir)
{
$dirs = glob($dir. DIRECTORY_SEPARATOR. "*", GLOB_ONLYDIR);
if (is_array($dirs) && count($dirs) > 0)
{
while(list(,$v) = each($dirs))
{
compile_files($v);
}
}
$files = glob($dir. DIRECTORY_SEPARATOR. "*.php");
if (is_array($files) && count($files) > 0)
{
while(list(,$v) = each($files))
{
apc_compile_file($v);
}
}
}
compile_files("/path/to/dir");

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

if (!$config = apc_fetch("config"))
{
require("/path/to/includes/config.php");
apc_store("config", $config);
}
Практический пример иллюстрируется на базе использования Zend Framework и простого запуска утилиты ab, в данном примере результат XML-конфигурации сохраняется в кэше. Ускорение времени разбора позволяет экстремально быстро получить доступ к параметрам конфигурации.
Код:
if (!$conf = apc_fetch("pbs_config"))
{
$conf = new Zend_Config_Xml(PB_PATH_CONF. "/base.xml", "production");
apc_store("pbs_config", $conf);
}
Команда для теста ab -t30 -c5 www.example.com
Результат без кэширования
Concurrency Level: 5
Time taken for tests: 30.33144 seconds
Complete requests: 684
Failed requests: 0
Write errors: 0
Результат с кэшированием
Concurrency Level: 5
Time taken for tests: 30.12173 seconds
Complete requests: 709
Failed requests: 0
Write errors: 0
Как вы видите, мы получили около 3-4% в производительности, закэшировав значения конфигурационного файла. Существует много других мест, которые также можно оптимизировать, нахождение таких мест позволит увеличит количество обработанных запросов.

Файловое кэширование результатов
В некоторых случаях сервер обрабатывает запросы, результатом которых является одинаковый контент. Есть возможность закэшировать подобные вид контента (полностью или его часть)
В данном тексте иллюстрируется пример на основе пакета Pear::Cache_Lite .

Полное кэширование вывода
Полное кэширование довольно тяжело выполнить на большинстве сайтов с постоянно обновляющимися данными из большого количества источников. Все это правда, однако, нет необходимости обновлять данные каждую секунду. Даже 5-10 минутная задержка при экстремально высокой загрузке сайта позволит вам увеличить производительность.
Пример ниже, сохраняет слепок страницы для будущего использования. Такой подход может помочь большому количеству пользователей.
Я не рекомендую использовать данное решение, но если вам нужно что-то быстрое, вы можете его использовать, рано или поздно вы увидите недостатки этого метода.
The Bootstrap Cache Example:

require("/path/to/pear/Cache/Lite/Output.php");
$options = array(
"cacheDir" => "/tmp/",
"lifeTime" => 10
);

if (!($cache->
{
require("/path/to/bootstrap.php");
$cache->end();
}
Пример на основе.htaccess:
.htaccess
php_value auto_prepend_file /path/to/cache_start.php
php_value auto_append_file /path/to/cache_end.php
cache_start.php
require("Cache/Lite/Output.php");

$options = array(
"cacheDir" => "/tmp/",
"lifeTime" => 10
);
$cache = new Cache_Lite_Output($options);
if (($cache->start($_SERVER["REQUEST_URI"])))
exit;

Cache_end.php
$cache->end();


Cache Lite делает большинство тяжелой работы такой как блокирование файла, решение как сохранять контент для различных параметров (в данном примере используется REQUEST URI). Вам также может быть необходимы значения $_POST, $_COOKIE и $_SESSION.

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

require("Cache/Lite.php");
$options = array(
"cacheDir" => "/tmp/",
"lifeTime" => 3600 //1 hour
);

if (!($categories = $cache->get("categories")))
{

$categories = "";
$cache->save($categories, "categories");
}
echo $categories;
Пока это чересчур упрощенный пример, он только показывает гибкость сохранения значения. Вы можете сохранять значения массивов для того чтобы обращаться к ним позже.
Кэширования значения массива
require("Cache/Lite.php");
$options = array(
"cacheDir" => "/tmp/",
"lifeTime" => 3600, //1 hour
"automaticSerialization" => true
);
$cache = new Cache_Lite($options);
if (!($categories = $cache->get("categories")))
{
$rs = mysql_query("SELECT category_id, category_name FROM category");
$categories = array();
while($row = mysql_fetch_assoc($rs))
{
$categories = $row;
}
$cache->store($categories, "categories");
}
var_dump($categories);

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

Кэширование в оперативной памяти
Существует множетсво путей для того чтобы произвести кэширование в памяти: memcached, memory tables в базах данных, RAM disk и другие.
Memcached
С сайта memcache memcached это высокопроизводительная и распределенная кэширующая система, которая увеличивает скорость динамических веб-приложений путём снижения загрузки с базы данных.
О чем это говорит, о том, что можно сохранить данные на одном сервере, к которому будут обращаться другие сервера, это не зависит от вашего веб-сервера (как в случае кеширования промежуточного кода), так как memcached – это демон, который в большинстве случаев используется для кэширования результатов запросов к базам данных.
Пример работы с Memcache:

$post_id = (int) $_GET["post_id"];
$memcached = new Memcache;
$memcached->connect("hostname", 11211);
if (!$row = $memcached->get("post_id_". $post_id))
{
//yes this is safe, we type casted it already ;)
$rs = mysql_query("SELECT * FROM post WHERE post_id = ". $post_id);
if ($rs && mysql_num_rows($rs) > 0)
{
$row = mysql_fetch_assoc($rs);
// cache compressed for 1 hour
$memcached->set("post_id_". $post_id, $row, MEMCACHE_COMPRESSED, time() + 3600);
}
}
var_dump($row);
Это довольно простой пример работы с memcached. Мы сохранили простой элемент в памяти для будущего использования, до которого в будущем получим лёгкий доступ. Я рекомендую использовать данный метод для данных, к которым вы чаще всего будете обращаться.
Пример настройке параметров сессий для работы с Memcache
session.save_handler = memcache
session.save_path = «tcp://hostname:11211»

Как вы видите поддержка сессий довольно таки простая. Если у вас много серверов memcached переменная save_path должна содержать названия серверов через запятую with each server.
Memory Tables баз данных
Memory tables баз данных могут быть использованы для хранения данных сессии. Вы можете создать таблицу такого типа используя MySQL. Создайте ваш собственный обработчик сессий. Это один из способов увеличить производительность сессий.
RAM Disk
В то время как подход использования оперативной памяти как диска не является примером распределенности, данный подход легко может быть приспособлен для увеличения производительности сайта. Запомните информация находящаяся на таком диске исчезает после перезагрузки сервера.
Создание RAM-диска mount --bind -ttmpfs /path/to/site/tmp /path/to/site/tmp
Я бы попытался избегать такого подхода, так как считаю, что риск в этом случае перевешивает выгоду, когда речь идет о большом количестве серверов. В таком случае лучшем выходом является использование memcached.

Надеюсь, что описанное выше было достаточно информативно. Здесь не описан весь потенциал кэширования, например использование кэширования в распределенных базах данных или использование Squid. В будущих статьях я опишу и это…


кэширование в PHP

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

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

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

2. Типичный веб-сервер, например, Apache, использует время модификации файла, чтобы правильно сообщить браузеру состояние кэша запрашиваемой странички. В динамических веб-сайтах сам PHP-скрипт может изменяться только изредка, в то время как отображаемый им контент, возможно располагающийся в базе данных, изменяется часто. Веб-сервер не может узнать об изменениях в базе данных, поэтому он не отправляет дату последней модификации. Если клиент (браузер) не получает никакого признака того, как долго данные являются корректными, он предполагает, что в следующий раз необходимо запросить страничку по новой. Веб-сервер всегда будет отвечать обновленной версией странички, независимо от того, изменились ли данные. Чтобы избежать этого недостатка большинство веб-разработчиков используют мета-теги или HTTP-заголовки, чтобы приказать браузеру никогда не использовать кэшированную версию странички. Однако это отрицает естественную способность веб-браузера кэшировать веб-страницы и обладает некоторыми существенными недостатками. Например, содержание динамической странички может изменяться раз в сутки, поэтому выгода, получаемая от наличия даже 24-часового кэширования странички браузером, очевидна.

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

как я предотвращаю кэширование страницы браузерами?

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

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

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

2. Прокси-серверы, кэширующие веб-страницы, вообще не будет исследовать непосредственно содержимое HTML-документа. Вместо этого они полагаются только на веб-сервер, с которого пришли документы, и протокол HTTP. Иными словами, браузер может считать, что не должен кэшировать страницу, но прокси-сервер между браузером и веб-сервером вероятно не знает этого – и продолжит отправлять клиенту ту же самую страницу.

Лучший подход состоит в том, чтобы использовать непосредственно протокол HTTP с помощью функции PHP header:


header("Pragma: no-cache");
?>

Мы можем пойти на один шаг вперед, воспользовавшись заголовком Cache-Control, совместимым с браузерами, поддерживающими HTTP 1.1:

header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", FALSE);
header("Pragma: no-cache");
?>

Это гарантирует, что никакой веб-браузер или промежуточный прокси-сервер не будет кэшировать страницу, таким образом, посетители всегда получат самую последнюю версию контента. Фактически, первый заголовок должен быть самодостаточным, это лучший способ гарантировать, что страница не кэшируется. Заголовки Cache-Control и Pragma добавлены с целью подстраховки. Хотя они не работают во всех браузерах или прокси, они отловят некоторые случаи, в которых Expires не работает должным образом (например, если дата на компьютере клиента установлена неправильно). Конечно, полный отказ от кэширования обеспечивает нас проблемами, которые мы обсуждали в начале этой главы. Сейчас мы рассмотрим решение этих проблем.

Internet Explorer и кэширование загрузки файлов

Проблемы могут возникать, когда вы имеете дело с кэшированием и загрузкой файлов. Если при обслуживании загрузки файла PHP-скриптом используются такие заголовки, как, например Content-Disposition: attachment, filename=myFile.pdf или Content-Disposition: inline, filename=myFile.pdf, у вас будут проблемы с Internet Explorer’ом, если вы сообщите браузеру не кэшировать страницу.

Internet Explorer оперирует загрузкой довольно необычным образом, выполняя два запроса к веб-сайту. Первый запрос загружает файл и сохраняет его в кэше, пока не будет создан второй запрос (без сохранения отклика). Этот запрос вызывает процесс передачи файла конечному пользователю в соответствии с типом файла (например, запускает Acrobat Reader, если файл является PDF-документом). Это значит, что если вы отправили заголовки, запрещающие браузеру кэшировать страницу, Internet Explorer удалит файл между первым и вторым запросом, в результате чего конечный пользователь ничего не получит. Если файл, который вы отдаете PHP-скриптом, не изменяется, одним из простейших решений будет убрать«запрещающие кэширование заголовки из скрипта.

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

как я могу захватить данные на стороне сервера для кэширования?

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

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

Сейчас мы рассмотрим встроенный механизм кэширования на PHP, использующий буферизацию вывода, который может использоваться вами независимо от способа создания контента (с шаблонами или без шаблонов). Рассмотрим ситуацию, в которой ваш скрипт отображает результат, используя, к примеру, echo или print, чтобы выдать данные непосредственно в браузер. В таком случае вы можете использовать функции управления выводом PHP для хранения данных в буферной памяти, над которой ваш PHP-скрипт имеет контроль.
Вот простой пример:


ob_start();
// Выводим некоторый текст (который сохраняется в буфере);
echo "1. Выводим это в буфер
";
// Получаем содержимое буфера
$buffer = ob_get_contents();
// Останавливаем буферизацию и очищаем буфер вывода
ob_end_clean();
// Выводим некоторый текст обычным образом
echo "2. Нормальный вывод
";
// Вывод содержимого буфера
echo $buffer;
?>

Сам буфер хранит вывод как строку. Так, в приведенном скрипте мы начинаем буферизацию с ob_start и используем echo, чтобы вывести что-либо. Затем мы используем ob_get_contents, чтобы выбрать данные, помещенные в буфер оператором echo, и сохранить их в строке. Функция ob_end_clean останавливает буферизацию вывода и уничтожает его содержимое. Как альтернативу можно использовать ob_end_flush, чтобы вывести содержимое буфера. Вышеописанный скрипт выведет:

2. Нормальный вывод
1. Выводим это в буфер

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

заголовки HTTP и буферизация вывода

Буферизация вывода может помочь решить наиболее общую проблему, связанную с функцией header, не говоря уже о session_start и set_cookie. Обычно, если вы вызываете любую из этих функций после того, как начался вывод страницы, вы получите противное сообщение об ошибке. При включенной буферизации вывода единственным типом вывода, избегающим буферизации, являются HTTP-заголовки. Используя ob_start в самом начале выполнения вашего приложения, вы можете посылать заголовки в любой понравившейся точке программы, не сталкиваясь с обычными ошибками. Затем, как только вы будете уверены, что больше выводить HTTP-заголовки не потребуется, вы можете сразу же вывести содержимое страницы из буфера.

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

использование буферизации вывода для кэширования на стороне сервера

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

// Если существует кэшированная версия…
if (file_exists("./cache/2.cache")) {
// Читаем и выводим файл
readfile("./cache/2.cache");
exit();
}
// Начинаем буферизацию вывода
ob_start();
// Выводим остальной HTML
?>
>http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
>http://www.w3.org/1999/xhtml">

Кэшированная страница

Эта страница кэшируется средствами PHP
Функции>http://www.php.net/outcontrol">Функции управления выводом

// Получаем содержимое буфера
$buffer = ob_get_contents();
// Останов буферирования и вывод буфера
ob_end_flush();
// Сохранение кэш-файла с контентом
$fp = fopen("./cache/2.cache", "w");
fwrite($fp, $buffer);
fclose($fp);
?>

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

блочная буферизация

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

/
* Запись кэш-файла
* @param string contents – содержание буфера
* @param string filename – имя файла, используемое при создании кэш-файла
* @return void
*/
function writeCache($content, $filename) {
$fp = fopen("./cache/" . $filename, "w");
fwrite($fp, $content);
fclose($fp);
}
* Проверка кэш-файлов
* @param string filename – имя проверяемого кэш-файла
* @param int expiry – максимальный «возраст» файла в секундах
* @return mixed содержимое кэша или false
*/
function readCache($filename, $expiry) {
if (file_exists("./cache/" . $filename)) {
if ((time() - $expiry) >filemtime("./cache/" . $filename))
return FALSE;
$cache = file("./cache/" . $filename);
return implode("", $cache);
}
return FALSE;
}
?>

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

В этом примере я использовал процедурный подход. Однако я не советую делать это на практике, поскольку это закончится очень грязным кодом (смотри последующие решения с лучшей альтернативой) и, вероятно, вызовет проблемы с блокировкой файла (например, что случится, когда кто-то обращается к кэшу в момент его обновления?).
Давайте продолжим этот пример. После того, как запущена буферизация вывода, начинается обработка. Сначала скрипт вызывает readCache, чтобы узнать, существует ли файл 3_header.cache - он содержит шапку страницы, то есть заголовок HTML и начало тела. Мы используем функцию date чтобы вывести время, когда страница фактически была сгенерирована, таким образом вы увидите различные кэш-файлы в работе, когда страница будет отображена.

// Начинаем буферизацию вывода
ob_start();
// Обработка шапки
if (!$header = readCache("3_header.cache", 604800)) {
// Вывод шапки
?>
>http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
>http://www.w3.org/1999/xhtml">

Страница, кэшированная поблочно

Время создания шапки:

$header = ob_get_contents();
ob_clean();
writeCache($header,"3_header.cache");
}
?>

Что же случается когда кэш-файл не найден? Выводится некоторый контент и присваивается переменной при помощи ob_get_contents, после чего буфер очищается функцией ob_clean. Это позволяет нам перехватывать вывод по частям и сопоставлять их с индивидуальными кэш-файлами при помощи writeCache. Заголовок страницы теперь хранится как файл, который может быть использован без нашего вмешательства в пересборку страницы. Давайте вернемся на секунду к началу условного оператора. Когда мы вызывали readCache, мы передали ей время жизни кэша в 604800 секунд (одна неделя), readCache использует время модификации кэш-файла, чтобы определить, является ли кэш-файл все еще допустимым.

Для содержимого (тела) страницы мы по-прежнему будем использовать тот же процесс. Однако на сей раз при вызове readCache мы будем использовать время жизни кэша в пять секунд, кэш-файл будет модифицироваться каждый раз, когда он «старше» 5 секунд:

// Обработка тела страницы
if (!$body = readCache("3_body.cache", 5)) {
echo "Время создания тела: " . date("H:i:s") . "
";
$body = ob_get_contents();
ob_clean();
writeCache($body, "3_body.cache");
}
?>

Нижний колонтитул эффективно изменять так же, как заголовок.
Конечный результат выглядит примерно так:
- время создания шапки - 17:10:42;
- время создания тела - 18:07:40;
- время создания нижнего колонтитула - 17:10:42.
Заголовок и нижний колонтитул обновляются еженедельно, в то время как тело модифицируется, когда оно старее 5 секунд.

вложенные буферы

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

Как мне реализовать простую систему кэширования на стороне сервера?
Теперь, когда мы понимаем идеи буферизации вывода, пришло время рассмотреть, как мы можем использовать этот процесс в действии таким образом, чтобы его было легко поддерживать. Чтобы сделать это, мы воспользуемся небольшой помощью от PEAR::CacheLite.

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

Cache_Lite состоит из трех основных классов. Первым является базовый класс Cache_Lite, который отвечает только за создание и чтение кэш-файлов и не занимается буферизацией вывода. Данный класс можно использовать в одиночку в тех случаях, когда нет необходимости использовать буферизацию вывода, как, например, при сохранении результата разбора шаблона PHP-скриптом. Приведенные здесь примеры не используют класс Cache_Lite напрямую и демонстрируют применение остальных двух классов. Cache_Lite_Function используется для вызова функции или метода класса и последующего кэширования результатов работы. Это может оказаться полезным, например, для кэширования результата запроса к MySQL. Класс Cache_Lite_Output использует PHP-функции контроля вывода для перехвата данных, сгенерированных скриптом, и сохранения их в кэш-файлах. Это позволяет выполнять те же задачи, что и предыдущее решение.

настройки Cache_Lite

В текущей версии класса (1.1) доступны следующие настройки:
- cacheDir - каталог, в который будут помещаться файлы кэша. Значение по умолчанию - каталог, где выполняется скрипт;
- caching - эта опция включает или выключает возможности Cache_Lite. Например, если у вас очень много запросов к Cache_Lite, а в процессе отладки вы захотите выключить кэширование, установить в FALSE. Значение по умолчанию - TRUE.
lifetime - параметр содержит в себе заданный по умолчанию отрезок времени жизни кэша (в секундах);
- fileNameProtection - использование MD5-кодирования для генерации имени файла с кэшем. Это позволяет вам использовать в названии файлов кэша и групп любые символы, даже запрещенные файловой системой;
- fileLocking - включает механизмы блокирования файла с кэшем на время записи в него данных;
- writeControl - проверяет, что файл кэша был записан правильно сразу после окончания записи;
- readControl - перед чтением файла с кэшем проверяет его на искажения;
- readControlType - этот параметр определяет тип механизма чтения файлов кэша. Доступные механизмы: цикличная проверка избыточности, MD5-хэш или простая проверка длинны. Обратите внимание, что этот механизм не предназначен для защиты файлов кэша от вызова их напрямую посторонними пользователями. Это всего лишь способ определить - испорчен файл или нет;
- pearErrorMode - включает принятый в PEAR способ возврата ошибок;
- memoryCaching – каждый раз, когда вы вызываете запись кэша в файл, он записывается в массив Cache_Lite. saveMemoryCachingState и
getMemoryCachingState используются для доступа к кэшу, сохраненному в памяти между запросами. Преимущество подобного метода состоит в том, что содержимое кэша может быть сохранено в едином файле, что сокращает число циклов чтения/записи на диск. Кэш восстанавливается прямо в массив, к которому ваш скрипт имеет доступ;
memoryCachingLimit - парметр определяет предел количества файлов кэша, которые могут быть сохранены в массиве в памяти.

очистка кэша

Cahce_Lite содержит в себе удачный механизм определения времени жизни файлов кэша, что создает хорошую основу для сохранности ваших файлов и их своевременного обновления. Однако бывают моменты, когда вам требуется немедленное обновление файла кэша. Для таких случаев существует методы remove() и clean(). Метод remove() предназначен для удаления конкретного файла кэша. ему требуется ID кэша и название группы, в которую входит файл. Следующий пример удалит файл с кэшем тела (body) из предыдущего примера:

$cache->remove("body", "Dynamic");

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

$cache->clean("Static");

Методы remove() и clean() нужно, очевидно, вызывать в ответ на события, в пределах приложения. Например, если у вас есть форум, наверняка следует удалить файл кэша, если какой-либо пользователь отправит новое сообщение. Несмотря на то, что это решение выглядит красиво, оно может повлечь за собой изменение кода. Если у вас есть главный скрипт, который подключается к каждой странице приложения, которую может просмотреть посетитель, вы можете просто наблюдать за поступающими событиями, например, за переменной $_GET["newPost"], удаляя требуемые файлы кэша. Это позволит вам создать централизованный механизм управления кэшем. Вы могли бы даже включить этот код в php.ini.

кэширование вызовов функций

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

PEAR Web installer использует в своей работе Cache_Lite для кэширования XML-RPC запросов, передаваемых PEAR Web серверу.
Вот код, который получает данные от удаленного сервера:

$countries = $stationInfo->listCountries();

$country = $stationInfo->searchByCountry($_GET["country"]);

В обоих случаях эти вызовы соответствуют запросу данных по сети. Используя Cache_Lite, мы могли бы кэшировать данные, возвращаемые сервисом, и могли бы использовать их многократно. Это позволило бы избежать дополнительных ненужных сетевых соединений и значительно увеличило бы скорость выполнения. Обратите внимание, что здесь мы рассматриваем только код, касающийся нашей темы. В начале мы подключаем Cache_Lite_Function:

// Include PEAR::Cache_Lite_Function
require_once "Cache/Lite/Function.php";

// Задаем параметры для for Cache_Lite_Function
// ВНИМАНИЕ: fileNameProtection = TRUE!
$options = array(
"cacheDir" =>"./cache/",
"fileNameProtection" =>TRUE,
"writeControl" =>TRUE,
"readControl" =>TRUE,
"readControlType" =>"strlen",
"defaultGroup" =>"SOAP"
);
// Создаем объект класса Cache_Lite_Function
$cache = new Cache_Lite_Function($options);

Важно, что параметр fileNameProtection установлен в TRUE. Это значение принято по умолчанию, однако я специально выставил его вручную, чтобы подчеркнуть значимость. Если этот параметр установить в FALSE, получится недопустимое имя файла, так что кэширования не будет.
Далее, мы делаем запрос к нашему клиенту SOAP:

$countries = $cache->call("stationInfo->listCountries");
$country = $cache->call("stationInfo->searchByCountry",
$_GET["country"]);

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

PEAR::Cache

Вообще, Cach_Lite обеспечивает единую, простую в использовании систему, чтобы решить любые вопросы, связанные с кэшированием. Поскольку следующий уровень - это сайты с особенно большим трафиком, вам стоит разобраться с PEAR::Cache – «старшим братом» Cache_Lite. Он также предусматривает расширение возможностей кэширования, например, кэширование в общей памяти, как альтернативу кэширование в файл, или помощь Msession PHP extension, храня данные в сбалансированной сессии, которая является особенно полезной в сбалансированных веб-серверах. Cache_Lite, однако, предлагает более чем достаточные возможности, и отвечает потребностям большинства сайтов.

как управлять кэшированием на стороне клиента средствами PHP?

После того, как мы рассмотрели варианты отмены кэширования на стороне клиента, пришло время посмотреть на механизм, который позволит нам контролировать кэш на стороне клиента средствами PHP. Этот подход будет работать только если вы используете PHP в связке с сервером Apache, поскольку мы будем использовать функцию getallheaders, чтобы получить заголовки, передаваемые браузером. Эта функция работает только в Apache. Если вы используете PHP 4.3.0 с Apache, работа с HTTP-заголовками возможна с помощью функций apache_request_headers и apache_response_headers. Функция getallheaders стала псевдонимом для новой функции apache_request_headers.

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

проверка HTTP-заголовков в вашем браузере

Простым, но очень удобным инструментом для проверки заголовков запросов и откликов является LiveHttpHeaders – аддон к браузеру Mozilla. Необходимо точно знать, какие заголовки посылает ваш скрипт, особенно когда вы имеете дело с заголовками кэширования HTTP.
Для простоты мы рассмотрим только заголовки кэширования HTTP 1.0, а именно Expires, Last-Modified и If-Modified-Since, а также статус-код HTTP 304 (Not Modified).

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

истечение срока жизни страницы

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


function setExpires($expires){
header("Expires: " . gmdate("D, d M Y H:i:s", time() + $expires) . "GMT");
}
echo "Эта страница самоуничтожится через 10 секунд
";
echo "Сейчас " . gmdate("H:i:s") . " GMT
";
echo "
";
?>

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

даты и время в HTTP

Даты в HTTP всегда вычисляются относительного меридиана времени Гринвича (GMT). Функция PHP gmdate - точно такая же функция, как date, за исключением того, что она автоматически компенсирует время по Гринвичу, основанное на системных часах и настройках региона вашего сервера. Когда браузер сталкивается с заголовком Expires, он кэширует страницу. Все последующие запросы страницы, сделанные до указанного времени истечения срока жизни, используют версию страницы из кэша, никаких запросов к веб-серверу при этом не происходит.
Заголовок Expires в принципе прост в реализации, но в большинстве случаев, если вы не высокоорганизованный человек, вы не можете знать точно, когда данная страница вашего сайта будет обновлена. Поскольку браузер войдет в контакт с сервером только после того, как страница устареет, нет ни одного способа сообщить браузеру, что страница, находящаяся в его кэше, устарела.

время изменения страницы

Более практично использовать заголовки Last-Modified и If-Modified-Since, доступные в HTTP 1.0. При использовании этого метода вы должны отправлять заголовок Last-Modified в ответ на каждый запрос к вашему PHP-скрипту. При следующем запросе страницы браузером, он отправит заголовок If-Modified-Since, содержащий время, по которому ваш скрипт может определить, обновлялась ли страница со времени последнего запроса. Если это не так, ваш скрипт посылает код статуса HTTP 304, чтобы указать, что страница не изменялась, не выводя при этом содержимого страницы. Если вы объедините подход времени последнего изменения со значением времени, являющимся уже доступным в вашем приложении (например, время самой последней новостной статьи), вы сможете воспользоваться преимуществами кэша веб-браузера и разгрузите канал передачи данных, по возможности сэкономив информационный трафик с вашего сайта и улучшив его производительность.

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

Harry Fuecks, перевод Муллина Сергея (SiMM) и Кузьмы Феськова.

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

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

  • Когда сервером получен запрос динамической web-странички, производится некоторая промежуточная обработка, например синтаксический анализ (парсинг) скрипта движком PHP , которая должна быть завершена. Благодаря этому получаем задержку перед тем, как web-сервер начнёт отправку вывода в браузер. Для простого PHP -скрипта это не существенно, но для более сложного приложения движок PHP может выполнить много действий прежде чем страница будет готова для отправки. Эти дополнительные действия приводят к заметной задержке между запросами пользователей и реальным отображением страниц в их браузерах.
  • Типичный web-сервер, например Apache, использует время модификации файла чтобы правильно сообщить web-браузеру состояние кэша запрашиваемой странички. Для динамических web-страниц, фактически PHP -скрипт может изменяться только изредка, в то время как отображаемый им контент, возможно располагающийся в базе данных, изменяется часто. Web-сервер не имеет возможности знать о наличии изменений в базе данных, тем не менее он не отправляет дату последней модификации. Если клиент (браузер) не получает никакого признака того, как долго данные являются корректными, он предполагает, что в следующий раз необходимо запросить страничку по новой. Web-сервер всегда будет отвечать обновлённой версией странички, независимо от того, изменились ли данные. Чтобы избежать этого недостатка большинство web-разработчиков используют мета-тэги или HTTP -заголовки, чтобы сообщить браузеру никогда не использовать кэшированную версию странички. Однако это отрицает естественную способность web-браузера кэшировать web-страницы и обладает некоторыми существенными недостатками. Например, содержание динамической странички может изменяться раз в сутки, поэтому выгода, получаемая от наличия даже 24-часового кэширования странички браузером, очевидна.
  • Обычно для маленьких PHP-приложений вполне можно игнорировать существование этих проблем, однако с увеличением сложности и повышением трафика Вашего сайта Вы можете столкнуться с проблемами. Тем не менее, обе эти проблемы могут быть решены, первая путём кэширования на стороне сервера, вторая путём управления кэшированием на стороне клиента из вашего приложения. Подход, который вы будете использовать для решения проблем, будет зависеть от вашей области применения, но в этой главе мы увидим, как вы можете решить обе проблемы используя PHP и некоторые классы библиотеки PEAR .

    Как я предотвращаю кэширование страницы браузерами?

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

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

    Это хорошо звучит, но есть две проблемы, связанные с использованием мета-тэгов:

  • Если тэг не существовал когда страница была запрошена браузером впервые, но появляется позже (например, вы модифицировали включаемый файл pageheader.php который является шапкой каждой web-страницы), браузер останется в блаженном неведении и воспользуется свей кэшированной копей оригинала.
  • Прокси-серверы, кэширующие web-страницы, как например общий ISP , вообще не будет исследовать непосредственно содержимое HTML -документа. Вместо этого они полагаются только на web-сервер, с которого пришли документы, и протокол HTTP . Иными словами, web-браузер может считать, что не должен кэшировать страницу, но прокси-сервер между браузером и вашим web-сервером вероятно не знает этого – и продолжит отправлять клиенту ту же самую, уже устаревшую, страницу.
  • Лучший подход состоит в том, чтобы использовать непосредственно протокол HTTP с помощью функции PHP header() , эквивалентно приведённым выше двум мета-тэгам:

    Мы можем пойти на один шаг вперёд, воспользовавшись заголовком Cache-Control совместимым с браузерами, поддерживающими HTTP 1.1:

    Header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); header("Cache-Control: no-store, no-cache, must-revalidate"); header("Cache-Control: post-check=0, pre-check=0", FALSE); header("Pragma: no-cache");

    Это гарантирует, что никакой web-браузер или промежуточный прокси-сервер не будет кэшировать страницу, таким образом посетители всегда получат самую последнюю версию контента. Фактически, первый заголовок должен быть самодостаточным, это лучший способ гарантировать, что страница не кэшируется. Заголовки Cache-Control и Pragma добавлены с целью «подстраховаться». Хотя они не работают во всех браузерах или прокси, они отловят некоторые случаи, в которых Expires не работает должным образом (например, если дата на компьютере клиента установлена неправильно).

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

    Internet Explorer и кэширование загрузки файлов

    Если при обслуживании загрузки файла PHP -скриптом используются такие заголовки, как например Content-Disposition: attachment, filename=myFile.pdf или Content-Disposition: inline, filename=myFile.pdf у вас будут проблемы с Internet Explorer ’ом, если вы сообщите браузеру не кэшировать страницу.

    Internet Explorer оперирует загрузкой довольно необычным образом, выполняя два запроса к web-сайту. Первый запрос загружает файл и сохраняет его в кэше, пока не будет создан второй запрос (без сохранения отклика). Этот запрос вызывает процесс передачи файла конечному пользователю в соответствии с типом файла (например, запускает Acrobat Reader , если файл является PDF -документом). Это значит, что если вы отправили заголовки, запрещающие браузеру кэшировать страницу, Internet Explorer удалит файл между первым и вторым запросом, в результате чего конечный пользователь ничего не получит. Если файл, который вы отдаёте PHP -скриптом, не изменяется, одним из простейших решений будет убрать «запрещающие кэширование» заголовки из скрипта.

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

    Как я могу захватить данные на стороне сервера для кэширования?

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

    Несколько слов о кэшировании при помощи шаблонов Как мне управлять кэшированием на стороне клиента средствами PHP?

    Пришло время посмотреть на механизм, который позволит нам контролировать кеш на стороне клиента средствами PHP . Этот подход будет работать только если вы используете PHP в связке с сервером Apache , поскольку мы будем использовать функцию getallheaders() , чтобы получить заголовки, передаваемые браузером. Эта функция работает только в Apache .

    Новые имена функций

    Если вы используете PHP 4.3.0 с Apache , HTTP-заголовки доступны функцией apache_request_headers() и apache_response_headers() . Функция getallheaders() стала псевдонимом для новой функции apache_request_headers() .

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

    Проверка HTTP-заголовков в вашем браузере

    Простым но очень удобным инструментом для проверки заголовков запросов и откликов является LiveHttpHeaders – аддон к браузеру Mozilla . Необходимо точно знать, какие заголовки посылает ваш скрипт, особенно когда вы имеете дело с заголовками кэширования HTTP .

    Для простоты мы рассмотрим только заголовки кэширования HTTP 1.0, а именно Expires , Last-Modified и If-Modified-Since , а также статус-код HTTP 304 (Not Modified) .

    Другие заголовки, доступные с HTTP 1.1, например Cache-Control и ETag , предназначены для обеспечения расширенного механизма, который может использоваться совместно с состоянием web-сессии, иными словами, версия данной страницы, отображаемой неавторизованному посетителю, может значительно отличаться от отображаемой авторизованному пользователю. Заголовки HTTP 1.1 изначально добавлялись для того, чтобы позволить кэшировать такие страницы.

    Истечение срока жизни страницы

    Самым простым в использовании заголовком является заголовок Expire , который устанавливает дату (возможно, будущую), когда страница устареет. До этого момента web-браузеру разрешается использовать кэшированную версию страницы.

    Пример 7. 6.php

    Функция setExpires отправляет заголовок HTTP Expires с будущим временем, заданном в секундах. Вышеприведённый пример показывает текущее время по Гринвичу и выводит ссылку, которая вам позволяет перейти на страницу вновь. Используя кнопку Refresh вашего браузера, вы можете сообщить браузеру о желании обновить кэш. Используя ссылку, вы увидите, что время изменяется только раз в 10 секунд.

    Даты и время в HTTP

    Даты в HTTP всегда вычисляются относительного меридиана времени Гринвича (GMT). Функция PHP gmdate() точно такая же функция, как date() , за исключением того, что она автоматически компенсирует время по Гринвичу, основанное на системных часах и настройках региона вашего сервера.

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

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

    Время изменения страницы

    Более практично использовать заголовки Last-Modified и If-Modified-Since , доступные в HTTP 1.0. Технически он известно как выполнение условного GET-запроса, вы возвращаете любой контент, основываясь на условии пришедшего заголовка запроса If-Modified-Since .

    При использовании этого метода вы должны отправлять заголовок Last-Modified каждый раз, когда обращаются к вашему PHP-скрипту. При следующем запросе страницы браузером, он отправит заголовок If-Modified-Since , содержащий время, по которому ваш скрипт может определить, обновлялась ли страница со времени последнего запроса. Если это не так, ваш скрипт посылает код статуса HTTP 304 , чтобы указать, что страница не изменялась, не выводя при этом содержимого страницы.

    Устанавливаем время модификации кэш-файла этой строкой: $lastModified = filemtime($cache_file);

    Затем, используя время модификации кэш-файла, мы посылаем заголовок Last-Modified . Нам нужно посылать её для каждой предоставляемой страницы, чтобы вынудить браузер посылать нам заголовок If-Modified-Since с каждым запросом.

    // Выдаём заголовок HTTP Last-Modified header("Last-Modified: " . gmdate("D, d M Y H:i:s", $lastModified) . " GMT");

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

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

    Кэширование ваших страниц в 5 шаговОригинал: Posted in PHP / JavaScript by ibzi on the February 17th, 2007
    Перевод: Кузьма Феськов ([email protected] ,http://kuzma.russofile.ru)

    Кэширование ваших страниц может оказаться красивым и полезным механизмом, особенно, если они генерируются средствами PHP и делают множество SQL запросов. Как только вы примените кэширование, ваш сервер тут же снизит нагрузку и перестанет съедать много памяти на генерацию страниц - он просто будет загружать их из КЭШа. Я покажу вам, как PHP может кэшировать страницы и, в дальнейшем, вы сможете тратить на это минут 5.


    Расмотрим технологию кэширования пошагам:

  • В домашней директории создаем файлы .htaccess , start_cache.php , end_cache.php , а также папку с названием cache_files .
  • Папке cache_files необходимо проставить атрибуты 777 .
  • Внутри .htaccess файла пропишите следующие строки: php_value auto_prepend_file /home/username/public_html/start_cache.php php_value auto_append_file /home/username/public_html/end_cache.php Строку /home/username/public_html/ необходимо заменить на путь к вашей домашней директории.
  • В скрипт start_cache.php помещаем следующий код: Не забывайте исправлять путь /home/username/public_html/ на путь к вашей домашней директории.
  • А следующий код поместите в скрипт end_cache.php :
  • Все ваши страницы будут кэшироваться на 3600 секунд = 1 час. Этот параметр вы легко можете поменять в скрипте start_cache.php . Кэш страниц будет сохранен в папке cache_files .

    Совершенно очевидно, что в данном случае атрибуты 777 являются определенным нарушением безопасности. В связи с чем, рекомендую вынести папку cahce_files за пределы public_html , например, поместить ее на один уровень выше. Это закроет доступ к находящимся в ней файлам пользователей вашего сайта, но никак не повлияет на работоспособность системы.

    Также, у данного метода есть еще один серьезный недостаток: автор статьи складывает весь кэш в одну папку, что, при достаточном количестве страниц на вашем сайте, вызовет проблему, например, в системах Unix наблюдается достаточное замедление работоспособности при наличие в папке более чем 1000 файлов. В связи с чем, в алгоритм необходимо внести ряд изменений и раскладывать файлы по отдельным подпапкам внутри папки cache_files . Например, используя для этого первые 3-4 символа md5 КЭШа.

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

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

    Регенерация содержания на лету

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

    Это делается следующим набором директив:

    RewriteCond %{REQUEST_FILENAME} !-s RewriteRule ^page\.html$ page.php

    Здесь, запрос к page.html приводит к внутреннему запуску соответствующего page.php, если page.html все-ещё отсутствует или имеет нулевой размер. Фокус здесь в том что page.php это обычный PHP скрипт который в дополнение к собственному выводу, записывает свой вывод в файл page.html. Запустив это один раз, сервер передает данные page.html. Когда вебмастер хочет обновить содержание, он просто удаляет page.html (обычно с помощью cronjob).

    Проблема с кэшированием страниц у Internet Explorer.

    У IE при работе с заголовком "Vary" встречается одна неприятная ошибочка, связанная с кэшированием страниц. Проблема решается добавлением в.htaccess следующих строк:

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

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

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

    Необходимо понимать, что работу с данными можно производить как на стороне клиента, так и на сервере. Притом, серверная обработка данных централизована и имеет ряд несомненных преимуществ (особенно для службы поддержки).


    Существует несколько видов кэширования, предлагаем рассмотреть каждый вид, его особенности и рекомендации по применению:1. Браузерное кэширование или клиентское кэширование Представляет собой составление для браузера команды использовать имеющуюся кэшированную копию. Работа такого кэширования основана на том, что при повторном посещении, браузеру отдаётся заголовок 304 Not Modified, а сама страница или картинка загружаются из локального пользовательского кэша. Получается, что вы экономите на трафике между браузером посетителя и хостингом сайта. Соответственно, страница вашего сайта начинает загружаться быстрее. 1.1 Кэширование файлов и картинок Браузерное кэширование как нельзя лучше подходит для сайтов, содержащих большое количество изображений: картинка не скачивается каждый раз при открытии сайта, а просто загружается через кэш браузера.


    Это первый уровень кэширования, который состоит в отдаче заголовка «expired» и заголовка «304 Not Modified» . Наиболее эффективным считается кэширование на 2 недели.

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

    1.2 Кэширование https Специальные заголовки вида strict-security. Позволяет браузеру всегда обращаться по https к выбранному домену. Сохраняет это состояние довольно жёстко и, в случае отмены этого вида кэша, браузер ещё довольно долго будет пытаться загрузить страницу по https, при этом игнорируя текущие заголовки.1.3 Кэширование центра сертификации Так называемый, stamp центра сертификации.

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

    1.4 Кэширование страниц Когда страница уже сгенерирована, нужно постоянно отслеживать ее актуальность. Для этого вы должны использовать серверный кэш с отслеживанием времени изменения отдельных частей страницы (если страница строится из множества динамически генерируемых блоков). При таком подходе в каждом ответе от сервера установлены специальные заголовки, обозначающие время изменения страницы, которые затем отправляются браузером пользователя при повторном обращении к странице сайта. Сервер при получении таких заголовков можем проанализировать текущее состояние страницы (возможно, даже отрисовать её), но вместо содержимого страницы отдать заголовок «304 Not Modified» , что для пользовательского браузера будет означать, что можно показать страницу из своего (браузера пользователя) кэша.

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

    Как правило, кэш подразделяется по типу пользователей:

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

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

    2. Серверное кэширование Под серверным кэшированием понимаются все виды кэширования, при котором данные хранятся на серверной стороне. Эти данные не доступны клиентским браузерам. Кэш создаётся и хранится по принципу «один ко многим» (многие, в данном случае, - это клиентские устройства).

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

    Пожалуй, любой когда-либо мечтал о сайте, работающем со скоростью «ping» или быстрее.
    Но и у этого типа кэша есть свои минусы: например, невозможность кэшировать страницы для авторизованного пользователя, либо пользователя, содержимое страницы которого зависит от текущих переменных пользователя.

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

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

    2.2 Кэширование результатов компиляции php-файлов Различают как чистую компиляцию кода, так и его оптимизацию во время компилирования (подмена скриптов). Наиболее яркие примеры:

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

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

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

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

    2.4 Кэширование php на основе неразделяемых ресурсов Лучше всего подходит при стандартизации запросов, получении данных из общих ресурсов, наличии внутренних переменных, к которым php-ресурсы обращаются несколько раз при генерации страницы.2.5 Кэширование php на основе общих ресурсов Такое кэширование применяйте для хранения сериализированных данных. Например: конфигурационного файла, состояния таблиц, списков файловой системы.2.6 Кэширование mysql на основе query cache Это довольно известная и наиболее освещённая тема. Тем не менее, хотелось бы рассмотреть специфику работы с timestamp и то, как можно избежать постоянного сброса query cache.

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

    WHERE show_tsExpires< содержит дату, прошедшую, по отношению к текущей, то при следующем обращении к ресурсу браузер будет вынужден снова обратиться к серверу. Это произойдет вследствие того, что либо документ не будет занесен в кэш — как уже устаревший, либо при обращении к кэшу браузер определит, что документ уже устарел. Следующий листинг на PHP демонстрирует использование заголовка Expires:

    Поле заголовка Last-Modified

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

    * запрашивает с сервера дату последнего обновления ресурса
    * сравнивает полученную дату и дату ресурса в локальном кэше
    * если ресурс на сервере новее ресурса в кэше — запрашивается ресурс с сервера

    Если ресурс, расположенный на сервере, содержит в данном поле текущую дату, то браузер будет каждый раз запрашивать ресурс с сервера, а не из локального кэша. Следующий листинг демонстрирует использование поля заголовка Last-Modified:

    header ("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");

    Поля заголовка Cache-Control и Pragma

    И, наконец, поля заголовка, непосредственно отвечающие за кэширование ресурса. Поле Было определено в стандарте Rfc 1945, описывающим протокол HTTP 1.0. Данное поле считается устаревшим, но в некоторых случаях приходится использовать именно его. В частности некоторые proxy-сервера неправильно обрабатывают запросы к постоянно изменяющимся ресурсам, если вместе с ресурсом не передается данное поле заголовка.

    Второе поле определено в стандарте Rfc 2068, который описывает протокол HTTP 1.1. Данное поле заголовка позволяет запретить кэширование, и каждый раз запрашивать ресурс с сервера. Следующий листинг демонстрирует использование полей заголовка Cache-Control и Pragma для запрета кэширования:

    header("Cache-Control: no-cache, must-revalidate"); header("Pragma: no-cache");

    Хорошо Плохо



    
    Top