Кеш в Drupal
Кеш в Drupal
Drupal насчитывает множество различных сегментов кеша. Сегменты кеша похожи для версий 7.XX и 6.XX:
1. {cache} — сегмент для общего хранилища кеша. Сюда попадают данные, которые невозможно классифицировать, либо же нет смысла создавать под них новый сегмент кеша.
2. {cache_block} — добавляется при включении модуля Block (входит в ядро). При загрузке региона темы Drupal производится загрузка данных по всем блокам этого региона и при необходимости производится построение блока или отображение его из кеша, пропуская вызов хука hook_block_view(). Стоит учесть, что кеширование для блоков отключается, если включаются модули по работе с доступами к материалу, использующие hook_node_access(). Так же необходимо знать, что при программном создании блока через hook_block_info() можно управлять параметрами кеширования для блока (подробнее — в документации).
3. {cache_filter} — модуль Filter создает свою таблицу для хранения кеша для обработанного фильтрами текста. Чем сложнее фильтр, тем больше процессорного времени тратится на обработку текста. Поэтому по возможности все вызовы check_markup() кешируются. Cache ID для таблицы {cache_filter} собирается по правилу название_формата: язык: хэш_текста.
4. {cache_form} — если остальные кешируемые данные хранятся для ускорения работы сайта, то этот кеш на производительность никак не влияет. Он необходим, чтобы формы, построенные с помощью Forms API, были абсолютно безопасными с точки зрения уязвимостей. Каждый раз при построении формы она сохраняется в сегменте {cache_form}. Если форм и посетителей много, то {cache_form} имеет свойство разрастаться до внушительных размеров, если не запускать cron для очистки кеша каждый час-два.
5. {cache_menu} — включается при включении модуля Menu и является хранилищем ссылок из всех меню, созданными через интерфейс. Cache ID строится по правилу links: имя_меню:tree-data: язык: хэш_параметров.
6. {cache_page} — хранит закешированные данные страниц для анонимных пользователей. Если найден кеш для текущей страницы, то будут вызваны только 2 хука: hook_boot() и hook_exit(). Остальные же хуки (включая hook_init() и прочие) будут пропущены. Это именно тот кеш, который включается на сайте в разделе настроек производительности (admin/config/development/performance) галочкой «Кешировать страницы для анонимных пользователей».
7. {cache_update} — модуль Update manager добавляет данный сегмент. Он хранит данные по всем релизам для включенных модулей.
Сегменты кеша, имеющиеся в Drupal версии 7.XX (нет в версии 6.XX):
1. {cache_path} — хранит соответствие между системным путём и его алиасами для более быстрого поиска алиаса по системному пути.
2. {cache_image} — зарезервирована модулем Image и может использоваться как хранение сведений о проведении различных манипуляций над изображениями.
3. {cache_bootstrap} — сегмент кеша, в котором хранятся данные, инициализируемые при загрузке Drupal.
4. {cache_field} — в данном сегменте хранятся данные по всем полям (fields). Cache ID формируется по правилу field: тип_сущности:id_сущности.
Так же сторонние модули могут создавать свои сегменты кеша. Например, сегменты для модулей hacked, l10n_update, token, views:
cache_hacked
cache_l10n_update
cache_token
cache_views
cache_views_data
Стоит упомянуть и объектный кеш Ctools’a, который не относится к ядру и создаётся модулем CTools. Объектный кеш CTools’a — это сегмент кеша, который предоставляет своё пространство под хранение больших объектов, которые редактируются в данный момент. Например, кеш изменённого представления до его сохранения в модуле Views хранится именно в объектном кеше CTools’a. В отличии от других сегментов кеша он имеет дополнительное поле sid (Session ID) — идентификатор текущей сессии пользователя. Благодаря ему изменённые данные видны только изменившему объект пользователю. Этот сегмент не имеет поля expire и не удаляется при очистке кеша через интерфейс, но очищается раз в сутки по Cron с удалением из этого сегмента кеша недельной давности.
Жизненный цикл страницы
При запросе страницы у веб-сервера браузером пользователя происходит следующее (на примере Drupal версии 7.XX):
Производится первичная загрузка ядра Drupal с одним из параметров:
DRUPAL_BOOTSTRAP_CONFIGURATION: Инициализирует только конфигурацию.
DRUPAL_BOOTSTRAP_PAGE_CACHE: Инициализация слоя кеширования.
DRUPAL_BOOTSTRAP_DATABASE: Инициализация слоя базы данных.
DRUPAL_BOOTSTRAP_VARIABLES: Инициализация слоя переменных.
DRUPAL_BOOTSTRAP_SESSION: Инициализация работы с сессиями.
DRUPAL_BOOTSTRAP_PAGE_HEADER: Инициализация слоя работы с заголовками.
DRUPAL_BOOTSTRAP_LANGUAGE: Инициализация слоя работы с языком страницы.
DRUPAL_BOOTSTRAP_FULL: Полностью загружает Drupal. А также добавляет функции проверки и исправления введенных данных.
Подробнее можно посмотреть в функции drupal_bootstrap() из файла bootstrap.inc расположенного в папке includes.
При выполнении drupal_bootstrap() с параметром DRUPAL_BOOTSTRAP_FULL производится:
Подключение файлов системных функций.
Инициализация всех слоёв и первичных настроек.
Подключение файлов всех включённых модулей.
Инициализация переменных и системных функций для работы с путями и их алиасами в Drupal.
Инициализация включённой темы оформления.
Производится выполнении module_invoke_all(). Реализует API для загрузки и взаимодействия с модулями Drupal, регистрируя все хуки текущих включённых модулей.
После этого производится вызов функции menu_execute_active_handler(), которая определяет навигационные меню и преобразует запросы страниц в вызовы функций, привязанные к путям на сайте. Также внутри данной функции производится вызов:
Функции drupal_deliver_html_page(), которая возвращает данные страницы в виде HTML в браузер пользователя. Внутри этой функции вызывается функция drupal_render(), которая не только выводит данные, но и сохраняет в один из сегментов кеша и достаёт их оттуда при их наличии вместо повторной генерации страницы с использованием шаблонизатора.
Функции drupal_page_footer(), которая устанавливает кеш страницы ('cache_path' и 'cache_bootstrap'), если это необходимо, и позволяет модулям реагировать на закрытии страницы по hook_exit (). Тут же при необходимости производится запуск Cron.
В общем случае:
Производится инициализация всех необходимых переменных и функций.
Производится проверка, имеется ли кеш по данному URL ({cache_page}). Если имеется, то он возвращается, иначе производятся дальнейшие действия.
Производится проверка имеется ли кеш полей ({cache_field}), контента ({cache_filter}), меню ({cache_menu}), блоков ({cache_block}), а так же изображений ({cache_image}) и алиасов ({cache_path}). Если кеш не имеется, то производится операция по получение и обработки необходимых данных с сохранением в кеш. Полученные данные передаются в функцию темизации.
Функция темизации строит страницу и кеширует её по данному URL ({cache_page}).
Данные возвращаются пользователю.
Программная работа с кешем в Drupal 7.X
Самым распространённым вариантом работы с кешем является сохранение данных разрабатываемого модуля в кеш используя функцию cache_set. А также извлечение их из него, используя функцию cache_get.
<?php
// Проверяем, имеется ли кеш с именем: my_module_data.
if ($cache = cache_get('my_module_data')) {
// Возвращаем его данные, если он имеется.
return $cache->data;
}
else {
// Если кеш отсутствует, реализуем построение данных.
$my_data = 'Тестовые данные для кеширования';
// Сохраняем данные в кеш с именем: my_module_data сегмента: {cache}.
cache_set('my_module_data', $my_data, 'cache');
// Возвращаем данные.
return $my_data;
}
?>
Очистить данные кеша с именем my_module_data можно, вызвав одну из функций:
<?php
// Очистим данные кешей с истёкшим сроком годности
// и времени (если кешировались на определённое время).
cache_clear_all();
// Полностью очистим сегмент {my_module_data}.
cache_clear_all('*', 'my_module_data', TRUE);
// Удалим из сегмента {my_module_data} записи,
// у которых Cache ID начинается c 'my_module'.
cache_clear_all('my_module', 'my_module_data', TRUE);
?>
Через интерфейс администратора сайта это можно сделать на странице: example.com/admin/config/development/performance нажав кнопку Очистить кеш.
Если необходимо, чтобы данные были закешированы на определённое время и не зависели от нажатия кнопки очистки кеша на странице example.com/admin/config/development/performance, то достаточно добавить в функцию cache_set дополнительный параметр — на сколько секунд кешировать данные.
<?php
cache_set('my_module_data', $my_data, 'cache', time() + 360);
?>
Drupal позволяет создавать свои сегменты кеша для хранения данных. Создадим для этого модуль: mymodule. Для этого в каталоге сайта: ./sites/default/modules создадим папку: mymodule. В ней создадим файл описания модуля mymodule.info с содержимым:
name = My module
description = "Тестовый модуль для создания своего сегмента кеша."
core = 7.x
version = 7.x-1.x-dev
files[] = mymodule.module
Так же создадим файл mymodule.install в котором, используя hook_schema(), создадим новую таблицу для хранения данных своего сегмента кеша:
<?php
/**
* Implements hook_schema().
*/
function mymodule_schema() {
// Копируем схему таблицы сегмента: {cache}.
// Это позволяет не описывать самим поля своей таблицы
// так как нам необходима идентичная таблица.
$schema['mymodule'] = drupal_get_schema_unprocessed('system', 'cache');
$schema['mymodule']['description'] = 'Cache table stores some example data.';
return $schema;
}
Последний шаг перед тем как начать использовать функцию cache_set с указанием созданного нами сегмента — создать фал mymodule.module и позаботиться об автоматическом сбросе кеша при нажатии на кнопку очистки кеша на странице example.com/admin/config/development/performance.
<?php
/**
* Implements hook_flush_caches().
*/
function mymodule_flush_caches() {
// Возвращаем имя собственного сегмента для очистки данных кеша в нём.
return array('mymodule');
}
Включаем модуль на странице example.com/аdmin/modules, после чего, например, сохраним в собственный сегмент кеша созданного модулем, данные:
<?php
cache_set('my_module_data', 'Строка сохранённая в собственный сегмент кеша', 'mymodule');
?>
Существует малоизвестная функция cache_is_empty, с помощью которой можно узнать, хранятся ли в кеше с заданным именем какие-либо данные:
<?php
cache_is_empty('my_module');
?>
Как работать с кешем в Drupal 6.X и 8.X, можно всегда посмотреть в подмодуле cache_example, модуля: examples со страницы: www.drupal.org/project/examples.
Вынесение кеша из базы данных
Кеш сегментов можно перенести, например в Memcached или Redis.
У каждого хранилища имеются свои сторонники. Помогавший в написании статьи Evgeniy Maslovskiy (Spleshka, www.drupal.org/u/spleshka) является сторонником Memcached, и на его сайте подробно описана интеграция Memcached и Drupal. В описанном им модуле интеграции с Memcached имеются дополнительные плюсы перед другими решениями: это обход подключения к БД при получении кеша и обход вызовов hook_exit() и hook_boostrap() в других модулях.
В данной статье рассмотрим вынесение кеша в Redis, который мне нравится более простой настройкой по сравнению с Memcached. А сравнительные тесты скорости обоих хранилищ рассмотрим в другой статье.
Настройку Redis на сервере можно найти в интернет. Модуль интеграции с Drupal скачаем со страницы www.drupal.org/project/redis и распакуем в директорию ./sites/all/modules. После чего внесём изменения в конфигурационный файл Drupal ./sites/default/settings.php, добавив в него строки:
$conf['redis_client_interface'] = 'PhpRedis';
$conf['redis_client_host'] = $relationships['redis'][0]['host'];
$conf['redis_client_port'] = $relationships['redis'][0]['port'];
// Имя используемой библиотеки в PHP для соединения с Redis.
$conf['redis_client_interface'] = 'PhpRedis';
$conf['cache_backends'][] = 'sites/all/modules/redis/redis.autoload.inc';
$conf['cache_default_class'] = 'Redis_Cache';
// Пример как сегмент кеша {cache_form} оставить для хранения в базе данных.
$conf['cache_class_cache_form'] = 'DrupalDatabaseCache';
// Пример как собственный сегмент кеша созданы в модуле ранее в статье: {mymodule}
// перенести для хранения в Redis.
$conf['cache_class_mymodule'] = 'Redis_Cache';
Не забывайте читать файл README.txt в папке модуля, так как в нём описаны все настройки модуля.
Где это можно применить?
Один из вариантов применения описанных знаний вынесение части данных сайта в блоки загружающиеся после основной загрузки страницы, как для ускорения загрузки сайта, так и для общего ускорения работы сайта. Достаточно лишь взять за основу модуль Ajax Blocks, интегрировать его с модулем High-performance JavaScript callback handler, а кеш данных поместить в своё хранилище в Redis, используя данную статью.