Кэширование в PHP. Реализация кэширования на PHP

В предыдущем материале о веб-технологиях мы упомянули полезную статью Кэширование в HTTP (далее: «Статья с nomagic.ru»). По статье, однако, у нас возникли некоторые вопросы, а обсуждение там заглохло, поэтому пришлось искать все ответы самим. Вопросы, собственно, не именно по статье – они копились в течение нескольких лет. Надоело иметь их нерешёнными, а статья просто дала повод искать решения более активно.

Инструменты

Первый вопрос – как увидеть HTTP-заголовки запросов браузера и ответов сервера? Автор статьи с nomagic.ru рекомендует использовать для этой цели "Web Developer tools" в Firefox"е и какой-то мутный "DevToolbar" для ИЕ. Рука потянулась было кликнуть по ссылке, но зависла в воздухе:

1) Web Developer tools для FF у нас и так есть, и там нет инструмента для просмотра HTTP-заголовков, там даже DOM-инспектор в версии 3 зачем-то убрали!

3) И совсем мрачная мысль: ладно, допустим, для FF у нас есть-таки LiveHTTPHeaders; с ИЕ – вдруг да повезёт; ну, а Опера? А Google chrome?.. Нам что теперь, по всему огороду метаться?

Почему бы прямо на сайте, средствами PHP не отобразить все HTTP-заголовки? Там ведь есть переменные окружения , переменные для работы с сервером и всё такое. То есть точно известно, что там есть, например, $_SERVER["HTTP_HOST"] и HTTP_REFERER (у нас на каждом сайте используются). Надо добавить все остальные HTTP_ – вот и будут заголовки запроса. Тем более, что в PHP для этого есть специальная функция getallheaders(). Или apache_request_headers(). И apache_response_headers(). Да. Так можно вывести на экран все HTTP-заголовки. Казалось бы. Но нас ожидал тяжёлый удар ниже пояса и 15-минутные мучения, результатом которых стало открытие: на нашем хостинге PHP установлен как cgi (а не как модуль Апач) && в такой конфигурации все эти функции...headers() не работают!

Запустив скриптик с echo phpinfo() и бегло просмотрев результат, обнаруживаем, что искомые заголовки HTTP-запроса есть в массиве $_ENV (и больше нигде). Ладно, _env так _env. Но там много всякого хлама (в данный момент для нас лишнего), поэтому создаём новый массив $varrvis и аккуратно откромсываем туда из _env более-менее нужные куски:

Foreach($_ENV as $ke=>$va) { if (preg_match("/^HTTP\_/i",$ke) && !preg_match("/COOKIE/i",$ke)) $varrvis["$ke"]=$va; }

А вот получить заголовки ответа нашего сервера – ну ваще никак, кроме функции headers_list() . И только те заголовки, которые мы сами отправим в скрипте PHP с помощью функции header() . По идее функцию headers_list() следует запускать после написания всех заголовков. Мы так примерно и сделали, хотя, скорее всего, для данного сайта (сайт – на котором ставились опыты) это без разницы, потому что везде используется ob_start("ob_gzhandler") . В конец тестируемых скриптов добавляем конструкцию:

Foreach(headers_list() as $ke=>$va) { $varrvis[$ke]=$va; }

И дополняем наш массив заголовков ответами сервера. А между Запросом и Ответом для удобства чтения вставим строку:

$varrvis["Response"]="==============================";

Осталось в самом конце тестируемых скриптов написать print_r($varrvis) – и потом бодро листать страницы сайта во всех подручных браузерах, любуясь HTTP-заголовками.

HTTP-кэширование инструкциями Apache

В статье с nomagic.ru указывается два источника инструкций кэширования: конфигурационные файлы Апача (http.conf && .htacces) и непосредственно PHP-скрипт с командами вида header("Pragma: no-cache"). Но существует ещё третий источник – его можно обнаружить несложным опытом:

1) пишем (раскомментируем) в httpd.conf (Апач 1.3.39) cтроки:

LoadModule expires_module modules/mod_expires.so LoadModule headers_module modules/mod_headers.so AddModule mod_expires.c AddModule mod_headers.c

2) в папке нашего сайта в .htaccess добавляем инструкции:

Header append Cache-Control "public" ExpiresActive On ExpiresDefault "access plus 1 hours"

3) пишем простенький скрипт pi.php из двух строк:

4) открываем страницу pi.php в Firefox и видим в LiveHTTPHeaders (Наш PHP «инструмент» может показывать только заголовки, отправленные функцией header(), а пока мы ей не пользуемся). следующие строки, имеющие отношение к кэшированию:

Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Expires: Thu, 19 Nov 1981 08:52:00 GMT Pragma: no-cache

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

Session.cache_limiter = nocache

Именно она заставляет PHP посылать анти-кэширующие заголовки в определённых условиях (например, при использование функции session_register()). Мы, конечно, немного схитрили, подогнав ситуацию под эти условия. Но кто поручится, что никогда не будет использовать в своих скриптах функцию session_register() ? Да, в общем-то, и без неё дело обстоит достаточно хреново: уберите первую строку из скрипта pi.php (оставив только echo phpinfo();) – тоже ничего хорошего:

И это всё, что дают кэширующие инструкции Апача в сочетании с "session.cache_limiter=nocache" в php.ini. Отсутствует самый главный заголовок – Last-modified (дата последнего изменения страницы), без которого невозможно ни правильно установить, ни правильно уничтожить кэширование в браузере.

Самый забавный результат получается, если "дёрнуть попугая сразу за обе ноги" – написать в php.ini "session.cache_limiter=private" (нужна перезагрузка Апача) и оставить в скрипте строку session_register("var1"):

Cache-Control: private, max-age=300, pre-check=300 Expires: Thu, 19 Nov 1981 08:52:00 GMT Last-Modified: Mon, 06 Jul 2009 15:13:40 GMT

Появляется Last-Modified , который показывает время последнего изменения скрипта php, а Cache-Control противоречит Expires . Поведение браузеров будет непредсказуемым.

Правильное HTTP-кэширование

В последних полученных нами в ходе опыта заголовках противоречивость в общем-то совсем не фатальна: браузеры и не такое ещё видали, к таким вещам они вполне «помехоустойчивы». Наибольшую проблему представляет как раз Last-Modified , который нужен и пользователям (браузерам), и поисковикам. Понятно, что дату изменения файла PHP для него использовать нельзя – потому что реальное содержимое страницы может быть вообще никак не связано с этой датой: обычно содержимое страницы извлекается из базы данных, и дату его изменения тоже надо извлекать оттуда (из БД ).

Если это конкретная статья с нашего сайта, мы просто берём дату текущей записи из поля `datrec` таблицы `articles`. Если это список статей (на главной странице сайта), мы ищем наибольшую дату всех записей по формуле "select max(`datrec`) from `articles`" – именно она и будет датой последнего изменения страницы, которую мы передадим в заголовке Last-Modified .

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

1) Etag – хэш содержимого страницы, получаемый, например, с помощью функции md5 (текст_страницы);

2) Content-length – общая длина текста, отправленного браузеру в ответ на его запрос.

Мы не можем использовать Content-length , потому что этот параметр постоянно меняется: в правой колонке каждой страницы у нас висит напоминание о том, что это всё-таки сайт рекламной газеты «Деловая неделя», – список товаров последнего номера газеты. Список этот довольно большой, поэтому на странице выводится только небольшая часть списка, выбранная случайным образом .

Как же, спросите вы, мы используем Etag – он ведь тоже тогда постоянно случайным образом меняется? А очень просто: мы не включаем переменную часть страницы в хэш, а составляем хэш только «по материалам базы данных статей». Почему же нельзя так же поступить и с Content-length ? Да потому что Content-length браузер может легко проверить (ИЕ так и делает – отправляет на сервер обратно действительную длину полученного содержимого). А хэш можно написать какой попало (главное, чтобы он менялся при изменении отслеживаемой части страницы), браузер ведь не знает, какой мы используем алгоритм, и вынужден просто принимать наш Etag на веру.

Мы используем два способа хэширования:

1) в случае списка текстов, получаемых из многих строк таблицы, создаём Etag * по формуле $etag=md5($list) ;

2) в более простом случае (извлекается только одна запись из таблицы) заставляем работать mysql, добавляя в запрос лишнее значение: "select `id`, `title`, `text`, `author`, `datrec`, old_password(concat(`title`,`text`,`author`)) as `etag` from `articles`...".

При отправке заголовков функцией header() нужно следить, чтобы эти действия производились раньше какой-либо отправки содержимого браузеру (через echo, print PHP или просто обычным HTML-кодом). То есть сначала вся проверяемая часть помещается в переменную, вычисляется Etag *, отправляются все заголовки, и только потом можно выводить содержимое. Если вы, конечно, не написали в начале страницы ob_start("ob_gzhandler"). Мы-то как раз написали, поэтому отправляем заголовки как попало и когда попало. Вот этот ob_gzhandler ещё позволяет получить всё содержимое, отправляемое браузеру, сразу – функцией ob_get_contents() , а также истинную длину содержимого (для заголовка Content-length ) – функцией ob_get_length() . Мы, как уже говорили, не можем на данном сайте использовать всё содержимое страницы для формирования этих заголовков. Но на других сайтах – вполне.

304 Not Modified

Итак, мы отправляем клиентам правильную дату изменения страницы и Etag . Клиенты относятся с пониманием – посылают в следующих обращениях к этой странице заголовки If-Modified-Since и If-None-Match , что вы можете увидеть сами в самом низу любой нашей статьи (после нажатия клавиши F5, разумеется). Но желанный результат не достигнут: сервер в ответ на все запросы браузера исправно посылает заголовок HTTP/1.x 200 OK , и никаких 304 . Наш «инструмент» не отображает заголовки "200 OK", потому что мы их не формируем функцией header().

Заголовок 304 можно увидеть в большом количестве через LiveHTTPHeaders – у файлов картинок, Javascript, css и простых HTML страниц. Этот заголовок отправляет сам Апач, и он делает это без всяких наших ухищрений с модулем headers.so и без дополнительных инструкций типа "ExpiresActive On". Но не для страниц, формируемых PHP.

Мы сами вписали в PHP-скрипт отправку заголовков браузеру, и сами должны проверять на наличие-отсутствие валидации последующие запросы браузера, и сами же потом сличать контрольные параметры и, в зависимости о результата, отправлять браузеру заголовок 200 или 304. Точнее, заголовок 200 PHP отправляет всегда сам, нам нужно только вычислять ситуацию необходимости 304. Мы делаем это в главном конфигурационном файле всех сайтов configbase.php.

Сложность получения информации о заголовках в том, что на одном хостинге PHP работает как cgi, а на другом как модуль Апач, поэтому сначала приходится проверять наличие переменных в «суперглобальных» массивах Env и Server , и в зависимости от результат создавать ссылку на подходящий массив:

$h304="HTTP/1.x 304 Not Modified"; $match=""; $since=""; $varr=array(); $varrvis=array(); if (array_key_exists("HTTP_HOST",$_ENV)) $varr =& $_ENV; if (array_key_exists("HTTP_HOST",$_SERVER)) $varr =& $_SERVER; if (isset($varr["HTTP_IF_NONE_MATCH"])) $match=$varr["HTTP_IF_NONE_MATCH"]; $match=trim(strval($match)); if (isset($varr["HTTP_IF_MODIFIED_SINCE"])) $since=$varr["HTTP_IF_MODIFIED_SINCE"]; $since=explode(";",$since); $since=strtotime(trim($since));

Предпоследняя строчка нужна из-за ИЕ, который в заголовке IF_MODIFIED_SINCE отправляет ещё и длину страницы: "Fri, 03 Jul 2009 15:42:30 GMT; length=20994" – мы отрезаем от данного заголовка всё, что может быть после точки с запятой. Затем создаём независимый от конкретного хостинга массив HTTP-заголовков:

Foreach($varr as $ke=>$va) { if (preg_match("/^HTTP\_/i",$ke) && !preg_match("/COOKIE/i",$ke)) $varrvis["$ke"]=$va; } $varrvis["Response"]="=============================";

Ну, и главный фрагмент кэширования, ядро всей нашей системы, находящееся внутри страниц PHP (где $dat – время из таблицы mysql, переведённое в секунды функцией strtotime ):

Header("Etag: $etag"); header("Cache-Control: private, max-age=0"); header("Expires: ".gmdate("r")." GMT"); header("Connection: Keep-Alive"); header("Keep-Alive: timeout=5, max=100"); if ($since==$dat) { if (!$match || $match==$etag){ $varrvis=$h304; include "bottom.php"; header($h304); header("Connection: Close"); exit; } } else { header("Last-Modified: ".gmdate("r", $dat)." GMT"); }

Система работает корректно во всех упомянутых в данной статье браузерах: кэширует, когда это нужно, и отправляет браузеру новую информацию, если она есть. Например, если после открытия главной страницы сайта (со списком статей) нажать F5 (не в Опере!:-), внизу страницы можно увидеть долгожданный заголовок 304 (в Опере его тоже можно увидеть, если попасть на данную страницу, щёлкнув по ссылке на другой странице сайта). Если в заголовок какой-нибудь статьи были внесены изменения или, например, добавилась новая статья, скрипт, получив от браузера запрос валидации, обнаружит изменение данных и отправит браузеру новое содержимое страницы, а не заголовок 304 .

Человеческими словами то, что мы делаем с помощью этих заголовков, можно пересказать так:

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

2) браузер послушно складывает страницу в свой кэш, вместе с картинками и файлами css; при последующих обращениях к странице браузер спрашивает у сервера, изменилась ли дата (IF_MODIFIED_SINCE) и, иногда, контрольная сумма (IF_NONE_MATCH) – про контрольную сумму ИЕ, например, не спрашивает;

3) если дата изменилась, мы проверяем, был ли от браузера запрос контрольной суммы, и если был, проверяем также её изменение; если ничего не поменялось, отправляем браузеру заголовок 304 ; если поменялось – не отправляем 304 (и PHP сам отправляет 200 OK);

Да, и ещё одна деталь для нашего «инструмента»: первый заголовок (HTTP-статуса) почему-то никак не извлекается функцией headers_list() . Когда он 200 , это не очень принципиально, но 304 хотелось бы видеть (чтобы убедиться в работоспособности нашей системы кэширования). Поэтому приходится «подрисовывать» этот заголовок в массив заголовков руками в строке

$varrvis=$h304; ,

а потом для всех остальных полученных функцией headers_list() заголовков увеличить индекс на единицу ($ke+1):

Foreach(headers_list() as $ke=>$va) { $varrvis[$ke+1]=$va; }

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

© 2009, «Деловая неделя», Михаил Гутентог

501 . SlipkeR

Спасибо) все понятно и доходчиво написано) автору спс)

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

Статические локальные переменные

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

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

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

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

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

Функции разделяемой памяти APC

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

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

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

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

Другие примечания по поддержке разделяемой памяти в APC:

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

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

APC является довольно популярным расширением, и поддерживается основными разработчиками PHP и (весьма вероятно) будет поставляться в комплекте с PHP 5.4.

Memcached для больших распределенных кэшей

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

Memcached с другой стороны, представляет собой распределенную службу для хранения данных ключ-значение. Расширение может быть развернуто на отдельном выделенном сервере или в том же стеке PHP приложений. Важно понимать, что нет никакой синхронизации/репликации между несколькими Memcached серверами, и они совсем ничего не знают друг о друге. Фактический сервер, который будет использоваться для хранения, выбирается на стороне клиента с помощью алгоритма хеширования на основе предоставленных данных "ключа". Именно поэтому, в отличие от APC, кэшированные данные не дублируются между различными машинами, а память лучше используется для крупных распределенных приложений.

API очень похож на функциональность разделяемой памяти в APC. Тот же пример с обменом валют, реализованный с помощью PHP расширения Memcache:

pconnect("..."); function getCurrencyRates() { global $memcache; $rates = $memcache->get("rates"); if ($rates === false) { // Запрос к базе данных или внешним API функциям $rates = ...; // Курс обмена будет кэширован на 1 час $memcache->set("rates", $rates, 0, 3600); } return $rates; } ?>

Обновление кэша такое же, как и в APC – с использованием TTL функциональности или набора функций.

delete("rates"); // Удаляет кэш $rates $memcache->flush(); // Удаляет все кэшированные данные?>

Локальный APC кэш всегда будет более быстрым методом по сравнению с Memcached.

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

Таблицы баз данных в оперативной памяти

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

MySQL предоставляет таблицы в оперативной памяти в подсистеме хранения данных MEMORY. Хотя данные будут очищены после перезагрузки сервера - схемы таблиц будут сохраняться:

CREATE TABLE test (...) ENGINE = MEMORY

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

CREATE TEMPORARY TABLE test (...)

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

true)); ?>

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

Простой файловый кэш

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

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

Во время разработки проектов на 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 - в конце:

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

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

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

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

Начнём с первого файла, который назовём read_cache.php :

$cache_time = 300; // Время жизни кэша в секундах
$file = strrchr($_SERVER["SCRIPT_NAME"], "/");// Получаем название файла
$file = substr($file, 1); // Удаляем слеш
$cache_file = "/cache/$file.html"; // Файл будет находиться, например, в /cache/a.php.html
if (file_exists($cache_file)) {
// Если файл с кэшем существует
if ((time() - $cache_time) < filemtime($cache_file)) {
// Если его время жизни ещё не прошло
echo file_get_contents($cache_file); // Выводим содержимое файла
exit; // Завершаем скрипт, чтобы сэкономить время на дальнейшей обработке
}
}
ob_start(); // Открываем буфер для вывода, если кэша нет, или он устарел
?>

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

Теперь создадим файл write_cache.php , который будет заниматься записью в файл того, что у нас накопилось в буфере:

$handle = fopen($cache_file, "w"); // Открываем файл для записи и стираем его содержимое
fwrite($handle, ob_get_contents()); // Сохраняем всё содержимое буфера в файл
fclose($handle); // Закрываем файл
ob_end_flush(); // Выводим страницу в браузере
?>

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

require_once "read_cache.php"; // Пытаемся вывести содержимое кэша
// Здесь идёт обычная генерация страницы
require_once "write_cache.php"; // Здесь идёт сохранение сгенерированной страницы в кэш
?>

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

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

Что такое кэширование?

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

На сегодняшний день кэши бываю двух типов - локальные и общие.

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

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

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

Ключевые принципы сохранения страниц в кэш

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

Несколько общих утверждений, которые характерны не только для PHP-приложений:

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

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

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

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

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

  1. Expires - задает дату истечения срока годности определенного документа. Задание ее в прошлом определяет запрет кэш для этой странички.
  2. Cache-control: no-cache - управление кэшем. Значение no-cache определяет запрет кэш этой странички. Для версии протокола HTTP/1.0 здесь действует "Pragma: no-cache".
  3. Last-Modified - это дата последнего изменения определенного содержимого. Поле применяется исключительно для статических страничек. Apache заменяет данное поле значением поля Date для динамически генерируемых страничек, в частности для страниц, которые содержат SSI.

Чтобы запретить кэширование, достаточно прописать:

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

Header("Expires: " . gmdate("D, d M Y H:i:s") . " GMT");

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

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

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

Главной задачей здесь является получить дату следующего понедельника в виде 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=секунды, который определяет время, по истечении которого документ уже считается устаревшим и имеющий гораздо больший приоритет при вычислении свежести конкретного документа.

Если Вы публикуете новости с интервалом в 60 минут:

Header("Cache-control: public"); header("Cache-control: max-age=3600");

Реализация кэширования на PHP

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

Начнем с первого файла, который назовем read_cache.php .

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

Теперь создадим специальный файл write_cache.php . Он будет записывать в файл то, что накопилось в буфере.

Теперь в любом php-файле, который отвечает за вывод, можно включить кэширование:

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



Есть вопросы?

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: