Сайт вебмастера

Кэширование в PHP

24-04-2020Reading time ~ 4 min.PHP 14236

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

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

Совершенно бессмысленно создавать кэш для функций, которые редко вызываются в коде. В этом случае затраты на кэширование просто не «окупаются».

Существует два основных подхода к организации кэша: в файле и в памяти.

Скажем, есть некая функция, которая выполняет какой-то сложный расчёт. Эта функция вызывается много раз. Для того, чтобы избежать повторного выполнения, результат сохраняется в статическую переменную. Примерно так:

function my1()
{
    static $cache = false;
    
    if ($cache) return $cache;
    
    $out = ... что-то сложное и ресурсоёмкое ...
    
    $cache = $out;
    
    return $out;
}

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

function my2($args)
{
    static $cache = false;
    
    if ($cache) {
        $data = $cache;
    } else {
        $data = ... что-то сложное и ресурсоёмкое ...
        $cache = $data;
    }
 
    $out = ... то что зависит от $data и $args
    
    return $out;
}

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

В ООП действуют аналогично, только вместо глобальной переменной применяют singleton/multiton с паттерном Registry.

Кэш в памяти — достаточно простой вариант для реализации. Если вы уже перешли на PHP 7, то учитывайте, что в этой версии очень здорово доработали оптимизацию, поэтому перед доработкой функций, убедитесь, что её выполнение действительно затратно (например с помощью функций профилирования).

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

Например вы реализуете функцию, которая работает с csv-файлом: его получение, обработка, проверка и результат помещается в массив, который передаётся уже дальше по коду. Это затратные операции, поэтому полученный массив нужно сохранить в файл. Здесь не имеет смысла применять static-кэш, поскольку функция будет срабатывать для каждого посетителя сайта. Если же мы сохраним в файл, то обработка произойдёт только один раз.

Лучшим способом для хранения php-данных в файле — сериализация. Также можно использовать формат JSON — это для случаев, если нужно сделать кэш читаемым.

Файловый кэш может быть каким-то «глобальным», как например в MaxSite CMS, или «локальным», скажем на уровне шаблона — где задаётся какой-то фиксированный файл кэша определенной функции. «Глобальный» вариант реализуется достаточно просто — есть функции, которые либо добавляют, либо возвращают данные из кэша. Как именно он организован уже не имеет значения.

«Локальный» вариант обычно реализуется по такому алгоритму.

  • Проверяется существование файла кэша.
  • Если он есть, то он считывается и деселиализуется.
  • Если нет, то выполняется участок кода, результат которого сериализуется и записывается в файл кэша.
function my3($file)
{
    $cacheFile = $file . '.cache.txt';
    
    $flag = true; // флаг, что нужна полная обработка
    
    if (file_exists($cacheFile)) {
    
        // проверка устаревания кэша
        if (filemtime($cacheFile) > filemtime($file)) {
        
			$cache = file_get_contents($cacheFile);
			$cache = @unserialize($cache);
			
			if ($cache) {
				$data = $cache;
				$flag = false;
			}
		}
    }
    
    if ($flag) {
        // требуется полная обработка данных
        
        $data = ... что-то делаем ...
        
        // сохраняем в кэш
        file_put_contents($cacheFile, serialize($data));
    }
    
    ... дальше выполняем код с $data ...
}

При необходимости в такую функцию можно добавить ещё и static-кэширование. Это при условии, что обработка данных также ресурсозатратно. То есть смотреть по ситуации.

Кэширование вывода базируется на предыдущих вариантах и используется, когда даже нет смысла выполнять функции. То есть формируется готовый для вывода html-код, он загоняется в кэш (обычно файловый) и потом просто отдаётся как есть. Это самый быстрый в мире кэш. (Кто в курсе, то именно так работает мой maxcache для вп — он формирует кэш из готовых страниц так, что вп даже не запускается.)

Для организации такого кэша используется буферизация вывода.

... если нет кэша, то {
 
    ob_start();
 
    ... формируем html-вывод любым вариантом ...
 
    // результат оказывается в $out
    $out = ob_get_contents();
 
    // обнуляем буфер
    if (ob_get_length()) ob_end_clean();
 
    ... сохраняем $out в кэш ...
} else {
   ... сразу отдаём кэш ...
}

Такой кэш самый эффективный, но его использование будет оправдано для достаточно больших кусков html-кода. При этом нужно учитывать, что в кэш может попасть содержимое индивидуальное для каждого посетителя, например ссылка на профиль. То есть кэш должен учитывать такие ситуации. В MaxSite CMS по этому принципу работает плагин global_cache, где проверяется залогиненность посетителя: если она есть, то кэш для него не работает.

PS Вы можете подписаться а мой telegram-канал, где я рассказываю о PHP, MaxSite CMS и т.п. На сайте они публикуются.

Related Posts
Comments (8) RSS
1 Сергей 2020-04-24 18:48:14

Спс. И не забрасывайте телеграм канал. То как вы там пишете - сюда бы в документашку добавить. Оч понятно


2 Виктор 2020-04-24 20:37:13

а можно управлять временем жизни кеша?


3 Admin 2020-04-24 21:11:19 admin

Хороший вопрос. :) Можно, но это более сложная задача. Самый простой способ — это смотреть время жизни файла кэша и если он более старый, чем текущее время + N минут, то он считается устаревшим его следует создать заново. Этот вариант реализован в моём LPF (mso_get_cache и mso_add_cache) http://lpf.maxsite.com.ua/.

Более сложный вариант — прописывать временную метку в начало файла кэша. Так работает кэш MaxSite CMS (и CodeIgniter). То есть перед unsetialize содержимое файла прогоняется через регулярку, по ней считывается время, а остаток уже десериализируется.

Так же может создаваться отдельный файл (в MaxSite CMS cache/_mso_cache_last.txt), где прописывается время последнего сброса кэша. По нему определяется «общее» устаревание кэша: когда оно достигнуто, то удаляются все файлы кэша. Это позволяет как бы синхронизировать все данные сайта.


4 Аноним 2020-04-24 22:20:29

"..текущее время + N минут" управляется через .htaccess, насколько я понимаю.

или есть более тонкие инструменты в LPF?


5 Аноним 2020-04-24 22:27:12

Где бы подробнее почитать про

ob_start();

ob_end_clean();

при использовании этих функций в своих страницах, формируемых плагинами.


6 Admin 2020-04-25 08:01:30 admin
"..текущее время + N минут" управляется через .htaccess, насколько я понимаю.

Нет, .htaccess здесь ни при чём. Смотрится время файла, как я описал выше.

Где бы подробнее почитать про

ob_start();

ob_end_clean();

при использовании этих функций в своих страницах, формируемых плагинами.

В оф.документации PHP: https://www.php.net/manual/ru/function.ob-start.php


7 M 2020-05-21 00:31:11

А как управлять временем жизни кэша из первого примера (где if ($cache)) - как сбросить, если надо обновить инфу и "пересобрать" кэш?


8 Admin 2020-05-21 08:53:00 admin

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