Кэширование в 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 и т.п. На сайте они публикуются.
Спс. И не забрасывайте телеграм канал. То как вы там пишете - сюда бы в документашку добавить. Оч понятно
а можно управлять временем жизни кеша?
Хороший вопрос. :) Можно, но это более сложная задача. Самый простой способ — это смотреть время жизни файла кэша и если он более старый, чем текущее время + N минут, то он считается устаревшим его следует создать заново. Этот вариант реализован в моём LPF (mso_get_cache и mso_add_cache) http://lpf.maxsite.com.ua/.
Более сложный вариант — прописывать временную метку в начало файла кэша. Так работает кэш MaxSite CMS (и CodeIgniter). То есть перед unsetialize содержимое файла прогоняется через регулярку, по ней считывается время, а остаток уже десериализируется.
Так же может создаваться отдельный файл (в MaxSite CMS cache/_mso_cache_last.txt), где прописывается время последнего сброса кэша. По нему определяется «общее» устаревание кэша: когда оно достигнуто, то удаляются все файлы кэша. Это позволяет как бы синхронизировать все данные сайта.
"..текущее время + N минут" управляется через .htaccess, насколько я понимаю.
или есть более тонкие инструменты в LPF?
Где бы подробнее почитать про
ob_start();
ob_end_clean();
при использовании этих функций в своих страницах, формируемых плагинами.
Нет, .htaccess здесь ни при чём. Смотрится время файла, как я описал выше.
В оф.документации PHP: https://www.php.net/manual/ru/function.ob-start.php
А как управлять временем жизни кэша из первого примера (где if ($cache)) - как сбросить, если надо обновить инфу и "пересобрать" кэш?
В этом примере кэш - статическая переменная, которая живёт в пределах одного «пользователя». Если функция в разных вызовах в пределах одного процесса отдаёт разный результат, то кэширование в ней просто не нужно.