Битрикс. Управляемый кеш

05.11.2018

Теги: CMSWeb-разработкаБитриксКеширование

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

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

Работа с управляемым кешем состоит из 3-х операций:

  • Cвязь объекта кеша с директорией хранения кеша
  • Задание одного или нескольких тегов
  • Очистка кеша по тегу

Для начала нужно включить механизм управляемого кеширования. Определяем в dbconn.php константу:

define('BX_COMP_MANAGED_CACHE', true);

И проверим, что механизм теперь работает:

if (defined('BX_COMP_MANAGED_CACHE')) {
    ShowNote('Управляемый кеш включен');
}  else {
    ShowError('Управляемый кеш не включен');
}

За работу кеша отвечает глобальный объект $CACHE_MANAGER, который является экземпляром класса CCacheManager:

// тип инфоблока, откуда будем получать элементы
$iblockType = ARTICLES_IBLOCK_TYPE;
// идентификатор инфоблока, откуда будем получать элементы
$iblockId = ARTICLES_IBLOCK_ID;
// символьный код инфоблока, откуда будем получать элементы
$iblockCode = ARTICLES_IBLOCK_CODE;

// условия выборки элементов инфоблока с помощью метода ElementTable::getList()
$getListParams = array(
    'select' => array('ID', 'NAME', 'CODE', 'PREVIEW_TEXT'),
    'filter' => array('=IBLOCK_ID' => $iblockId),
    'limit'  => 3
);
// уникальный идентификатор кеша
$cacheId = md5('\\Bitrix\\Iblock\\ElementTable::getList' . serialize($getListParams));

// время жизни кеша
$cacheTime = 3600;
// путь к директории кеша
$cachePath = '/iblock/' . $iblockType . '/' . $iblockCode;
// создаем объект кеша
$phpCache = new CPHPCache();
 
if (!$phpCache->InitCache($cacheTime, $cacheId, $cachePath)) {
    /*
     * Если кеш пустой или утратил актуальность, получаем данные из базы данных
     */
    // Получаем CACHE_MANAGER из $GLOBALS и привязываем его к нашей директории кеша
    $GLOBALS['CACHE_MANAGER']->StartTagCache($cachePath);
    // Вешаем тег на текущий кеш. У нас тут будет выборка элементов инфоблока,
    // поэтому имеет смысл зарегистрировать тег, зависящий от ID инфоблока
    $GLOBALS['CACHE_MANAGER']->RegisterTag('iblock_id_' . $iblockId);

    // Делаем выборку из базы данных элементов инфоблока
    $result = \Bitrix\Iblock\ElementTable::getList($getListParams);
    while ($arItem = $result->fetch()) {
        $arResult['ITEMS'][] = $arItem;
        // Можно пометить кеш тегами всех выбранных элементов инфоблока
        $GLOBALS['CACHE_MANAGER']->RegisterTag('article_id_' . $arItem['ID']);
    }
    // Можно повесить и какой-то другой (более понятный) тег с говорящим именем
    $GLOBALS['CACHE_MANAGER']->RegisterTag('iblock_code_' . $iblockCode);

    // Завершаем тегирование кеша
    $GLOBALS['CACHE_MANAGER']->EndTagCache();
    // После чего сохраняем результат выборки в кеш
    if ($phpCache->StartDataCache()) {
        $phpCache->EndDataCache(array('dbResult' => $arResult));
    }
} else {
    /*
     * Иначе, получаем данные из кеша
     */
    $arResult = $phpCache->GetVars();
}

Выводим полученные данные:

<section>
<?php foreach ($arResult['ITEMS'] as $arItem): ?>
    <article>
        <h3><?= $arItem['NAME']; ?></h3>
        <p><?= $arItem['PREVIEW_TEXT']; ?></p>
    </article>
<?php endforeach; ?>
<section>

Файл кеша bitrix/cache/content/articles/7c/7cb8ee7684eef61a2bf9e6abb07aca9a.php:

<?
if ($INCLUDE_FROM_CACHE!='Y') return false;
$datecreate = '001541417904';
$dateexpire = '001541421504';
$ser_content = 'a:2:{s:7:"CONTENT";s:0:"";s:4:"VARS";a:1:{s:8:"dbResult";a:1:{s:5:"ITEMS";a:3:{i:0;a:4:{s:2:"ID";
s:3:"347";s:4:"NAME";s:35:"Английский бульдог";s:4:"CODE";s:17:"angliyskiy-buldog";s:12:"PREVIEW_TEXT";s:665:"По
названию («bull» переводится «бык», «dog» — собака) понятно, что бульдог был предназначен для травли быков. Это
зрелище со времён Рима было очень популярно в Европе вплоть до начала девятнадцатого столетия. По вышедшему в
Англии в 1835 году закону были запрещены все бои, в которых участвовали животные. В связи с этим количество
бульдогов в стране резко сократилось.";}i:1;a:4:{s:2:"ID";s:3:"348";s:4:"NAME";s:16:"Далматин";s:4:"CODE";
s:8:"dalmatin";s:12:"PREVIEW_TEXT";s:566:"О точном происхождении далматинов известно мало. Считается, что их
родиной была Далмация, область Балканского полуострова, в честь которой, собственно, и была названа порода,
существующая уже больше двух тысячелетий. Собаки, напоминающие далматинов, украшают древние египетские барельефы
и греческие фрески.";}i:2;a:4:{s:2:"ID";s:3:"349";s:4:"NAME";s:31:"Афганская борзая";s:4:"CODE";s:18:
"afganskaya-borzaya";s:12:"PREVIEW_TEXT";s:565:"Изящная красавица с длинной развевающейся на бегу шелковистой
шерстью, афганская борзая, полна энергии и готова каждую минуту стремительно мчаться наперегонки с ветром. Наблюдая
за её необычайно грациозными движениями, нетрудно представить себе такой элегантный силуэт на фоне бескрайней
азиатской степи.";}}}}}';
return true;
?>

В результате работы приведенного выше кода мы закешируем выборку элементов инфоблока. Сформированный кеш будет находиться в директории bitrix/cache/content/articles, и этот кеш будет помечен тегами iblock_id_5 и iblock_code_articles, а также набором тегов, зависящих от идентификатора элемента, т.е. article_id_347, article_id_348, article_id_349.

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

<?php
/*
 * Файл local/php_interface/init.php
 */

define('ARTICLES_IBLOCK_TYPE', 'content');
define('ARTICLES_IBLOCK_ID', 5);
define('ARTICLES_IBLOCK_CODE', 'articles');

// регистрируем три обработчика событий
AddEventHandler( // при добавлении элемента
    'iblock',
    'OnAfterIBlockElementAdd',
    array(
        'IblockElementEventHandler',
        'OnAfterIBlockElementAdd'
    )
);
AddEventHandler( // при изменении элемента
    'iblock',
    'OnAfterIBlockElementUpdate',
    array(
        'IblockElementEventHandler',
        'OnAfterIBlockElementUpdate'
    )
);
AddEventHandler( // при удалении элемента
    'iblock',
    'OnAfterIBlockElementDelete',
    array(
        'IblockElementEventHandler',
        'OnAfterIBlockElementDelete'
    )
);

class IblockElementEventHandler {
    // создаем обработчик события OnAfterIBlockElementAdd
    function OnAfterIBlockElementAdd(&$arFields) {
        \Bitrix\Main\Diag\Debug::writeToFile(
            'Добавлен новый элемент с идентификатором ' . $arFields['ID']
        );
        if (defined('BX_COMP_MANAGED_CACHE')) {
            $GLOBALS['CACHE_MANAGER']->ClearByTag('iblock_id_' . ARTICLES_IBLOCK_ID);
            // $GLOBALS['CACHE_MANAGER']->ClearByTag('iblock_code_' . ARTICLES_IBLOCK_CODE);
        }
    }
    // создаем обработчик события OnAfterIBlockElementUpdate
    function OnAfterIBlockElementUpdate(&$arFields) {
        \Bitrix\Main\Diag\Debug::writeToFile(
            'Элемент с идентификатором ' . $arFields['ID'] . ' был изменен'
        );
        if (defined('BX_COMP_MANAGED_CACHE')) {
            $GLOBALS['CACHE_MANAGER']->ClearByTag('iblock_id_' . ARTICLES_IBLOCK_ID);
            // $GLOBALS['CACHE_MANAGER']->ClearByTag('iblock_code_' . ARTICLES_IBLOCK_CODE);
        }
    }
    // создаем обработчик события OnAfterIBlockElementDelete
    function OnAfterIBlockElementDelete(&$arFields) {
        \Bitrix\Main\Diag\Debug::writeToFile(
            'Удален элемент инфоблока с идентификатором ' . $arFields['ID']
        );
        if (defined('BX_COMP_MANAGED_CACHE')) {
            $GLOBALS['CACHE_MANAGER']->ClearByTag('iblock_id_' . ARTICLES_IBLOCK_ID);
            // $GLOBALS['CACHE_MANAGER']->ClearByTag('iblock_code_' . ARTICLES_IBLOCK_CODE);
        }
    }
}

Привязка тега кеша к директории кеша хранится в таблице b_cache_tag базы данных. После создания кеша в ней будут такие записи:

SITE_ID  CACHE_SALT  RELATIVE_PATH             TAG
-------------------------------------------------------------------
NULL     NULL        0:1541594962              **
s1       /464        /iblock/content/articles  iblock_id_5
s1       /464        /iblock/content/articles  iblock_code_articles
s1       /464        /iblock/content/articles  article_id_347
s1       /464        /iblock/content/articles  article_id_348
s1       /464        /iblock/content/articles  article_id_349

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

/bitrix/cache/iblock/content/articles.~539735

А в таблице таблице b_cache_tag теперь будут записи:

SITE_ID  CACHE_SALT  RELATIVE_PATH                                  TAG
-----------------------------------------------------------------------
NULL     NULL        2:1541595584                                   **
*        *           /bitrix/cache/iblock/content/articles.~539735  *
*        *           /bitrix/managed_cache/MYSQL/agents.~139166     *

В файле лога __bx_log.log появятся записи:

Элемент с идентификатором 354 был изменен
Добавлен новый элемент с идентификатором 357
Удален элемент инфоблока с идентификатором 357

Кеширование в нативных компонентах, использующих инфоблоки

Управляемый кеш обеспечивает обновление кеша нативных компонентов Битрикс, использующих инфоблоки. Реализация крайне проста — в ядре, внутри методов CIBlockResult::Fetch() и CIBlockResult::GetNext() (для элементов и разделов) спрятана регистрация тега вида

'iblock_id_' . $res['IBLOCK_ID']

Соответственно, если компонент использует автокеширование, то при вызове Fetch() или GetNext() будет зарегистрирован кеш с идентификатором инфоблока. А уже внутри методов Add(), Update() и Delete() для разделов и элементов спрятаны вызовы очистки кешей, связанных с этим тегом.

Соответственно, даже если компонент закеширован на год, то при обновлении элементов или разделов — кеш будет сброшен, и на сайте будут всегда актуальные данные.

Поиск: CMS • Web-разработка • Битрикс • Кeширование • CACHE_MANAGER • CCacheManager • CPHPCache • InitCache • StartTagCache • RegisterTag • EndTagCache • ClearByTag • StartDataCache • EndDataCache • ElementTable::getList • Инфоблок • AddEventHandler • OnAfterIBlockElementAdd • OnAfterIBlockElementUpdate • OnAfterIBlockElementDelete • BX_COMP_MANAGED_CACHE • Событие

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.