Тарифы Услуги Сим-карты

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.

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

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

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

Кэширования байткода в PHP

Начиная с PHP 5.5 в интерпретатор языка была добавлена поддержка кэширования байткода из ZendFramework. В новых версиях этот кэш позволяет очень сильно увеличить производительность вашего ресурса, например, есть сведения, что на PHP 7 Wordpres и другие движки работают чуть ли не в два раза быстрее. Перед тем как настраивать кєширование opcode php, нужно установить его пакет:

sudo apt install php-opcache

Или для Red Hat дистрибутивов:

sudo yum install php-opcache

Затем, чтобы включить кэширование нужно добавить несколько строк в php.ini, можно также создать отдельный файл в /etc/php/conf.d/

vi /etc/php.d/opcache.ini

zend_extension=opcache.so;
opcache.error_log=/var/log/php-fpm/opcache-error.log
opcache.enable=1;
opcache.memory_consumption=256;
opcache.interned_strings_buffer=8;
opcache.max_accelerated_files=4000;
opcache.revalidate_freq=180;
opcache.fast_shutdown=0;
opcache.enable_cli=0;
opcache.revalidate_path=0;
opcache.validate_timestamps=2;
opcache.max_file_size=0;
opcache.file_cache= /var/www/сайт/opcache;

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

  • opcache.error_log - указывает файл для записи лога ошибок, будет полезно при отладке;
  • opcache.log_verbosity_level - указывает насколько подробным должен быть лог файл, значение от 1 до 4;
  • opcache.enable - включает кэширование;
  • opcache.enable_cli - включает кэширование страниц php для консольной версии;
  • opcache.memory_consumption - количество оперативной памяти для хранения кэша;
  • opcache.max_accelerated_files - число скриптов/файлов, которые нужно кэшировать;
  • opcache.validate_timestamps - проверять время изменения данных в файле скрипта;
  • opcache.revalidate_freq - частота проверки для предыдущего параметра;
  • opcache.revalidate_path - установите в 0 чтобы выполнять проверку при include только первый раз;
  • opcache.enable_file_override - кэширует запросы к атрибутам файлов, например, существование и т д;
  • opcache.blacklist_filename - список файлов, которые не нужно кэшировать;
  • opcache.max_file_size - максимальный размер файла скрипта для кэширования, 0 - не ограниченно;
  • opcache.interned_strings_buffer - допустимое количество строк в буфере;
  • opcache.fast_shutdown - использовать быстрый способ освобождения памяти.

После сохранения всех настроек вам останется только перезапустить php или ваш веб-сервер:

systemctl restart php-fpm

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

chmod 777 /var/www/сайт/opcode.php

http://localhost/opcache.php

Здесь можно видеть подробную статистику по кєширвоанию, настройки и количество занятой памяти.

Хранение сессий в memcached

По умолчанию php хранит сессии в файловой системе, в некоторых случаях, вы можете достаточно сильно ускорить работу php, если перенесете хранение сессий из файлов в оперативную память, например, memcached. Сначала нужно установить memcached и php библиотеку для работы с ней:

sudo apt install memcached php-memcached

Или для систем на базе Red Hat:

sudo yum install memcached php-memcached

Сначала нам нужно настроить memcached, откройте файл /etc/sysconfig/memcached и найдите строку CACHESIZE, здесь нужно указать объем оперативной памяти, которая выделяется под кэш:

vi /etc/sysconfig/memcached

session.save_handler = memcache
session.save_path = "tcp://localhost:11211"

Осталось перезапустить ваш php интерпретатор:

systemctl restart php-fpm

Если вы хотите проверить все ли правильно кэшируется и есть ли вообще что-либо в кэше, можно использовать phpmemcacheadmin .

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

Я не советую использовать кэширование fastgci для сайтов WordPress, потому что там есть специальные плагины, которые могут точно контролировать кэш, очищать его когда нужно и вовремя обновлять. Но во всех остальных случаях кэш fastcgi может очень сильно ускорить работу сайта. Настраивается он в конфиге, где вы включаете fastgci, например, в конфигурации веб-сервера Nginx. Минимально для настройки кэша fastgci достаточно добавить в блок server такие строки:

vi /etc/nginx/vhosts/site.conf

fastcgi_cache_path /var/nginx/cache levels=1:2 keys_zone=MYAPP:100m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";

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

Теперь нужно настроить блок обработки php:

location ~ \.php$ {
fastcgi_pass unix:/var/run/php7-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_cache MYAPP;
fastcgi_cache_valid 200 60m;
}

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

ls -lR /var/nginx/cache/

С помощью таких методов ваши страницы будут загружаться намного быстрее. Если вам понадобится отключить кєширование php для отдельных страниц, то сначала создаем переменную no_cache со значением 0:

set $no_cache 0;

Затем проверяем нужные параметры, и если соответствует, то устанавливаем значение в 1:

if ($request_method = POST)
{
set $no_cache 1;
}

И на завершение передаем значение этой переменной таким директивам, это отключит кэширование когда не нужно:

fastcgi_cache_bypass $no_cache;
fastcgi_no_cache $no_cache;

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

Шаг первый. Создаем файл top-cache.php

Нам нужно создать два файла. Первый: создаем файл с именем top-cache.php и копируем в него следующий код:

\n"; include($cachefile); exit; } ob_start(); // Запуск буфера вывода?>

Что происходит в данном коде? Первые 5 строк создают имя файла кеша в соответствии с текущем PHP файлом. Например, если мы используем файл с именем list.php , файл кеша будет иметь вид cached-list.html .

Строка 6 создает переменную $cachetime , которая определяет время жизни кеша.

Строки с 9 по 13 определяют условное выражение, которое служит для проверки наличия файла с именем, определенным в переменной $cachefile . Если файл существует, вставляется комментарий и файл, определенный в переменной $cachefile . Затем выражение exit прерывает выполнение скрипта и файл отправляется браузеру клиента. То есть, если найден статичный файл, то PHP код не будет выполняться сервером.

Строка 14 создает буфер, если файл, определенный переменной $cachefile не найден.

Шаг второй. Создаем файл bottom-cache.php

Теперь создаем второй файл PHP с именем bottom-cache.php и копируем в него следующий код:

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

Шаг три. Включаем файлы кеширования в код страницы

Теперь у нас есть два необходимых файла. Просто включаем их в страницу PHP, которую нужно кешировать. Файл top-cache.php нужно включить в начало страницы, а файл bottom-cache.php - в конце:

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

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

Сами кэши бываю двух видов — локальные и общие. Локальный это кеш, хранимый непосредственно на диске у клиента, создаваемый и управляемый его браузером. Общий — кэш прокси-сервера организации или провайдера и может состоять из одного или нескольких прокси-серверов. Локальный кеш присутствует, наверное в каждом браузере, общими пользуется значительная часть людей использующих Internet. И если малую часть сайтов сейчас оценивают по расходу трафика, то скорость загрузки — важный критерий, который должен учитываться при разработке Вашего web-проекта.
Для динамических страниц, создаваемых в результате работы PHP-программы, казалось бы, кэширование вредно. Содержание страницы формируются по запросу пользователя на основе какого-либо источника данных. Однако, кэширование может быть полезным. Управляя им Вы можете сделать работу с Вашим сервером комфортнее для пользователя, разрешая загрузку из кэш определенных страниц, предотвращая тем самым их повторную выгрузку с Вашего сервера и экономя пользователю время и трафик.

Кэшировать или нет?

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

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

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

Общие принципы сохранения страниц в кэш

PHP-программа может управлять кэшированием результатов ее работы формируя дополнительные поля в заголовке HTTP ответа вызовом функции Header().
Несколько общих утверждений характерных не только для PHP-программ:

  • Страницы передаваемые по POST никогда не сохраняются в кэш.
  • Страницы запрашиваемые по GET и содержащие параметры (в URL присутствует ‘?’) не сохраняются в кэш, если не указано обратное.

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

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

Запрет кэширования документов, кэшируемых по умолчанию

Эта задача возникает для PHP-скриптов вызываемых без параметров или являющимися индексами директорий, однако формирующих данные персонально под пользователя (например на основе cookies или user agent) или работающих на основе быстро изменяющихся данных. По спецификации HTTP/1.1 мы можем управлять следующими полями:

Expires
Задает дату истечения срока годности документа. Задание ее в прошлом определяет запрет кэш для данной страницы.

Cache-control: no-cache
Управление кэш. Значение no-cache определяет запрет кэш данной страницы. Для версии протокола HTTP/1.0 действует «Pragma: no-cache».

Last-Modified
Дата послднего изменения содержимого. Поле актуально только для статических страниц. Apache заменяет это поле значением поля Date для динамически генерируемых страниц, в том числе для страниц содержащих SSI.

На сайте www.php.net дается следующий код для запрета кеширования.

header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Date in the past
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); // always modified
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Pragma: no-cache"); // HTTP/1.0

Однако, данный заголовок избыточен. В большинстве случаев достаточно:

Чтобы пометить документ как «уже устаревший» следует установить Expires равным полю Date .
header("Expires: " . gmdate("D, d M Y H:i:s") . " GMT");

Ну и не следует забывать, что формы, запрошенные по POST также не подлежат кэшированию.

Кэширование документов, не подлежащих кэшированию по умолчанию

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

Статья по теме: Поисковое продвижение интернет-магазина в Яндексе и Google: чек-лист аудита факторов ранжирования


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

header("Cache-control: private");

Кэширование до истечения корректности

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

Кэширование с прогнозируемым обновлением

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

$dt_tmp=getdate(date("U"));
header("Expires: " . gmdate("D, d M Y H:i:s", date("U")-(86400*($dt_tmp["wday"]-8))) . " GMT");
header("Cache-control: public");

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

Другой подход, применяемый при более оперативном обновлении информации и одновременной высокой посещаемости сервера (иначе кэширование не будет эффективным) состоит в использовании заголовка Cache-control: max-age=секунды, определяющий время по истечении которого документ считается устаревшим и имеющий больший приоритет при вычислении «свежести» документа.

В старые добрые времена, когда создание 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.

Обратите внимание, что в главе «Кэширование» обсуждаются только решения, осуществляемые при помощи PHP. Они не должны быть перепутаны с решениями кэширования скриптов, работающими на основе оптимизации и кэширования откомпилированных PHP-скриптов. В эту группу можно включить Zend Accelerator, ionCube PHP Accelerator, Turck MMCache/eaccelerator, APC.

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

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

"Mon, 26 Jul 1997 05:00:00 GMT" />

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

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

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

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

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

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" ) ;

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

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

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

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

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

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

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

Несколько слов о кэшировании при помощи шаблонов

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

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

Вот простой пример:

Пример 5.1. 1.php

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

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

Вышеописанный скрипт выведет:

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

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

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

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

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

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

Пример 5.2. 2.php

// Если существует кэшированная версия… if (file_exists ("./cache/2.cache" ) ) { // Читаем и выводим файл readfile ("./cache/2.cache" ) ; exit () ; } // Начинаем буферизацию вывода ob_start () ; // Выводим остальной HTML ?> Кэшированная страница "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 для отображения страницы пользователю.

Файл 2.cache содержит точную копию HTML, которую предоставляет скрипт:

"http://www.w3.org/1999/xhtml" > > > Кэшированная страница> "text/html; charset=windows-1251" /> > > Эта страница кэшируется средствами PHP "http://www.php.net/outcontrol" > Функции управления выводом> > >

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

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

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

Вот пример, демонстрирующий этот принцип:

Пример 5.3. 3.php (начало)

/** * Запись кэш-файла * @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 PHP чтобы вывести время, когда страница фактически была сгенерирована, таким образом вы увидите различные кэш-файлы в работе, когда страница будет отображена.

Пример 5.4. 3.php (продолжение)

// Начинаем буферизацию вывода ob_start () ; // Обработка шапки if (!$header = readCache("3_header.cache" , 604800 ) ) { // Вывод шапки ?> "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" > "http://www.w3.org/1999/xhtml" > Страница, кэшированная поблочно "text/html; charset=windows-1251" /> Время создания шапки:

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

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

Пример 5.5. 3.php (продолжение)

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

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

Пример 5.6. 3.php (окончание)

// Обработка нижнего колонтитула страницы if (!$footer = readCache("3_footer.cache" , 604800 ) ) { ?> Время создания нижнего колонтитула:
// останавливаем буферизацию ob_end_clean () ; // Выводим содержимое страницы echo $header . $body . $footer ;

Конечный результат выглядит примерно так:

Время создания шапки: 17:10:42 Время создания тела: 18:07:40 Время создания нижнего колонтитула: 17:10:42

Заголовок и нижний колонтитул обновляются еженедельно, в время как тело модифицируется, когда оно старее 5 секунд.

Блок-схема на рисунке 5.1 суммирует методологию блочной буферизации.

Рисунок 5.1. Блок-схема блочной буферизации вывода

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

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

Как мне реализовать простую систему кэширования на стороне сервера?

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

Cache_Lite

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

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

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

Пример 5.7. 4.php (начало)

// Подключаем класс вывода PEAR::Cache_Lite require_once "Cache/Lite/Output.php" ; // Определяем настройки для Cache_Lite $options = array ( "cacheDir" => "./cache/" , "writeControl" => "true" , "readControl" => "true" , "readControlType" => "md5" ) ; // Создаем объект класса Cache_Lite_Output

Для каждой части вывода вашего скрипта, которую вы хотите кэшировать, необходимо установить время жизни кэша в секундах. это время определяет, как долго нужно брать данные из кэше. По истечении этого времени, данные в файле будут обновлены. Далее, мы вызываем метод start(), доступный только в классе Cahce_Lite_Output, который включает буферизацию вывода. В метод мы передаем 2 параметра: первый – идентификатор файла с кэшем, второй – группа (тип кэша). Параметр «группа» позволяет объединять несколько шаблонов, это позволяет производить групповые действия, например, удалить все файлы кэша в группе. Как только вывод нужной нам части закончен, мы должны вызвать метод stop(). Этот метод остановит буферизацию и сохранит содержимое буфера в файл.

Пример 5.8. 4.php (продолжение)

// Устанавливаем время жизни кэша для данной части $cache ->setLifeTime (604800 ) ; // Начинаем буферизацию для участка с именем header // и помещаем его в группу Static if (!$cache ->start ("header" , "Static" ) ) { ?> "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" > "http://www.w3.org/1999/xhtml" > PEAR::Cache_Lite пример "text/html; charset=windows-1251" />

PEAR::Cache_Lite пример

Время создания заголовка:
// Останавливаем буферизацию и пишем буфер в файл $cache ->end () ; }

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

Пример 5.9. 4.php (продолжение)

$cache ->setLifeTime (5 ) ; if (!$cache ->start ("body" , "Dynamic" ) ) { echo "Время создания тела: " . date ("H:i:s" ) . "
" ; $cache ->end () ; } $cache ->setLifeTime (604800 ) ; if (!$cache ->start ("footer" , "Static" ) ) { ?> Время создания нижней части:
end () ; }

Когда вы вызовете для просмотра эту страницу, Cache_Lite создаст в каталоге для кэширования следующие файлы:

./cache/cache_Static_header ./cache/cache_Dynamic_body ./cache/cache_Static_footer

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

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

Настройки Cache_Lite

При вызове Cache_Lite (или любого из субклассов, например Cache_Lite_Output), есть много способов управлять его поведением. Все параметры должны быть помещены в массив и переданы конструктору:

Пример 5.10. 4.php (окончание)

// Задаем настройки для Cache_Lite $options = array ( "cacheDir" => "./cache/" , "writeControl" => TRUE , "readControl" => TRUE , "readControlType" => "md5" ) ; // Создаем объект Cache_Lite_Output $cache = new Cache_Lite_Output($options ) ;

В текущем версии класса (1.1) доступны следующие настройки:

  • cacheDir – Это каталог, в которрый будут помещаться файлы кэша. Значение по умолчанию – каталог, где выполняется скрипт.
  • caching – эта опция включает или выключает возможности Cache_Lite. Например, если у вас очень много запросов к Cache_Lite, а в процессе отладки вы захотите выключить кэширование, установить в FALSE. Значение по умолчанию – TRUE.
  • lifetime – Этот параметр содержит в себе заданный по умолчанию отрезок времени жизни кэша (в секундах). Изменить значение можно вызвав метод setLifeTime(). Значение по умолчанию 3600 (один час).
  • fileNameProtection – Если данная опция включена, Chache_Lite будет использовать MD5 кодирование для генерации имени файла с кэшем. Это позволяет вам использовать в названии файлов кэша и групп любые символы, даже запрещенные файловой системой. Этот параметр должен быть включен, когда вы используете Cache_Lite_Function. Значение по умолчанию – TRUE (включено).
  • fileLocking – Этот параметр включает механизмы блокирования файла с кэшем, на время записи в него данных. Значение по умолчанию – TRUE (включено).
  • writeControl – Проверяет, что файл кэша был записан правильно сразу после окончания записи. В случае ошибки бросает PEAR:Error. Эта возможность позволяет вашему скрипту перезаписать файл кэша еще раз, но замедляет его работу. Значение по умолчанию TRUE (включено).
  • readControl – Перед чтением файла с кэшем проверяет его на искажения. Cache_Lite размещает в файле значение длинны файла, которое можно использовать для контроля его целостности. Также имеется альтернативный механизм проверки целостности файла. Он включается параметром readControlType. Эти механизмы несколько замедляют скорость, но помогают гарантировать, что ваши пользователи увидят неиспорченную страницу. значение по умолчанию TRUE (включено).
  • readControlType – Этот параметр определяет тип механизма чтения файлов кэша. Доступные механизмы: цикличная проверка избыточности ("crc32" – значение по умолчанию) – использует функцию crc32 PHP, "MD5" хэш – используется функция md5 PHP, или простую проверку длинны – "strlen". Обратите внимание, что этот механизм не предназначен для защиты файлов кэша от вызова их напрямую посторонними пользователями. Это всего лишь способ определить – испорчен файл или нет.

pearErrorMode – включает принятый в PEAR способ возврата ошибок. Значение по умолчанию CACHE_LITE_PEAR_RETURN – возвращает объект /#c#?PEAR::Error.

  • memoryCaching – Если данный параметр включен, каждый раз, как вы вызываете запись кэша в файл, он записывается в массив Cache_Lite. saveMemoryCachingState и getMemoryCachingState используются для доступа к кэшу, сохраненному в памяти между запросами. Преимущество подобного метода состоит в том, что содержимое кэша может быть сохранено в едином файле, сокращая число циклов чтения/записи на диск. Кэш восстанавливается прямо в массив, к которому ваш скрипт имеет доступ. В наших примерах мы будем использовать обычный механизм Cache_Lite, но вам стоит поэкспериментировать с этим параметром в дальнейшем, если у вас очень большой сайт. По умолчанию TRUE (выключено).
  • onlyMemoryCaching – Если вы включили этот параметр – будет использоваться только механизм кэширования в памяти. По умолчанию TRUE (выключено).

memoryCachingLimit – Этот парметр определяет предел количества файлов кэша, которые могут быть сохранены в массиве в памяти. чем больше число файлов – тем больше памяти будет расходоваться. Поэтому определить ограничение – это очень хорошая идея. Конечно это не влияет на размер файла кэша, поскольку один или два массивных файла не создадут никаких проблем. значение по умолчанию – 1000.

Очистка кэша

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

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

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

$cache ->clean ("Static" ) ;

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

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

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

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

В разделе, называемом “How do I consume SOAP Web services with PHP?”, мы писали клиента для обслуживания SOAP Web service основанного на его WSDL файле; этот сервис обслуживает информацией о погоде все аэропорты в мире. Вот код, который получает данные от удаленного сервера:

$countries = $stationInfo ->listCountries () ;

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

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

Пример 5.11. 5.php (начало)

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

Пример 5.12. 5.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, получится недопустимое имя файла, так что кэширования не будет.

Пример 5.13. 5.php (продолжение)

$countries = $cache ->call ("stationInfo->listCountries" ) ;

Пример 5.14. 5.php (окончание)

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

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

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

Как мне управлять кэшированием на стороне клиента средствами 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-заголовков в вашем браузере

Простым но очень удобным инструментом для проверки заголовков запросов и откликов является Live Http Headers? – аддон к браузеру 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-браузеру разрешается использовать кэшированную версию страницы.

Пример 5.15. 6.php

/** * Посылает заголовок Expires HTTP 1.0. * @param int количество секунд до времени истечения срока жизни */ function setExpires($expires ) { header ("Expires: " . gmdate ("D, d M Y H:i:s" , time () + $expires ) . "GMT" ) ; } // Устанавливаем заголовок времени истечения срока жизни Expires setExpires(10 ) ; // Отображаем echo "Эта страница самоуничтожится через 10 секунд
"
; echo "Сейчас " . gmdate ("H:i:s" ) . " GMT
" ; echo """>Посмотреть вновь
" ;

Функция 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, чтобы указать, что страница не изменялась, не выводя при этом содержимого страницы.

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

Пример 5.16. 7.php (начало)

// Подключаем PEAR::Cache_Lite require_once "Cache/Lite.php" ; // Определяем настройки Cache_Lite $options = array ( "cacheDir" => "./cache/" ) ; // Инициализируем Cache_Lite $cache = new Cache_Lite($options ) ; // Некоторые фиктивные данные для хранения $id = "MyCache" ; // Инициализируем кэш, если страница запрошена впервые if (!$cache ->get ($id ) ) { $cache ->save ("Dummy" , $id ) ; } // Рандомизатор… $random = array (0 , 1 , 1 ) ; shuffle ($random ) ; // Произвольное обновление кэша if ($random [ 0 ] == 0 ) { $cache ->save ("Dummy" , $id ) ; } // Получаем время последней модификации кэш-файла $lastModified = filemtime ($cache ->_file) ; header ("Last-Modified: " . gmdate ("D, d M Y H:i:s" , $lastModified ) . " GMT" ) ; < v6 отдаёт их неправильно) $modifiedSince = 0 ; } if ($lastModified <= $modifiedSince ) { header ("HTTP/1.1 304 Not Modified" ) ; exit () ; } echo "Сейчас " . gmdate ("H:i:s" ) . " по Гринвичу
"
; echo """>Обновить
" ;

Не забудьте пользоваться ссылкой «Обновить» при запуске этого примера (нажатие Refresh’а обычно очищает кэш вашего браузера). Если вы кликните по ссылке неоднократно, в конечном итоге кэш будет изменён, ваш браузер удалит версию из кэша и сохранит в нём новую страницу, предоставленную PHP.

В вышеприведённом примере мы использовали PEAR::Cache_Lite для создания произвольно модифицируемого кэш-файла. Мы устанавливаем время модификации кэш-файла этой строкой:

$lastModified = filemtime ($cache ->_file) ;

Говоря техническим языком, это хак, поскольку переменная $_file класса PEAR::Cache_Lite должна быть приватной. Тем не менее мы вынуждены её использовать, чтобы получить имя кэш-файла и узнать его время модификации.

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

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

Использование функции getallheaders обеспечивает нам получение от PHP всех входящих заголовков в виде массива. Затем мы должны проверить, что заголовок If-Modified-Since действительно существует, если он есть, мы должны обработать специальный случай старых версий Mozilla (нижа 6й версии), который добавлял в конец (отклоняясь от спецификации) дополнительное поле к заголовку If-Modified-Since. Используя функцию PHP strtotime, мы получаем таймштамп даты, переданной нам браузером. Если такого заголовка нет, мы присваиваем таймштампу ноль, вынуждая таким образом PHP отдать посетителю последнюю версию страницы.

// Получаем заголовки запроса клиента – только для Apache $request = getallheaders () ; if (isset ($request [ "If-Modified-Since" ] ) ) { // Разделяем If-Modified-Since (Netscape < v6 отдаёт их неправильно) $modifiedSince = explode (";" , $request [ "If-Modified-Since" ] ) ; // Преобразуем запрос клиента If-Modified-Since в таймштамп $modifiedSince = strtotime ($modifiedSince [ 0 ] ) ; } else { // Устанавливаем время модификации в ноль $modifiedSince = 0 ; }

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

// Сравниваем время последней модификации контента с кэшем клиента if ($lastModified <= $modifiedSince ) { // Разгружаем канал передачи данных! header ("HTTP/1.1 304 Not Modified" ) ; exit () ; }

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

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

Дополнительные ссылки

  • GET-запрос с условием (Conditional GET)
  • Слежение за контентом на динамических сайтах