364  /  382
Справочник

Кеширование в собственных компонентах

Просмотров: 84233
Дата последнего изменения: 16.11.2023
Роберт Басыров
Сложность урока:
3 уровень - средняя сложность. Необходимо внимание и немного подумать.
1
2
3
4
5
Недоступно в лицензиях:
Ограничений нет

Описание

Для чего нужно кэширование в собственных компонентах

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

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

Не менее важный момент, на который стоит обратить внимание – скорость отработки компонента при получении данных из кэша. Если без кэширования компонент отрабатывает за 2 сек. для каждого пользователя, то при использовании кэширования компонент для одного пользователя отработает за 2 сек., а для остальных 100 пользователей в ближайшие полчаса, допустим, будет отрабатывать 0.1 сек.

При использовании кэширования в собственных компонентах 2.0:

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

Время кеширования

Примечание: В Bitrix Framework время кеширования учитывается в секундах.

Период времени кэширования зависит от типа кеширования. Если используется Авто+Управляемое кэширование – информация будет отдаваться из кэша до тех пор, пока она не поменяется в базе данных и кэш сбросится автоматически. Время кэширования для этого режима должно быть большим, к примеру, 1 год.

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

Встроенная поддержка кеширования

В компонентах 2.0 есть встроенная поддержка типичного алгоритма кеширования. Структура компонента с использованием встроенной поддержки кеширования будет примерно такова:

// Проверка и инициализация входных параметров
if ($arParams["ID"] <= 0)
	$arParams["ID"] = 10;

// Если нет валидного кеша (то есть нужно запросить
// данные и сделать валидный кеш)
if ($this->StartResultCache())
{
	// Запрос данных и заполнение $arResult
	$arResult = array(
		"ID" => rand(1, 100)
	);

	for ($i = 0; $i < 5; $i++)
		$arResult["FIELDS"][] = rand(1, 100);

	// Если выполнилось какое-то условие, то кешировать
	// данные не надо
	if ($arParams["ID"] < 10)
		$this->AbortResultCache();

	// Подключить шаблон вывода
	$this->IncludeComponentTemplate();
}

// Установить заголовок страницы с помощью отложенной
// функции
$APPLICATION->SetTitle($arResult["ID"]); 

Пояснения по коду

Метод StartResultCache имеет следующее описание: bool $this->StartResultCache($cacheTime = False, $additionalCacheID = False, $cachePath = False)

где:

  • $cacheTime - время кеширования (если False - подставляется IntVal($arParams["CACHE_TIME"]));
  • $additionalCacheID - от чего дополнительно зависит кеш кроме текущего сайта SITE_ID, имени компонента и входных параметров;
  • $cachePath - путь к файлу кеша (если False - подставляется "/".SITE_ID.<путь к компоненту относительно bitrix/components>).

Если есть валидный кеш, то метод отправляет на экран его содержимое, заполняет $arResult и возвращает False; если нет валидного кеша, то он возвращает True.

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

if ($this->StartResultCache(false, $USER->GetGroups()))
{
	// Валидного кеша нет. Выбираем данные из 
	// базы в $arResult
}

Если в результате выборки данных (в случае отсутствия валидного кеша) выяснилось, что кешировать данные не надо, то нужно вызвать метод $this->AbortResultCache();. Например, если выяснилось, что новости с таким ID нет, то нужно прервать кеширование и выдать сообщение, что такой новости нет. Если кеширование не прерывать, то злоумышленники смогут забить кешем все отведенное сайту дисковое пространство вызывая страницу с произвольными (в том числе и не существующими) ID.

Метод $this->IncludeComponentTemplate(); подключает шаблон компонента и сохраняет в кеш-файл вывод и массив результатов $arResult. Все изменения $arResult и вывод после вызова метода подключения шаблона не будут сохранены в кеш.

Если при исполнении кода компонента мы не вошли в тело условия if ($this->StartResultCache()), то значит для данного компонента, страницы и входных параметров есть валидный кеш. После вызова этого метода HTML из кеша отправлен на вывод и мы имеем заполненный массив $arResult. Здесь можно выполнить некоторые действия. Например, установить заголовок страницы с помощью отложенных функций.

Если при выполнении некоторых условий нужно очистить кеш компонента (например, компонент знает, что данные изменились), то можно воспользоваться методом $this->ClearResultCache($additionalCacheID = False, $cachePath = False). Параметры этого метода соответствуют одноименным параметрам метода StartResultCache.

Сложное кеширование

Если компоненту требуется какое-либо особое кеширование, которое не может быть выполнено с помощью встроенной поддержки кеширования, то можно использовать стандартный класс CPHPCache. Структура компонента с использованием класса CPHPCache будет примерно такова:

// Проверка и инициализация входных параметров
if ($arParams["ID"] <= 0)
	$arParams["ID"] = 10;

$arParams["CACHE_TIME"] = IntVal($arParams["CACHE_TIME"]);
$CACHE_ID = SITE_ID."|".$APPLICATION->GetCurPage()."|";
// Кеш зависит только от подготовленных параметров без "~"
foreach ($this->arParams as $k => $v)
	if (strncmp("~", $k, 1))
		$CACHE_ID .= ",".$k."=".$v;
$CACHE_ID .= "|".$USER->GetGroups();

$cache = new CPHPCache;
if ($cache->StartDataCache($arParams["CACHE_TIME"], $CACHE_ID, "/".SITE_ID.$this->GetRelativePath()))
{
	// Запрос данных и формирование массива $arResult
	$arResult = array("a" => 1, "b" => 2);

	// Подключение шаблона компонента
	$this->IncludeComponentTemplate();

	$templateCachedData = $this->GetTemplateCachedData();

	$cache->EndDataCache(
		array(
			"arResult" => $arResult,
			"templateCachedData" => $templateCachedData
		)
	);
}
else
{
	extract($cache->GetVars());
	$this->SetTemplateCachedData($templateCachedData);
}

Пояснения по коду

Кеш должен зависеть только от подготовленных параметров. То есть от параметров, которые инициализированы нужным образом, приведены к нужному типу (например, с помощью IntVal()) и т.д. В массиве $arParams содержатся как подготовленные параметры, так и исходные параметры (с тем же ключем, но с префиксом "~"). Если кеш будет зависеть от неподготовленных параметров, то злоумышленники смогут забить кешем все отведенное сайту дисковое пространство вызывая страницу с ID равными "8a", "8b", ... (которые после IntVal() дадут 8).

Метод $this->IncludeComponentTemplate() не запрашивает данные из базы. Но его тоже лучше включить в кешируемую область, так как этот метод производит определенные дисковые операции.

Перед вызовом метода завершения кеширования и сохранения кеша (метод EndDataCache) необходимо запросить у шаблона параметры, которые должны быть использованы даже в том случае, если сам шаблон не подключается и данные берутся из кеша. В текущей реализации такими параметрами являются стили css шаблона, которые подключаются отложенными функциями, а значит не попадают в кеш. Структура возвращаемых шаблоном данных не документирована и не имеет значения для компонента. Это просто какие-то данные, которые нужно положить в кеш, а затем взять из кеша и вернуть в шаблон.

Чтобы вернуть шаблону те данные, которые он просил сохранить в кеше, можно пользоваться методами $this->SetTemplateCachedData($templateCachedData); или CBitrixComponentTemplate::ApplyCachedData($templateCachedData);. Один из этих методов должен быть вызван в той области компонента, которая выполняется в случае наличия валидного кеша. В параметрах ему должны быть переданы те данные, которые шаблон просил сохранить.

Пример сложного кеширования с классом кеша из D7:

use Bitrix\Main\Data\Cache;

// Проверка и инициализация входных параметров
if ($arParams['ID'] <= 0) {
	$arParams['ID'] = 10;
}

$arParams['CACHE_TIME'] = intval($arParams['CACHE_TIME']);

$cacheId = implode('|', [
	SITE_ID,
	$APPLICATION->GetCurPage(),
	$USER->GetGroups()
]);

// Кеш зависит только от подготовленных параметров без "~"
foreach ($this->arParams as $k => $v) {
	if (strncmp('~', $k, 1)) {
		$cacheId .= ',' . $k . '=' . $v;
	}
}

$cacheDir = '/' . SITE_ID . $this->GetRelativePath();
$cache    = Cache::createInstance();

if ($cache->startDataCache($arParams['CACHE_TIME'], $cacheId, $cacheDir)) {
	// Запрос данных и формирование массива $arResult
	$arResult = ['a' => 1, 'b' => 2];

	// Подключение шаблона компонента
	$this->IncludeComponentTemplate();

	$templateCachedData = $this->GetTemplateCachedData();

	$cache->endDataCache([
		'arResult'           => $arResult,
		'templateCachedData' => $templateCachedData,
	]);
} else {
	extract($cache->GetVars());
	$this->SetTemplateCachedData($templateCachedData);
}

Несколько советов


Если в компоненте используется стандартное кэширование, но при этом не подключается шаблон (по причине ненадобности), нужно использовать:

if ($this->startResultCache())
{
	\\Код который модифицирует $arResult
	$this->endResultCache();
}


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

$cache_id = serialize(array($arParams, ($arParams['CACHE_GROUPS']==='N'? false: $USER->GetGroups()))); 
$obCache = new CPHPCache; 
if ($obCache->InitCache($arParams['CACHE_TIME'], $cache_id, '/')) 
{ 
	$vars = $obCache->GetVars(); 
	$arResult = $vars['arResult']; 
} 
elseif ($obCache->StartDataCache()) 
{ 

	// делаем то, что надо 

	$obCache->EndDataCache(array( 
		'arResult' => $arResult, 
	)); 
} 

Если код написан правильно и в template.php нет «тяжелого» кода, то этот вариант может работать достаточно хорошо.


35
Курсы разработаны в компании «1С-Битрикс»

Если вы нашли неточность в тексте, непонятное объяснение, пожалуйста, сообщите нам об этом в комментариях.
Развернуть комментарии