Ноя
18
2021

Всё о кешировании в Битрикс: D7, тегированный кеш, кеш в компонентах.

Что такое кеш и для чего он нужен

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

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

Представим пример, есть страница списка новостей в какой-то категории, первая страница. На странице требуется проверить права доступа пользователя, получить только активные элементы из базы данных, учесть раздел. Новости добавляются 1-2 раза в день, при этом каждый час на странице бывают сотни посетителей.

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

Как работает кеширование в Битрикс

Принцип работы кеша в Битрикс

В битрикс есть несколько реализаций кеша:

  • Единый класс в новом API D7 Bitrix\Main\Data\Cache
  • Классы из старого ядра:
    - CPageCache - для кеширования только HTML
    - CPHPCache - для кеширования HTML и переменных

В начале использования кеша мы должны:

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

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

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

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

Еще в Битрикс можно помечать кеш тегами.

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

Тегированный кеш в Битрикс

Новое универстальное АПИ кеширования в Битрикс на D7

В новом API D7 в Битрикс появился замечательный класс Bitrix\Main\Data\Cache. Он позволяет кешировать HTML вывод и переменные.

Пример работы с классом:

use \Bitrix\Main\Data\Cache;

$cache = Cache::createInstance(); // Служба кеширования

$cachePath = 'mycachepath'; // папка, в которой лежит кеш
$cacheTtl = 3600; // срок годности кеша (в секундах)
$cacheKey = 'mycachekey'; // имя кеша

if ($cache->initCache($cacheTtl, $cacheKey, $cachePath))
{
    $vars = $cache->getVars(); // Получаем переменные
    $cache->output(); // Выводим HTML пользователю в браузер
}
elseif ($cache->startDataCache())
{
    $vars = [
        'date' => date('r'),
        'rand' => rand(0, 9999), // Если данные закешированы - число не будет меняться
    ];
    
	echo '<b>Какие-то данные выводятся пользователю (Если кеш не работает, то это число будет меняться: '.rand(0, 9999).')</b>';
	
    // Если что-то пошло не так и решили кеш не записывать
    $cacheInvalid = false;
    if ($cacheInvalid)
    {
        $cache->abortDataCache();
    }
    
    // Всё хорошо, записываем кеш
    $cache->endDataCache($vars);
}

// Данные будут обновляться раз в час
print_r($vars);

Чтобы проверить что всё действительно работает:

  1. Создайте новую страницу на сайте
  2. В контент вставьте код, который выше
  3. Откройте страницу в своем браузере (проверьте чтобы в адресной строке не было параметра clear_cache)
  4. Вы увидите что числа, которые в коде заданы случайными, замирают и не обновляются с обновлением страницы. Это потому что код выполнился 1 раз и результат сохранился.
  5. Если на панели эрмитажа нажать Сбросить кеш - то цифры обновятся
Работа кеширования HTML в Битрикс
Числа, на которые указывают стрелки не обновляются при обновлении страницы.

Получение экземпляра класса Bitrix\Main\Data\Cache

$cache = Bitrix\Main\Data\Cache::createInstance()

Методы класса Bitrix\Main\Data\Cache

Статические методы:

  1. \Bitrix\Main\Data\Cache::createInstance() - создает объект для последующей работы не статических методов.
  2. \Bitrix\Main\Data\Cache::clearCache($initDir) - очищает кеш в папке /bitrix/cache/$initDir.

Не статические методы:

  1. $cache->initCache($TTL, $uniqueString, $initDir = false, $baseDir = "cache") - инициализация кеша. Проверяет существование кеша, вычитывает данные.

    $TTL - время жизни в секундах
    $uniqueString - уникальное имя
    $initDir - папка, где будет кеш (внутри $baseDir). Если не указать, то будет браться из текущей запрошенной страницы -> для каждой отдельной страницы будет создаваться новый кеш, хотя данные там одни и те же -> объем кеша будет огромный, а пользы будет мало. Рекомендуется всегда указывать папку.
    $baseDir - папка, внутри /bitrix. Менять не рекомендуется, потому что кеш должен храниться в /bitrix/cache.
  2. $cache->startDataCache($TTL = false, $uniqueString = false, $initDir = false, $vars = array(), $baseDir = "cache") - начинает процесс кеширования. Дальше данные, которые выводятся в браузер, будут записываться (используется буфер вывода PHP).

    Все параметры как у initCache, только есть еще $vars
    $vars - переменные, которые сохранятся в кеш. Далее в коде переменные можно еще передать в endDataCache(). Параметр избыточный, передавать сюда данные не рекомендуется.

    Если вы вызывали initCache(), то параметры в startDataCache() передавать уже не стоит, вызывайте без параметров.
  3. $cache->abortDataCache() - останавливает кеширование.
  4. $cache->endDataCache($vars = false) - сохраняет кеш.

    $vars - переменны, которые стоит сохранить. Если переменные передаются сюда, то переданные в startDataCache() - игнорируются.
  5. $cache->output() - выводит в браузер HTML из кеша.
  6. $cache->getVars() - получает переменные из кеша.
  7. $cache->clean($uniqueString, $initDir = false, $baseDir = "cache") - удаляет кеш с именем $uniqueString в папке $initDir.
  8. $cache->cleanDir($initDir = false, $baseDir = "cache") - удаляет весь кеш в папке $initDir.

Мы можем не использовать вызов $cache->output(), если работаем только с переменными. Если работаем только с HTML - так же можем не вызывать $cache->getVars().

HTML не требуется передавать в какую-то функцию, чтобы он попал в кеш, просто отдавайте в браузер. Переменные можно сохранять не только скалярные, а все, которые можно сериализовать (массивы, объекты...) (О сериализации на php.net).

Хранение кеша по умолчанию в /bitrix/cache.

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

Тегированный кеш в Битрикс D7 (он же Сache Dependencies)

В дополнение к основному классу кеширования, есть служба для установки тегов на кеш.

Рассмотрим пример с инфоблоком. Есть инфоблок Новости (ID 4), данные из него выводятся на главной странице, на странице списка новостей, так же для каждой новости есть детальная страница. Это всё отдельные файлы кеша. Когда мы добавляем какую-то новость, мы не знаем кеш каких страниц надо сбросить. API инфоблока говорит сбросить кеш по тегу iblock_id_4, а к тегу уже привязаны десятки файлов, которые удаляются.

Своего кеша у taggedCache нет, теги хранятся в базе данные в таблице b_cache_tag.

Пример работы кеширования с тегами:

use \Bitrix\Main\Data\Cache;
use \Bitrix\Main\Application;

$cache = Cache::createInstance(); // Служба кеширования
$taggedCache = Application::getInstance()->getTaggedCache(); // Служба пометки кеша тегами

/*
 * Чтобы тегированный кеш нашел что ему сбрасывать, необходим
 * одинаковый путь в $cache->initCache() и  $taggedCache->startTagCache()
 * У нас путь указан в $cachePath
 */
$cachePath = 'mycachepath';
$cacheTtl = 3600;
$cacheKey = 'mycachekey';

if ($cache->initCache($cacheTtl, $cacheKey, $cachePath)) {
    $vars = $cache->getVars();

    /*
     * Еще тут можно вывести данные в браузер, через $cache->output();
     * Тогда получится замена классу CPageCache
     */
} elseif ($cache->startDataCache()) {
    // Начинаем записывать теги
    $taggedCache->startTagCache($cachePath);
    $vars = [
        'date' => date('r'),
        'rand' => rand(0, 9999), // Если данные закешированы - число не будет меняться
    ];

    // Добавляем теги

    // Кеш сбрасывать при изменении данных в инфоблоке с ID 1
    $taggedCache->registerTag('iblock_id_1');

    // Кеш сбрасывать при изменении данных в инфоблоке с ID 2
    $taggedCache->registerTag('iblock_id_2');

    // Если что-то пошло не так и решили кеш не записывать
    $cacheInvalid = false;
    if ($cacheInvalid) {
        $taggedCache->abortTagCache();
        $cache->abortDataCache();
    }

    // Всё хорошо, записываем кеш
    $taggedCache->endTagCache();
    $cache->endDataCache($vars);
}

// Данные будут обновляться раз в час или при обновлении данных в инфоблоках 1 и 2
print_r($vars);

Очистка кеша по тегу:

use \Bitrix\Main\Application;

$taggedCache = Application::getInstance()->getTaggedCache(); // Служба пометки кеша тегами

/*
 * Где-то на отдельной странице чистим кеш по тегу.
 * В данному случае очистится кеш, который зависит от 2 инфоблока
 */
$taggedCache->clearByTag('iblock_id_2');

Получение экземпляра класса taggedCache:

$taggedCache = \Bitrix\Main\Application::getInstance()->getTaggedCache();

Методы класса тегированного кеша

  1. $taggedCache->startTagCache($relativePath) - путь, который будет очищаться при удалении кеша по тегу. Путь передавать тот же, что и в $cache->initCache().
  2. $taggedCache->endTagCache() - окончание. Сохраняет привязку путей к тегам в базу данных.
  3. $taggedCache->abortTagCache() - останавливает кеширование. Вызывать когда запущено кеширование, но оно уже не требуется.
  4. $taggedCache->registerTag($tag) - привязывает тег к пути кеша, указанному ранее при вызове startTagCache().
  5. $taggedCache->clearByTag($tag) - очистка кеша по тегу.

Стандартные теги в Битрикс

Модуль Универсальные списки (lists)
LISTS_ELEMENT_LIVE_FEED - помечается, но не чистится
lists_list_{$iblockId} - очищается при редактировании списка, который работает через инфоблок $iblockId
lists_list_any - чистится при редактировании любого списка

Модуль Веб-мессенджер (im)
IM_CONTACT_LIST - при изменении цвета статуса пользователя

Модуль Блог (blog)
blog_post_{$postId} - при изменении поста
blog_post_getsocnetperms_{$postId} - при изменении прав поста (при присваивании определенным постам привязок к рабочим группам)

Модуль Обучение (learning)
{$entityTypeId}{$entityId} - при добавлении оценки сущностям, чьи $entityTypeId равны LEARN_CHAPTER, LEARN_LESSON, LEARN_COURSE. LEARN_COURSE{$courseId} - При обновлении курса
LEARNING_GROUP_{(int) $groupId / 100} - обновление кеша сотни группы. При добавлении/изменении/удалении группы
LEARNING_GROUP - При добавлении/изменении/удалении групп

Модуль rest
bitrix24_left_menu - при смене прав приложения через Bitrix\Rest\AppTable::setAccess()

Модуль wiki
wiki_{$wikiId} - при изменении вики страницы

Модуль forum
forum_{$forumId}
forum_topic_{$topicId}
forum_msg_count{$forumId} - при изменений сообщений форума

Модуль инфоблоков (iblock)
iblock_property_enum_{$propertyId} - при изменении свойства типа перечисление
iblock_id_new - при добавлении инфоблоков
iblock_id_{$iblockId} - при изменении инфоблока

Модуль социальной сети (socialnetwork)
SONET_LOG_{$logId} - при изменении записи в живой ленте
sonet_features_{$entityType}_{$entityId} - при добавлении функционала группе sonet_feature_{$featureId} - при добавлении права на функционал
sonet_features2perms_{$permId} - при удалении права на функционал
sonet_search_{$entityType}_{$entityId}
sonet_user2group_G{$groupId} - при изменении членства пользователей в группах. При удалении группы sonet_user2group_U{$userId} - при изменении членства пользователей в группах
sonet_user2group - при изменении членства пользователей в группах. При удалении группы sonet_group_view_U{$userId}
sonet_group_{$groupId} - при удалении группы
sonet_group - при удалении группы
sonet_group_activity - при установке активности группы
sonet_subscription_{$subscriptionCode} - при добавлении, обновлении подписки. При удалении подписки с указанием кода. При удалении через указание ID не вызывается
sonet_subscription - при удалении всех подписок некоего пользователя
SONET_LOG_FOLLOW_{$userId} - при изменении подписок пользователя в живой ленте
blogpost_important_all - при редактировании важных записей
{$optionName}{$postId} - при редактировании опций блога пользователя
{$optionName}{$postId}_{$userId}} - при редактировании опций блога пользователя {$optionName}_USER_{$userId}} - при редактировании опций блога пользователя

Модуль calendar
calendar_user_{$userId} - при изменении событий в календаре пользователя
CALENDAR_EVENT_LIST - при изменении раздела в календаре

Модуль Интернет магазин (sale)
sale-location-data - при изменении местоположений

Главный модуль (main)
RV_CACHE - при удалении пользователем оценки
{$componentName} - при очистке кеша компонента
USER_CARD_{$chunkNum} - при изменении пользователя, где $chunkNum = (int) $userId / TAGGED_user_card_size; Кеш не на отдельных пользователей, а на пачку
USER_CARD - при редактировании пользователя с битриксовой авторизацией
EXTERNAL_USER_CARD - при редактировании пользователя с внешней авторизацией
USER_NAME_{$userId} - при изменении пользователя. При обновлении может вызываться не всегда, а только если передано хотя бы одно из следующих полей: NAME, LAST_NAME, SECOND_NAME, ACTIVE, LOGIN, EMAIL, PERSONAL_GENDER, PERSONAL_PHOTO, WORK_POSITION, PERSONAL_PROFESSION, PERSONAL_WWW, PERSONAL_BIRTHDAY, TITLE, EXTERNAL_AUTH_ID, UF_DEPARTMENT, AUTO_TIME_ZONE, TIME_ZONE, TIME_ZONE_OFFSET

Модуль голосований (vote)
vote_form_channel_{$voteId} - при изменении каналов голосования
vote_form_vote_{$voteId} - при изменении голоса, вопросов
vote_form_question_{$voteId} - при изменении вопросов, ответов
vote_form_answer_{$voteId} - устаревшее

Модуль landing
landing_page_{$landingId} - при изменении лендинга

Модуль веб форм (form)
form_{$formId} - при изменении формы, где {$formId} - ID формы.

Что делать, если тегов не хватает

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

Например, нам нужен сброс кеша при редактировании сайтов.

Добавим в init.php такой код:

AddEventHandler("main", "OnSiteDelete", "clearSiteCacheByTag", 10000);
AddEventHandler("main", "OnBeforeSiteAdd", "clearSiteCacheByTag", 10000);
AddEventHandler("main", "OnBeforeSiteUpdate", "clearSiteCacheByTag", 10000);

function clearSiteCacheByTag() {
	$taggedCache = \Bitrix\Main\Application::getInstance()->getTaggedCache();
	$taggedCache->clearByTag('site_edit');
}

Теперь при создании, обновлении и удалении сайтов будет удаляться весь кеш по тегу site_edit.

Управляемый кеш (managed cache)

Почему этот кеш называется управляемый - не ясно, управлять им особо не выходит. Managed cache в Битрикс это такая прослойка над обычным классом кеширования для более быстрой работы и компактного кода. Используется чаще всего для кеширования выборок отдельных таблиц.

Сразу начну с минусов:

  1. К этому кешу не привязать теги (без горы костылей).
  2. В старых версиях (где-то до 19 версии) при вызове cleanAll() управляемого кеша, удаляется так же весь кеш, на который установлены теги.

Простой пример работы с классом:

$managedCache = Bitrix\Main\Application::getInstance()->getManagedCache();

$uniqId = 'my_cache_key'; // ключ, по которому сохраняем и получаем данные
$ttl = 30; // время жизни кеша

$managedCache->read($ttl, $uniqId); // считываем кеш с диска

$res = $managedCache->get($uniqId); // получаем данные из считанного файла
if ($res === false) { // если данных нет
    $res = date('r'); // то генерируем данные, тут, например, у нас тяжелая логика

    $managedCache->set($uniqId, $res); // сохраняем данные (пока будут храниться в памяти)
}

var_dump($res); // работаем с данными

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

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

Крупные недочеты:

  1. Пока на диске есть кеш по вашему ключу, новые данные сохранить не получится. Надо сперва удалить старые, а потом вызвать read(), хотя мы точно знаем что данных нет. Read() надо вызывать, потому что там устанавливается ttl, а он обязателен для сохранения.
  2. В функции getImmediate() требуется указывать ttl, хотя он не используется. TTL нужен только при сохранении кеша. При том TTL не хватает при вызове setImmediate(), там то он нужен. Но так как setImmediate() не принимает в параметрах TTL, перед его вызовом требуется вызов read() для указания TTL.

В остальном работать можно.

Получение, замена кеша с использованием get/set функций.

$managedCache = Bitrix\Main\Application::getInstance()->getManagedCache();

$uniqId = 'my_table2';
$ttl = 30;

$managedCache->read($ttl, $uniqId);

// Получить данные из заранее считанного файла
$res = $managedCache->get($uniqId);
var_dump($res);

$dataIsValid = false;
if (!$dataIsValid) {
    $managedCache->clean($uniqId);
    $managedCache->read(900, $uniqId);

    // Сохранит в файл при окончании работы скрипта Битрикс
    $managedCache->set($uniqId, [
        'data_table_2' => rand(0, 999) . date(' :: r'),
    ]);
}

Получение, замена кеша с использованием getImmediate/setImmediate функций.

$uniqId = 'my_table3';
$ttl = 30;

// Считать файл и получить данные. Информация о считанном файле не сохраняется, поэтому, если вы захотите записать данные, придется всё равно вызывать read(). $ttl тут ни на что не влияет, параметр этот лишний
$res = $managedCache->getImmediate($ttl, $uniqId);
var_dump($res);

$dataIsValid = false;
if (!$dataIsValid) {
    // Вызов clean() очищает ресурсы после работы, поэтому если хотите после работать с кешем, то надо заново вызывать read()
    $managedCache->clean($uniqId);
    $managedCache->read(900, $uniqId);

    // Сохранит в файл немедленно. Перед этим вызовом обязателен вызов read()
    $managedCache->setImmediate($uniqId, [
        'data_table_3' => rand(0, 999) . date(' :: r'),
    ]);
}

Получение экземпляра класса managedCache:

$managedCache = Bitrix\Main\Application::getInstance()->getManagedCache();

Методы класса Bitrix\Main\Data\ManagedCache:

  1. $managedCache->read($ttl, $uniqueId, $tableId = false) - читает файл кеша, на случай если мы какие-то данные добавим. $ttl - время жизни, $uniqueId - уникальное имя, $tableId - папка в которой сохранится файл (относительно /bitrix/managed_cache). Папку указывать не обязательно.
  2. $managedCache->getImmediate($ttl, $uniqueId, $tableId = false) - получение данных из кеша без предварительного считывания. $ttl - ни на что не влияет, не понятно для чего добавили его, остальное как в пункте 1.
  3. $managedCache->get($uniqueId) - получить данные, которые должны быть предварительно считаны с помощью read функции из пункта 1.
  4. $managedCache->set($uniqueId, $val) - добавление данных по имени $uniqueId. Чтобы данные добавились и сохранились, предварительно надо вызвать read из пункта 1. Значение может быть как скалярным значением, так и другим, которое можно сериализовать (О сериализации на php.net). Данные сохраняются на диск при подключении эпилога.
  5. $managedCache->setImmediate($uniqueId, $val) - установка значения с последующим моментальным сохранением данных на диск, предварительно должна быть вызвано read(). Буфер при этом очищается и если вы снова захотите работать с $uniqueId, то надо будет вызвать заново read().
  6. $managedCache->clean($uniqueId, $tableId = false) - удаление кеша по имени и папке
  7. $managedCache->cleanDir($tableId) - удаление всех кешей в папке
  8. $managedCache->cleanAll() - удаление всего управляемого кеша. Где-то до 19 версии битрикс удаляется почему-то еще и весь тегированный кеш.
  9. Bitrix\Main\Data\ManagedCache::finalize() - сохранение данных на диск. Автоматически вызывается в эпилоге битрикса.

Кеширование ORM запросов и Highload блоков.

При разработке модулей часто требуется хранение данных в базе данных, операции при этом одни и те же: Create, Read, Update, Delete (создание, чтение, обновление и удаление данных). Для быстрой работы с новыми таблицами и стандартизированного подхода создан ORM. Оно надо чтобы не писать каждый раз заново логику валидации данных, генерацию запросов, API получения данных, создания событий.

Для реализации этих целей введены понятия:

  1. Cущности (Bitrix\Main\Entity\Base);
  2. Поля сущностей (Bitrix\Main\Entity\Field и его наследники);
  3. Датаменеджер (Bitrix\Main\Entity\DataManager).

Сущность описывает таблицу в БД. Датаменеджер производит операции выборки и изменения сущности.

Более подробно можно почитать в курсе битрикса про ORM. А мы рассмотрим кеш.

D7 классы можно определить по наличию пространства имен, которое отделяется обратными слешами от имени класса. Например, в старом API работа с группами пользователей идет через класс CGroup, в новом же через Bitrix\Main\GroupTable. Наличие символов "\" говорит нам о том, что это новый API. В D7 большинство сущностей построено на основе ORM, а это нам говорит что наконец у всех классов единая сигнатура методов. Больше не будет такого у что одних первый параметр это сортировка, у других сортировка это второй параметр, а первый это фильтр...

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

$res = \Bitrix\Main\GroupTable::getList([
    'cache' => ['ttl' => 3600],
]);

При добавлении/изменении/удалении данных кеш автоматически сбрасывается.

Что не так с HL блоками

Хайлоад блоки тоже работают на основе ORM, та же сигнатура методов, но вот методы изменения данных переопределены и сброс кеширования туда не добавлен! Почему? Загадка...

То есть используете вы код:

$hlbl = 2; // Указываем ID нашего highloadblock блока к которому будет делать запросы.
$hlblock = \Bitrix\Highloadblock\HighloadBlockTable::getById($hlbl)->fetch();
$entity = \Bitrix\Highloadblock\HighloadBlockTable::compileEntity($hlblock);

$entity_data_class = $entity->getDataClass();

$rsData = $entity_data_class::getList([
    'cache' => ['ttl' => 360000],
]);

while($arData = $rsData->Fetch()){
    var_dump($arData);
}

Изменяете как-то данные, а кеш по прежнему дает вам устаревшие значения до тех пор, пока не истечет срок его годности.

Решается проблема добавлением такого кода в init.php:

$eventManager = \Bitrix\Main\EventManager::getInstance();

$eventManager->addEventHandler('', 'BrandReferenceOnAfterAdd', 'clearBrandReferenceCache');
$eventManager->addEventHandler('', 'BrandReferenceOnAfterUpdate', 'clearBrandReferenceCache');
$eventManager->addEventHandler('', 'BrandReferenceOnAfterDelete', 'clearBrandReferenceCache');

function clearBrandReferenceCache($event)
{
    $event->getEntity()->cleanCache();
}

Где BrandReference - имя вашего HL блока. Данный код удаляет кеш HL блока после изменения данных.

Я бы такое событие повесил сразу на все хайлоад блоки, но так не получается, надо знать и перечислять все имена блоков.

Старый API кеширования

Использовать уже не стоит эти старые классы, есть удобные новые. Описываю для пониманию работы старого кода. Вместо единого нового класса есть 2 старых: CPageCache - для кеширования только HTML, - CPHPCache - для кеширования HTML и переменных.

Получение экземпляра класса CPHPCache

$obCache = new CPHPCache;

Методы CPHPCache:

  1. $obCache->InitCache($TTL, $uniq_str, $initdir=false, $basedir = "cache") - проверяет наличие кеша на диске, инициализирует параметры. Переданные сюда параметры можно уже не передавать в StartDataCache().
  2. $obCache->StartDataCache($TTL=false, $uniq_str=false, $initdir=false, $vars=Array(), $basedir = "cache") - где
    $TTL - время жизни кеша
    $uniq_str -имя
    $initdir - папка относительно $basedir
    $vars - переменные, которые в будущем могут сохраниться в кеш
    $basedir - папка относительно /bitrix, лучше не трогать
  3. $obCache->EndDataCache($vars=false) - завершает кеширование. Сохраняет HTML вывод, который был после начала кеширования, и переменные из $vars на диск.
  4. $obCache->GetVars() - получает сохраненные переменные из кеша.
  5. $obCache->Output() - выводит сохраненный в кеше HTML.
  6. $obCache->AbortDataCache() - отменяет начатое кеширование.

Пример работы с классом:

// создаем объект
$obCache = new CPHPCache;

// время кеширования - 30 минут
$life_time = 30*60;

// формируем идентификатор кеша в зависимости от всех параметров
// которые могут повлиять на результирующий HTML
$cache_id = $ELEMENT_ID.$USER->GetUserGroupString();

if($obCache->InitCache($life_time, $cache_id, "/")) {
    // получаем закешированные переменные
    $vars = $obCache->GetVars();

    // выводим на экран содержимое кеша
    $obCache->Output();
} else if ($obCache->StartDataCache()) {
    $vars = [
        /* какие-то данные, например, из базы */
    ];

    echo '<b>Какие-то данные, которые попадут в кеш</b>';
    
    $obCache->EndDataCache($vars);
}

В CPageCache всё аналогично классу CPHPCache, только нет работы с переменными.

Получение экземпляра класса CPageCache:

$obCache = new CPageCache;

Методы класса:

  1. $obCache->InitCache($TTL, $uniq_str, $initdir = false, $basedir = "cache")
  2. $obCache->StartDataCache($TTL, $uniq_str=false, $initdir=false, $basedir = "cache")
  3. $obCache->EndDataCache()
  4. $obCache->Output()
  5. $obCache->AbortDataCache()

Кеш в компонентах

На основе описанных ниже принципов работают все стандартные компоненты и некоторые от самостоятельных разработчиков. Раздел описывает логику таких компонентов, как bitrix:news.list, bitrix:news.detail, bitrix:catalog.section, bitrix:catalog.element и других.

Для работы кеша в параметры вызова компонента добавляются параметры:

"CACHE_GROUPS" => "N", // Для разных групп пользователей - один кеш
"CACHE_TIME" => "36000000", // Время кеширования в секундах
"CACHE_TYPE" => "A", // Тип кеширования. N - выключено, A - включено, кеш автоматически сбрасывается при изменении данных, Y - включено, но кеш сам не сбрасывается

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

В компоненте, если предусмотрено кеширование, всегда вызывается метод startResultCache() класса CBitrixComponent. Внутри этого метода, если включено в параметрах вызова компонента, запускается кеширование (обычное и сразу тегированное). Теги нигде тут не устанавливаются.

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

Если требуется установить дополнительные теги в компоненте, то делать это стоит после вызова startResultCache() и до includeComponentTemplate().

В общем виде код компонента выглядит так:

<?
if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();

/*
 * Валидация параметров
 */

if(!isset($arParams["CACHE_TIME"]))
    $arParams["CACHE_TIME"] = 36000000;

$arParams["IBLOCK_TYPE"] = trim($arParams["IBLOCK_TYPE"]);
$arParams["IBLOCK_ID"] = trim($arParams["IBLOCK_ID"]);
$arParams["SORT_BY1"] = trim($arParams["SORT_BY1"]);

// Требуется для того, чтобы кеширование сохранило данные из $arResult
$arResult = &$this->arResult;

/*
 * Начало кеширования
 * 
 * Если кеш уже существует - startResultCache() возвращает false,
 * заполняет $arResult, выводит верстку.
 */
if($this->startResultCache(...))
{
    /*
     * Код тут выполняется только когда нет кеша
     */
    $iterator = CIBlockElement::GetList(...);
    
    /*
     * При вызове GetNext() кеш помечается тегом инфоблока
     */
	while ($arItem = $iterator->GetNext()) {
        $arResult['ITEMS'][] = $arItem;
    }
	$this->setResultCacheKeys(array(
		"ID",
		"IBLOCK_TYPE_ID",
		"NAME",
		"SECTION",
		"ELEMENTS",
	));
	$this->includeComponentTemplate();
}

/*
 * Устанавливаются параметры, которые надо выполнять всегда, в независимости от наличия кеша
 */
$APPLICATION->SetTitle($arResult["NAME"]);

Кеширование завершается и сохраняется на диск внутри вызова CBitrixComponent::includeComponentTemplate() . Записывается HTML вывод шаблона, данные из $arResult (по фильтру указанному при вызове CBitrixComponent::setResultCacheKeys()), а так же адреса стилей, файлов js, component_epilog.php, в том числе от вложенных компонентов.

Так как шаблон подключается внутри секции кеширования, в нем тоже можно вызывать CBitrixComponent::setResultCacheKeys(), указывать теги и даже отменять кеширование.

В шаблоне компонента можно создать файлы script.js и style.css. Они будут подключаться автоматически при подключении компонента. Их подключение будет происходить даже когда компонент закеширован.

Методы класса компонентов для кеширования:

Методы класса используются одни и те же что при работе через файл component.php, что через class.php. В случае работы через class.php используются $this->arResult и $this->arParams.

  1. $this->startResultCache($cacheTime = false, $additionalCacheID = false, $cachePath = false) - проверяет наличие кеша компонента, если есть, то заполняет переменную $arResult, выводит HTML в браузер и возвращает false. Если кеша нет, то начинает кеширование, запускает тегированный кеш, возвращает true.
    Так же возвращает true, но не начинает кеширование при выключенном кешировании или если кеширование выбрано авто, но в настройках отключено кеширование компонентов.

    $cacheTime - время кеширования, если передано false, то берется из $arParams['CACHE_TIME']
    $additionalCacheID - кеш зависит от текущего сайта ( SITE_ID), имени компонента, имени шаблона, входных параметров $arParams. Если кеш должен зависеть от каких-либо дополнительных параметров, то их необходимо передать сюда в виде строки.
    $cachePath - Путь к файлу кеша относительно папки кешей. По умолчанию равен "/".SITE_ID.<путь к компоненту относительно bitrix/components>/<еще какая-то строка зависящая от переменной состояния>.
  2. $this->endResultCache() - завершает кеширование, сохраняет кеш на диск, запоминая в том числе HTML вывод шаблона и $arResult. Автоматически вызывается при вызове $this->includeComponentTemplate().
  3. $this->abortResultCache() - завершает кеширование, данные на диск не сохраняет. Метод на случай если кеширование запущено, но произошла ошибка и стоит прервать операцию.
    Если начать кеширование и не завершить его, появится множество багов.
  4. $this->setResultCacheKeys($arResultCacheKeys) - принимает массив ключей по которому будет фильтрация данных в $arResult. Позволяет кешировать не весь результат, а только нужные данные.
    При повторном вызове не затирает старые ключи, а дополняет их.
  5. $this->getCacheID($additionalCacheID = false) - генерирует имя кеша. Если переопределить в своем классе и указывать $cachePath, можно добиться того, что кеш не будет зависеть от текущего сайта.
    $additionalCacheID - данные из вызова startResultCache()
  6. $this->clearResultCache($additionalCacheID = false, $cachePath = false) - чистит кеш компонента, параметры аналогичны startResultCache(). Вместо вызова этой функции, пользуйтесь тегированным кешем.
  7. $this->clearComponentCache($componentName, $siteId = "") - очищает кеш по имени компонента.

Пример работы с кешем в компоненте при работе через class.php:

<? if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true) die();

class MyComponent extends CBitrixComponent
{

    public function executeComponent()
    {
        if ($this->startResultCache()) {

            if (empty($this->arParams['KEY'])) {
                // Если требуется отменить кеш
                $this->AbortResultCache();
                ShowError('Не установлен ключ');
                return;
            }

            /* Тут какая-то логика по заполнению $this->arResult */
            

            $this->arResult['CITY'] = 'Ижевск';

            $this->IncludeComponentTemplate();
        }
    }

}

Чтобы в настройках компонента появилась вкладка с настройками кеширования достаточно добавить параметр CACHE_TIME:

$arComponentParameters = array(
    "PARAMETERS" => array(
        // KEY - какой-то наш параметр, к кешу отношения не имеет
        "KEY" => array(
            "NAME" => "API ключ",
            "TYPE" => "STRING",
            "GROUP" => "BASE",
            "DEFAULT" => "",
        ),

        // Говорим что компонент работает с кешем
        "CACHE_TIME" => array("DEFAULT" => "3600"),
    ),
);

Важно: при подключении в шаблоне других вложенных компонентов, указывайте вышестоящий через четвертый параметр IncludeComponent(). В файле template.php ссылка на вышестоящий компонент в переменной $component. Иначе у вложенного компонента будут проблемы с подключением стилей, скриптов и component_epilog.php.

$APPLICATION->IncludeComponent($componentName, $componentTemplate, $arParams, $parentComponent)

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

Интересно:

  1. В шаблоне компонента можно отменить запись кеша, а так же добавить теги на кеш.
  2. Если вложенные компоненты не используют кеширование, их вывод всё равно будет закеширован в основном компоненте. После этого result_modifier.php и template.php вызываться не будут, работать будет component_epilog.php.
  3. Кеш зависит от параметров вызова компонента. Если в параметрах у вас динамические данные, то для каждого варианта будет свой кеш, в этом случае бывает выгоднее просто не использовать кеширование.
  4. Если вы хотите взаимодействовать из шаблона иным способом, нежели вывод HTML, то делайте это через файл component_epilog.php. Например, не получится устанавливать заголовок страницы из template.php, этот файл 1 раз закешируется и заголовок устанавливаться больше не будет.

Работа с кешем в инфоблоке с большим количеством записей

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

Чтобы решить эту проблему, придется отказаться от стандартного тега. Использовать будем только result_modifier.php и init.php.

Наш план:

  1. Вызов компонента оставить с типом кеширования - Авто
  2. В result_modifier.php сбросить стандартный тег, установить свой.
  3. В init.php добавить события для сброса кеша по своему тегу.

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

Формула для получение тега конкретного элемента:

define('IBLOCK_CHUNK_SIZE', 500);

$iblockId = 5;
$elementId = 10;

$tag = 'iblock_id_' . $iblockId . '_chunk_' . intval($elementId / IBLOCK_CHUNK_SIZE);

Для элементов с ID 1-499 тег будет iblock_id_5_chunk_0, для 500-999 iblock_id_5_chunk_1 и так далее. Но этот тег не получится добавить на страницы списка. Потому что при добавлении или удалении новости на первой странице, происходит сдвиг записей на всех страницах пагинации. При обновлении записи может измениться сортировка и опять же сдвинутся все страницы списка. Поэтому, менять принцип кеширования страниц списка нет смысла.

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

В result_modifier.php шаблона детальной новости добавьте код:

if ($arParams['CACHE_TYPE'] === 'A') {
    $taggedCache = \Bitrix\Main\Application::getInstance()->getTaggedCache();
    $taggedCache->abortTagCache(); // сбрасываем стандартные теги

    $tag = 'iblock_id_' . $arParams['IBLOCK_ID'] . '_chunk_' . intval($arResult['ID'] / IBLOCK_CHUNK_SIZE);

    $taggedCache->startTagCache($this->__component->getCachePath());
    $taggedCache->registerTag($tag); // Ставим свой тег
}

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

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

В result_modifier.php шаблона news.list добавьте:

if ($arParams['CACHE_TYPE'] === 'A') {
    $taggedCache = \Bitrix\Main\Application::getInstance()->getTaggedCache();
    $taggedCache->abortTagCache();

    $taggedCache->startTagCache($this->__component->getCachePath());

    foreach ($arResult['ITEMS'] as $arItem) {
        $tag = 'iblock_id_' . $arParams['IBLOCK_ID'] . '_chunk_' . intval($arItem['ID'] / IBLOCK_CHUNK_SIZE);
        $taggedCache->registerTag($tag);
    }
}

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

В init.php добавьте код:

define('IBLOCK_CHUNK_SIZE', 500);

AddEventHandler("iblock", "OnAfterIBlockElementUpdate", "ClearIblockCacheHandler");
AddEventHandler("iblock", "OnAfterIBlockElementAdd", "ClearIblockCacheHandler");
AddEventHandler("iblock", "OnAfterIBlockElementDelete", "ClearIblockCacheHandler");

function ClearIblockCacheHandler($arFields)
{
    if($arFields['IBLOCK_ID'] == 7 and (! isset($arFields["RESULT"]) or $arFields["RESULT"])) {
        $tag= 'iblock_id_' . $arFields['IBLOCK_ID'] . '_chunk_' . intval($arFields['ID'] / IBLOCK_CHUNK_SIZE);

        $taggedCache = \Bitrix\Main\Application::getInstance()->getTaggedCache();
        $taggedCache->clearByTag($tag);
    }
}

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

Так же можно добавить события на сброс кеша при изменении параметров инфоблока, при изменении свойств и другое. Список всех событий есть по ссылке https://dev.1c-bitrix.ru/api_help/iblock/events/index.php

Так при обновлении одной записи сбрасывается кеш только 500 страниц, а не всех десятков тысяч.

Проблема чрезмерного роста кеша

Рост кеша меню

Частая проблема на сайтах - огромный кеш меню, хотя в нем нет много данных.

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

У компонента есть параметр CACHE_SELECTED_ITEMS, который, по не понятной причине, не выведен в настройки компонента. По умолчанию он устанавливается в Y и кеш начинает зависеть от адреса страницы.

Чтобы отключить это поведение, добавьте такой параметр в вызов меню: "CACHE_SELECTED_ITEMS" => "N"

В итоге получится у вас что-то такое:

<?$APPLICATION->IncludeComponent("bitrix:menu", "top", Array(
   "ROOT_MENU_TYPE" => "top",   // Тип меню для первого уровня
   "MENU_CACHE_TYPE" => "A",   // Тип кеширования
   "MENU_CACHE_TIME" => "3600",   // Время кеширования (сек.)
   "MENU_CACHE_USE_GROUPS" => "N",   // Учитывать права доступа
   ...
   "CACHE_SELECTED_ITEMS" => "N", // Не создавать кеш меню для каждой страницы
   ),
   false
);?>

Сброс кеша в Битрикс

Через админку

Основной способ сброса кеша находится в админке в разделе Настройки -> Настройки продукта -> Автокеширование. Вкладка Очистка файлов кеша.

Варианты очистки:

  1. Только устаревшие - удаление кеша срок годности которого уже истек. Удаляет файлы которые уже не используются.
  2. Все - всё понятно, удалить весь кеш.
  3. Меню - удалить кеш меню.
  4. Весь управляемый
  5. Все страницы HTML кеша - HTML кеш - это часть композитного кеша. Удалить эти данные.
  6. Сайты24 - удалить кеш лендингов сайтов 24.

Другие вкладки на странице:

  1. Кеширование компонентов - тут кнопка включения/выключения кеширования компонентов, но не всех, а тек у кого режим Авто + Управляемое.
  2. Управляемый кеш - включение/отключение управляемого кеша.

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

Удалить кеш кнопкой на эрмитаже

Удалить кеш Битрикс

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

Сбросить кеш програмно

Так же можно удалить кеш через API, но для этого надо знать как к нему обратиться.

Удалить кеш по тегу (в данном случае удалится кеш инфоблока 2):

$taggedCache = \Bitrix\Main\Application::getInstance()->getTaggedCache();
$taggedCache->clearByTag('iblock_id_2');

Удалить кеш зная имя и папку:

$cache = Bitrix\Main\Data\Cache::createInstance()
$cache->clean($uniqueString, $initDir);

Удалить весь кеш в папке:

$cache = Bitrix\Main\Data\Cache::createInstance()
$cache->cleanDir($initDir);

Другие варианты сброса кеша

Если кеш удаляется через админку долго, можно ему помочь удалив файлы на сервере через SSH или FTP. Удалите вручную все файлы в папках bitrix/cache, bitrix/managed_cache.

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

Другое

Что за папки /bitrix/cache/js и /bitrix/cache/css

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

Для чего глобальная переменная $CACHE_MANAGER

Переменная является посредником для управляемого и тегированного кеша. Используется устаревшими частями кода Битрикс, не удаляется для сохранения обратной совместимости.

Объем кеша: 0 Б при работе memcached.

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

Про кеширование в браузере

Браузер кеширует статические ресурсы (стили, скрипты, картинки...). Браузер не кеширует HTML страницу. (Кеширование HTML браузером можно сделать при работе через заголовок 304 Not Modified, но такое делают редко).

Если вы добавляете скрипты через штатное API Битрикс (например $APPLICATION->AddHeadScript(), Bitrix\Main\Page\Asset::getInstance()->addCss(), файлы script.js, style.css в шаблоне...), то браузер будет сбрасывать кеш скриптов и стилей при изменении файла на сервере. Так происходит потому что Битрикс добавляет к адресу файла временную метку.

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

К картинкам временная метка не добавляется, поэтому при их обновлении могут быть проблемы.

Кеш стилей и скриптов не сбрасывается, когда вы подключаете их не через API, а тегами в шаблоне.

<script type="text/javascript" src="/bitrix/templates/books/js/script.js"></script>

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

Чтобы заставить браузер не использовать кеш, можно открыть Инстументы разработчика и на вкладке Network поставить галочку Disable cache. Пока инструменты разработчика открыты, кеш будет отключен на текущей вкладке.

Так же можно обновить вкладку сочетанием Shift + F5, тогда кеш браузера сбросится.

Пожалуйста, оцените на сколько вам понравилась статья!
Голосов: 45 Среднее: 5