Страничное кеширование в WordPress

imageВ последнее время на Хабре появилось довольно много постов по данной теме, но по своей сути их можно назвать: «Смотрите, я поставил Varnish / W3 Total Cache и держу миллион запросов на «Hello world» страничке». Данная же статья рассчитана больше на гиков, желающих познать, как же это все работает и написать собственный плагин для страничного кеширования.

Зачем? Стандартный вопрос, который возникает у каждого разработчика перед созданием велосипеда уже существующего функционала. Действительно, готовых плагинов уйма и многие из них довольно качественные, но нужно понимать что в первую очередь они рассчитаны на статические блоги. Что же делать, если у вас не стандартный WordPress сайт? Приступим Какие инструменты предоставляет нам WordPress? Как все знают, данная CMS позволяет легко расширять свою функциональность с помощью плагинов, но не все знают, что есть несколько типов плагинов: обычные плагинынаходятся в wp-content/pluginsадминистратор может их свободно устанавливать, активировать и деактивировать; обязательные плагинынаходятся в wp-content/mu-pluginsданные плагины включаются автоматически и не могут быть деактивированы; системные плагинынаходятся в wp-contentпозволяют переопределять классы ядра или внедрять в них собственный функционал; к ним относятся: sunrise.phpПодгружается в самом начале инициализации ядра. Чаще всего используется для domain mapping; db.phpПозволяет переопределять стандартный класс для работы с базой данных; object-cache.phpПозволяет переопределись стандартный класс объектного кеширования, например если захотите использовать Memcached или Redis; advanced-cache.phpПозволяет реализовать страничное кеширование, то что нам и нужно! advanced-cache.php Для того, чтобы данный плагин начал функционировать, его нужно поместить в директорию wp-content, а в wp-config.php добавить строку: define ('WP_CACHE', true); Если заглянуть в код WordPress, то можно увидеть, что данный скрипт подгружается на раннем этапе загрузки платформы. // wp-settings.php:63 // For an advanced caching plugin to use. Uses a static drop-in because you would only want one. if (WP_CACHE) WP_DEBUG? include (WP_CONTENT_DIR. '/advanced-cache.php') : @include (WP_CONTENT_DIR. '/advanced-cache.php'); Также, после загрузки ядра, CMS попытается вызвать функцию wp_cache_postload (), но о ней позже. // wp-settings.php:226 if (WP_CACHE && function_exists ('wp_cache_postload')) wp_cache_postload (); Хранилище Для хранения кеша лучше всего использовать быстрые хранилища, так как от их скорости напрямую зависит скорость отдачи контента из кеша. Я бы не советовал использовать MySql или файловую систему, гораздо лучше с этим справятся Memcached, Redis или другие хранилища, использующие оперативную память.Мне лично нравится Redis, так как им довольно просто пользоваться, имеет хорошие показатели скорости чтения\записи и как приятный бонус — сохраняет копию данных на жесткий диск, что позволят не терять информацию при перезагрузке сервера.

$redis = new Redis (); // подключение к серверу $redis→connect ('localhost');

// сохранить данные $value под ключем $key на время $timeout $redis→set ($key, $value, $timeout); // получить данные по ключу $key $redis→get ($key); // удалить данные по ключу $key $redis→del ($key); Разумеется это не полный перечень методов, весь список API можно изучить на официальном сайте, но для большинства задач этого достаточно.Если на сайте используется прокаченный объектный кеш (object-cache.php), то имеет смысл использовать его API:

wp_cache_set ($key, $value, $group, $timeout); wp_cache_get ($key, $group); wp_cache_delete ($key, $group); Простейшое страничное кеширование Код нарочно упрощен, многие проверки убраны, дабы не путать читателя лишними конструкциями и сфокусироватся на логике самого кеширования. В файл advanced-cache.php прописываем: // если как хранилище используется объектный кеш, то его нужно инициализировать вручную, // поскольку на данном этапе загрузки он еще не загружен wp_start_object_cache ();

// формируем ключ // чаще всего это URL страницы $key = 'host:' . md5($_SERVER['HTTP_HOST']) . ': uri:' . md5($_SERVER['REQUEST_URI']);

// берем данные из кеша по ключу if ($data = wp_cache_get ($key, 'advanced-cache')) {

// если данные существуют, отображаем их и завершаем выполнение $html = $data['html']; die ($html); } // если данных нет, продолжаем выполнение

// не сохраняем в кеш запросы админ панели if (! is_admin ()) {

// перехватываем буфер вывода ob_start (function ($html) use ($key) {

$data = [ 'html' => $html, 'created' => current_time ('mysql'), 'execute_time' => timer_stop (), ];

// после генерации страницы сохраняем данные в кеш на 10 минут wp_cache_set ($key, $data, 'advanced-cache', MINUTE_IN_SECONDS * 10);

return $html; });

} Все, вы получили простейший рабочий страничный кеш, теперь рассмотрим каждый участок детальнее.

Создание ключа

$key = 'host:' . md5($_SERVER['HTTP_HOST']) . ': uri:' . md5($_SERVER['REQUEST_URI']); В данном случае ключем является URL страницы. Использование глобальной переменной $_SERVER и хеширования нельзя назвать лучшей практикой, но для простого примера подойдет. Советую добавлять разделяющие участки строки как «host:» и «uri:», так как их удобно использовать в регулярных выражениях. Например получить все ключи по определенному хосту: $keys = $redis→keys ('host:' . md5('site.com') . ':*'); Выдача из кеша // берем данные из кеша по ключу if ($data = wp_cache_get ($key, 'advanced-cache')) {

// если данные существуют, отображаем их и завершаем выполнение $html = $data['html']; die ($html); } Тут все просто, если кеш уже создан, то выдаем его пользователю и завершаем выполнение.Сохранение в кешPHP функция ob_start перехватывает весь последующий вывод в буфер и позволяет обработать его в конце работы скрипта. Простыми словами мы получаем весь контент сайта в переменной $html.

ob_start (function ($html) { // $html — HTML код готовой страницы return $html; } Далее сохраняем данные в кеш: $data = [ 'html' => $html, 'created' => current_time ('mysql'), 'execute_time' => timer_stop (), ]; wp_cache_set ($key, $data, 'advanced-cache', MINUTE_IN_SECONDS * 10); Есть смысл сохранять не только HTML, но и прочую полезную информацию: время создания кеша и тд. Очень рекомендую сохранять HTTP заголовки, хотя бы Content-Type и посылать их при выдаче из кеша.Совершенствуем В примере выше мы использовали функцию is_admin () для исключения кеширования админ панели, но данный способ не очень практичен по двум причинам: запросы на admin-ajax.php не попадают в кеш; если администратор первым посетит страницу, то в кеш попадет его «admin bar» и прочие вредные для пользователей вещи; Наилучшим решением для простого сайта будет вообще не использовать кеш для залогиненых пользователей (администраторов). Так как advanced-cache.php выполняется до полной загрузки ядра, мы не можем пользоваться функцией is_user_logged_in () , но можем определить наличие аутентификации по cookie (как известно WordPress не использует сессии). // проверяем наличие cookie wordpress_logged_in_* $is_logged = count (preg_grep ('/wordpress_logged_in_/', array_keys ($_COOKIE))) > 0;

// сохраняем кеш только не залогиненых пользователей if (! $is_logged) { ob_start (function ($html) use ($key) { // … return $html; }); } Усложняем задачу Допустим, наш сайт отдает разный контент для пользователей из разных регионов или стран. В данном случае ключем кеша должен быть не только URL страницы, но и регион: $region = get_regeon_by_client_ip ($_SERVER['REMOTE_ADDR']); $key = 'host:' . md5($_SERVER['HTTP_HOST']) . ': uri:' . md5($_SERVER['REQUEST_URI']) . ': region:' . md5($region); По данному принципу мы можем формировать разный кеш разным группам пользователей по любым параметрам.wp_cache_postload () Данная функция вызывается после загрузки ядра и ее тоже удобно использовать в некоторых случаях.По опыту скажу, что такой вариант работает гораздо стабильней: function wp_cache_postload () {

add_action ('wp', function () { ob_start (function ($html) { // … return $html; }); }, 0); } На момент вызова wp_cache_postload (), функция add_action уже существует и ей можно пользоваться.Бывают ситуации, когда для формирования ключа кеша нужны данные, которые невозможно получить из cookie, IP и прочих доступных на этапе инициализации ресурсов. Например нужно генерировать индивидуальный кеш для каждого пользователя (иногда это имеет смысл).

function wp_cache_postload () {

$key = 'host:' . md5($_SERVER['HTTP_HOST']) . ': uri:' . md5($_SERVER['REQUEST_URI']) . ': user:' . get_current_user_id ();

if ($data = wp_cache_get ($key, 'advanced-cache')) {

$html = $data['html']; die ($html); }

add_action ('wp', function () {

ob_start (function ($html) { // … return $html; });

}, 0); } Как видно в примере, вся логика помещена в тело wp_cache_postload и тут уже доступны все функции платформы, включая get_current_user_id (). Данный вариант немного медленней предыдущего, но мы получаем безграничные возможности для тонкой настройки страничного кеша.О чем не стоит забывать Данные примеры очень упрощены, если будете их использовать в своих проектах — не поленитесь добавить условия для кеширования: только GET запросы только, если на странице нет ошибок только, если нет set-cookie только, если статус 200 или 301 Эффективность кеша напрямую зависит от его времени жизни. Увеличивая $timeout, потрудитесь продумать инвалидацию кеша при изменении данных. WP Cron запускается позже advanced-cache.php, может просто не срабатывать при высоком кешхите. Заключение Нет ничего сложного в написании собственного страничного кеширования. Разумеется, в этом нет смысла для типичного сайта, но если вы породили монстра — данный материал должен оказаться полезным.

© Habrahabr.ru