«Умное» кэширование страниц
В статье «Роботы и как с ними дружить» уже шла речь о том, что кэширование запрещать не нужно. Скажу больше, кроме серверного кэша, который работает всегда, полезно встроить в движок свою систему кэширования страниц. Она поможет максимально снизить нагрузку на сервер и ускорить выдачу страниц сайта. Вы думаете, что это невероятно сложно? – Да ничего подобного! На самом деле файловый кэш организовать очень просто.
В php есть группа функций управления выводом и она как будто специально создана для этой цели. В начале работы скрипта мы можем включить буферизацию вывода функцией ob_start(). Тогда все, что выводят операторы echo, print и прочие, будет накапливаться в буфере вывода до тех пор, пока скрипт не выполнит отправку содержимого буфера и отключение буферизации. При этом заголовки не буферизуются, а значит, можно в любой момент до очистки буфера отправить нужный заголовок. Но для нас важно, что пока информация из буфера не отправлена, к ней есть доступ. Функция ob_get_contents() в любой момент вернет нам строку со всем содержимым буфера.
Остается только подготовить «хранилище» для файлового кэша. Это должна быть директория, на которой нужно выставить атрибуты доступа 0777 (rwx-rwx-rwx), чтобы дать скрипту возможность создавать и удалять в ней файлы. Но доступ в эту директорию по http лучше на всякий случай закрыть, поместив в ней файл .htaccess с директивами:
Order Allow,Deny Deny from all
Каждую страницу сайта мы можем автоматически сохранять в файл в этой директории. Главное – чтобы имя файла однозначно соответствовало URL'у страницы. Тогда скрипт легко и просто найдет в кэше нужный файл. Можно собрать имя файла из параметров динамического URL, можно вообще использовать MD5-хэш URL'а, дело ваше.
Итак, в начале скрипта выполняем ob_start(). Затем генерируем имя файла, соответствующее запрошенному URL и проверяем, есть ли в директории кэша такой файл. Если он существует, уже не нужно посылать серию запросов к серверу базы данных, остается загрузить готовую страницу. Для этого достаточно вызова функции readfile(), которая считает файл и отправит его в поток вывода. Далее выполняем ob_end_flush() – вывод и закрытие буфера. Вот и все, страница отправлена.
Если файл с нужной страницей не найден в кэше, скрипт отрабатывает все свои операции, формируя страницу. Но вывод пока направляется в буфер. Поэтому после завершения всех действий скрипта нам нужно вытащить страницу из буфера функцией ob_get_contents() и сохранить ее в соответствующем файле. После этого выполняем ob_end_flush() для отправки страницы. Теперь кэш-файл для страницы есть и при следующем запросе этой страницы она будет просто отправлена из файла.
Вот, в сущности, и весь механизм файлового кэширования страниц. Он достаточно прост и немного «туповат». Попробуем сделать его более «умным».
Прежде всего, ни в коем случае не должны кэшироваться страницы, которые выдаются при обработке POST-форм. Обработчик формы всегда должен отрабатывать свой алгоритм. Поэтому прежде чем лезть в кэш за страницей, нужно убедиться, что массив $_POST не содержит никаких данных.
Во-вторых, кэш должен периодически обновляться. И здесь тоже нет никаких проблем, функция filemtime() вернет нам время последнего изменения файла (UNIX timestamp) или FALSE, если файла нет. С ее помощью проверку наличия страницы в кэше и проверку времени редакции страницы легко совместить. Задав константу «времени жизни» кэша, можно просто вычесть время изменения файла из текущего тайм-штампа. Если разница превышает «время жизни» или вместо времени редакции получено FALSE, скрипт должен отработать полностью и перезаписать файл. Заодно отпадает вопрос, какое время изменения страницы (Last-Modified:) отдавать в HTTP-заголовке. Конечно же, время изменения файла в кэше. Либо текущее время, если файл был только что перезаписан.
В-третьих (и это уже более сложно), кэш нужно избирательно очищать при изменениях страниц или структуры сайта. Конечно, проще всего делать полную очистку кэша при любом изменении. Но гораздо интереснее сделать очистку избирательной. И тут уже всё зависит от реализации движка и структуры данных. В каждом случае управление кэшем реализуется по-своему, но общий принцип очевиден: из кэша должны быть удалены файлы тех страниц, на которых скажутся изменения.
Например, если просто отредактирован текст страницы, нужно удалить только файл этой страницы. Но если страница в каком-то разделе добавляется или удаляется, нужно удалить файлы всех страниц, где в меню есть ссылка на эту страницу. Если добавляется или удаляется страница верхнего уровня иерархии, нужно очистить весь кэш, так как ссылка на эту страницу входит в главное меню.
Как видите, оснастить движок системой кэширования не так уж сложно. А эффект не нуждается в пояснениях – на сайте, где я впервые опробовал такое кэширование, установлено «время жизни» кэша 5 суток. Вот и представьте: для формирования страницы проводится одна выборка из базы данных за 5 суток – вместо повторения этой выборки при каждом посещении страницы. Теперь представим, что на сайте в день будет пара тысяч уникальных посетителей, просматривающих хотя бы по 3–5 страниц. Экономия серверных ресурсов просто потрясающая.
Еще одна выгода от внедрения файлового кэша – повышение надежности сайта. Не секрет, что даже на очень надежных хостингах время от времени возникают проблемы с серверами баз данных. Очень неприятно, когда именно в этот момент на сайт пожалует поисковый бот, а потом в выдаче поисковой системы вместо цитаты со страницы сайта окажется сообщение «Не удалось соединиться с базой данных». Но если есть файловый кэш, сайт может продержаться на нем. Достаточно просто ввести условие: если соединения с базой данных нет, искать страницу в кэше и не проверять дату ее обновления. Обновлять кэш в этом случае будем позже, когда сервер баз данных заработает нормально. Конечно, это обеспечит нам только отображение информационных страниц, в какой-то части сайт может быть неработоспособен, но он хотя бы не исчезнет с глаз полностью.
PS А этому движку файловый кэш не требуется. Он и так сделан на файлах, без MySQL, поэтому скорость и надежность доступа к данным повышать просто некуда.