Full Cache. Простое Frontend кеширование без Nginx
Цель. Снизить нагрузку при большом потоке незарегистрированных пользователей.
Причины нагрузки.
1) Даже при включении бэкенд кеша любого вида(memcache, file, xcache) движок все же ест память, так как структуру данных он выстраивает в любом случае. При большом количестве плагинов растет количество классов и экземпляров этих классов, которые загружаются в память. В данном случае использование бэкенд кеша увеличивает скорость работы за счет кеширования результатов запросов в базу данных. Но размер потребляемой памяти остается тот же. Например, у меня страница топика ела 19 Мб, а список — 30мБ. При этом частично помогает, например, использование ссылок на объекты с помощью & при переборе, а не их копий. Но все же это крошки.
2) Даже при полном кеше БД все же выполняются несколько запросов, таких как получение данных о сессии или update таблицы просмотров. Под нагрузкой и при использовании InnoDb базы «update запрос» может очень сильно тормозить.
Ресурсы и методы
Собственно смысл прост. Он состоит в том, чтобы один раз генерировать страницу, а потом отдавать ее сразу.
Проверять на существование и отдавать кеш надо будет перед запуском движка, что разгрузит потребляемую память.
Как выдавать кеш? Кеш нам надо выдавать как можно раньше, не допуская загрузки всего движка, а затем завершать скрипт принудительно. Хранить будем, например, в папке _cache в корне. Для этого ее предварительно создадим с нужными правами.
Для этого в index.php
После
вставляем
Как нам загнать полную страницу в кеш?
Для начала вспомним, как она генерируется. В начале у нас инициализируется Router, который в свою очередь инициализирует Engine, запускает контролеры (экшены), потом передает переменные во Вьюху. После чего мы уже видим готовую страницу.
Это все делается в методе $oRouter->Exec().
В конце метода запускается:
Тут есть два пути: лезть глубже во Viewer или использовать вызов метода прямо в роутере. Плюс первого в том, что мы можем избежать эксепшенов, которые кидает вьювер и смарти, и не кешить их. Но я остановлюсь на втором.
В engine\classes\Router.class.php код
меняем на
Мы забыли только вставить генерацию куки _key:
В classes\modules\user\User.class.php
вместо
ставим
А после
вставляем
Конечно же, кеш нужно чистить
Чищу я одной командой, которую поставил в крон. Например, раз в 5 мин:
UPD Совсем забыл. Чтобы кеш работал для аякс запросов, (блоков, тех что в сайдбаре или других, что дерут инфу с сервера) надо их переделать из POST в GET и убрать проверку security_ls_key (для незарегиных это не нужно). Тогда код на использование кеша станет:
$_GET['security_ls_key'] я оставил, чтоб работало разлогивание.
Для тех, кто хочет Full Highload проект.
Вообще сейчас в движке заимплеменчена бэкенд часть DKLAB кеша. Но на том же сайте есть чудесный мануал по внедрению фронтенд части и кеширования всей страницы целиком либо отдельных ее блоков, а также вывода кеша напрямую с помощью nginx, что дает значительное снижение нагрузки.
Надеюсь, скоро все-таки модуль DKLAB кеша для фронтенда войдет в модули LS по умолчанию.
Причины нагрузки.
1) Даже при включении бэкенд кеша любого вида(memcache, file, xcache) движок все же ест память, так как структуру данных он выстраивает в любом случае. При большом количестве плагинов растет количество классов и экземпляров этих классов, которые загружаются в память. В данном случае использование бэкенд кеша увеличивает скорость работы за счет кеширования результатов запросов в базу данных. Но размер потребляемой памяти остается тот же. Например, у меня страница топика ела 19 Мб, а список — 30мБ. При этом частично помогает, например, использование ссылок на объекты с помощью & при переборе, а не их копий. Но все же это крошки.
2) Даже при полном кеше БД все же выполняются несколько запросов, таких как получение данных о сессии или update таблицы просмотров. Под нагрузкой и при использовании InnoDb базы «update запрос» может очень сильно тормозить.
Ресурсы и методы
Собственно смысл прост. Он состоит в том, чтобы один раз генерировать страницу, а потом отдавать ее сразу.
Проверять на существование и отдавать кеш надо будет перед запуском движка, что разгрузит потребляемую память.
Как выдавать кеш? Кеш нам надо выдавать как можно раньше, не допуская загрузки всего движка, а затем завершать скрипт принудительно. Хранить будем, например, в папке _cache в корне. Для этого ее предварительно создадим с нужными правами.
Для этого в index.php
После
// Получаем объект конфигурации
require_once("./config/loader.php");
вставляем
/**
* Проверяем, использовать кеш или нет.
* Существование $_COOKIE['key'] будет идентифицировать то, что юзер, возможно, авторизован.
* Кука _key нужна для того, чтобы можно было при авторизации, разлогивании или регистрации исключить кеширование.
* Далее условия можете ставить какие угодно.
* Так как в LS парадигма REST еще не включена, то,
* если массивы $_POST и $_GET не пусты, тоже не будем использовать кеширование
*/
$bUseCache =
(!isset($_COOKIE['key']) || empty($_COOKIE['key'])) &&
(!isset($_COOKIE['_key']) || empty($_COOKIE['_key'])) &&
empty($_POST) && empty($_GET);
// уникальный путь к кеш файлу, используя переменные запроса и шаблона
$sCacheFile=Config::Get('path.root.server')."/_cache/".md5(Config::Get('view.skin').$_SERVER['REQUEST_URI']).".tmp";
// установим в конфиг
Config::Set('usefullcache',$bUseCache);
Config::Set('fullcache_file',$sCacheFile);
// собственно проверяем, есть ли кеш файл, затем выводим его содержимое и завершаем скрипт
if ($bUseCache && @file_exists($sCacheFile)) {
echo file_get_contents($sCacheFile);
exit();
}
Как нам загнать полную страницу в кеш?
Для начала вспомним, как она генерируется. В начале у нас инициализируется Router, который в свою очередь инициализирует Engine, запускает контролеры (экшены), потом передает переменные во Вьюху. После чего мы уже видим готовую страницу.
Это все делается в методе $oRouter->Exec().
В конце метода запускается:
$this->Viewer_Display($this->oAction->GetTemplate());Данный метод проверяет, аяксовый запрос или обычный. В зависимости от результата выводит данные с разными заголовками и в разном формате.
(в >=5 версиях данный код вынесен в метод Shutdown())
Тут есть два пути: лезть глубже во Viewer или использовать вызов метода прямо в роутере. Плюс первого в том, что мы можем избежать эксепшенов, которые кидает вьювер и смарти, и не кешить их. Но я остановлюсь на втором.
В engine\classes\Router.class.php код
$this->Viewer_Display($this->oAction->GetTemplate());
меняем на
// C помощью буфера получаем страницу в переменную
ob_start();
$this->Viewer_Display($this->oAction->GetTemplate());
$sOutput = ob_get_contents();
ob_end_clean();
// Затем, используя переменные, которые ранее мы загнали в конфиг, записываем в файл содержимое
if (Config::Get('usefullcache') && $sCacheFile = Config::Get('fullcache_file')) {
@touch($sCacheFile);
file_put_contents($sCacheFile,$sOutput);
}
// выводим на экран страницу
echo $sOutput;
Мы забыли только вставить генерацию куки _key:
В classes\modules\user\User.class.php
вместо
/**
* Ставим куку
*/
if ($bRemember) {
setcookie('key',$sKey,time()+60*60*24*3,Config::Get('sys.cookie.path'),Config::Get('sys.cookie.host'));
}
ставим
/**
* Ставим куку
*/
if ($bRemember) {
setcookie('key',$sKey,time()+60*60*24*3,Config::Get('sys.cookie.path'),Config::Get('sys.cookie.host'));
} else {
// to identify user before session starts
setcookie('_key',$sKey,time()+60*60*24*3,Config::Get('sys.cookie.path'),Config::Get('sys.cookie.host'));
}
А после
/**
* Дропаем куку
*/
setcookie('key','',1,Config::Get('sys.cookie.path'),Config::Get('sys.cookie.host'));
вставляем
setcookie('_key','',1,Config::Get('sys.cookie.path'),Config::Get('sys.cookie.host'));
Конечно же, кеш нужно чистить
Чищу я одной командой, которую поставил в крон. Например, раз в 5 мин:
# Cache remover
*/5 * * * * root find "ABSOLUTE_PATH_TO_LS/_cache" -name "*.*" -type f -cmin +5 -delete >/dev/null
UPD Совсем забыл. Чтобы кеш работал для аякс запросов, (блоков, тех что в сайдбаре или других, что дерут инфу с сервера) надо их переделать из POST в GET и убрать проверку security_ls_key (для незарегиных это не нужно). Тогда код на использование кеша станет:
$bUseCache =
(!isset($_COOKIE['key']) || empty($_COOKIE['key'])) &&
(!isset($_COOKIE['_key']) || empty($_COOKIE['_key'])) &&
empty($_POST) && !isset($_GET['security_ls_key']);
$_GET['security_ls_key'] я оставил, чтоб работало разлогивание.
Для тех, кто хочет Full Highload проект.
Вообще сейчас в движке заимплеменчена бэкенд часть DKLAB кеша. Но на том же сайте есть чудесный мануал по внедрению фронтенд части и кеширования всей страницы целиком либо отдельных ее блоков, а также вывода кеша напрямую с помощью nginx, что дает значительное снижение нагрузки.
Надеюсь, скоро все-таки модуль DKLAB кеша для фронтенда войдет в модули LS по умолчанию.
27 комментариев
Можно использовать для кеша и методы DKLAB Backend Cache, но там данные сериализируются и к ним подмешиваются еще и ненужные ключи.
я только верстальщик и дизайнер :) с некоторыми знаниями Smarty и JQ… Как я понимаю есть сложные моменты в реализации…
а вы смотрели 1.0 dev может там как то проще будет?
У меня вместо nginx кусок кода в index.php, а кеш пишется в файл, имя которого равно ключу кеша. Если POST запрос, то в URI параметр запроса невидны. Поэтому ключ сварганить для них нельзя. Да и не надо, POST запросы используются для отправки данных на сервер, а не для получения.
Но вот в некоторых блоках LS (те же блоки в сайдбаре) данные с сервера как раз-таки получаются POST — ом. Я считаю, что надо получать GET-ом, для их легкого кэширования. Поиск же сделан через GET:
И его результаты легко закешить, а потом отдать, потому что URI запроса будет уникальным для разных ?q=.
Вот на DKLAB пишут:
Конечно разницы, вы не увидите на небольших нагрузках.
Если уж и кешировать, то на уровне шаблонизатора, тем более, что у Smarty есть встроенные механизмы для этого.
Что до действий, то для них все равно надо передавать какой-то идентификатор или токен, например,
По нему можно и отсеивать.
Подсчет юзеров в онлайне считает только активные сессии зарегистрированных пользователей, для которых кеш не будет работать. Для незарегистрированных этот показатель будет обновляться на 2 минуты реже, что некритично.
Счетчик просмотра топиков для больших проектов выносят в отдельный скрипт или блок, что помогает избежать блокировку кеширования всей страницы в целом.
Ну ведь для этого опять же надо инициализировать движок, а смысл кеша был в том, чтобы это избежать. К тому же у нас шаблон генерируется в самом конце. Это было бы актуально, если бы модули вызывались прямо из шаблона.
А фронтенд кеширование прекрасно дружит с MVC фреймворками.
Еще на помощь может прийти Server side include.
сомнительно
echo $sOutput
После этого требуется ";".
Кэш пишется на диск, так что скорость отдачи зависит и от него(у меня используются sas-диски). Меня полностью устраивает.
После включения кэширования нагрузка на процессор и диск возврастет(не сильно) — записывать и обновлять кэш кому-то же надо.
У меня все полгигабайта лежат в RAMFS, и без xCache (опкод и данные. Да, именно так, memcached — велоипедное излишество) приходится весьма туго, LA при пиковой ~3.5. А так 1 в среднем, ну 1.8~2.3 в пик.
*/5 * * * * root find «ABSOLUTE_PATH_TO_LS/_cache» -name "*.*" -type f -cmin +5 -delete >/dev/null — root здесь лишнее.