Николай Ланец
25 авг. 2013 г., 20:11

MODX Revolution + nginx + memcached.

В продолжение темы. Очень кратко опишу свой опыт с экспериментом сохранения кода страницы в memcached, который в дальнейшем уже отдает nginx без лишних запросов, получая код непосредственно из memcached.
Общая идея: генерировать страницу средствами MODX-а и сохранять ее код полностью в memcached, чтобы в дальнейшем nginx проверял наличие кода страницы в memcached-е, и если есть, отдавал код сразу, не отправляя запрос на php. Пока что это только для сайтов-визиток и т.п. (так как на таких страницах нельзя использовать логику с учетом авторизации пользователей и т.п.), но в дальнейшем планирую страницы делать полностью статическими, а всю динамику переносить на Javascript + AJAX.
Основные проблемы, с которыми столкнулся: 1. Больше всего времени потратил на настройку правил для nginx-а (свой вариант покажу ниже). 2. Битая кодировка кеша больших страниц. Как выяснилось, в php для memcached стояла настройка по умолчанию memcached.compression_threshold=2000. То есть если кешируемая строка больше 1999 символов, то php компрессирует ее. Если получить кеш средствами php, то проблем нет, он и декомпрессирует. А вот nginx за этим не следит и отдает как есть. Полечил топорно: в настройках php указал memcached.compression_threshold=9999999
Итак, код плагина:
<?php switch($modx->event->name){ case 'OnWebPageComplete': if (!empty($modx->resource) && $modx->resource->get('cacheable') && $modx->resource->get('published') && $modx->resource->_output != '' ) { $key = '/index.php?'; // Заменяем спецсимвол амперсанта $key .= str_replace('&', '&',($_SERVER['QUERY_STRING'])); // $modx->log(1, "Key: '{$key}'"); $modx->cacheManager->set($key , $modx->resource->_output); } break; default:; }
Да, плагин вешаем на событие OnWebPageComplete. Здесь мы этот момент обсуждали.
Настройка для nginx:
server { listen 80; # Указываем свой хост server_name myhost; allow all; error_log /var/log/nginx/error_log; # Указываем путь до директории сайта root /var/www/site/public_html; # Всякие запрещенные папки location ~ /\.ht { deny all; } location /core/ { deny all; } location /temp/ { deny all; } # Статика location ~* \.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|tar|wav|bmp|rtf|js)$ { access_log off; expires 30d; } # Основное правило обработки запросов location / { # файлы по умолчанию index index.php index.html; # попытка получить запрашиваемый файл в именнованной локации # Попытка - подразумевает возможные неудачи. Перечисление по порядку # в указанных вариантах. # При запросе к корню сайта будет получен индексный файл index.php, # так что сработает правило location ~ \.php$ # А правило @modx сработает только если файл не найден try_files $uri $uri/ @modx; } # Именнованная лакация (правило) location @modx { # выполняем подмену на index.php rewrite ^/(.*)$ /index.php?q=$1 last; } # Правила обработки php location ~ \.php$ { # Контент по умолчанию, чтобы нгинкс не отдавал браузеру # как обычный файл для скачивания default_type text/html; # устанавливаем ключ-переменную для запроса к мемкешу set $memcached_key "default/$uri?$args"; # выполняем запрос к мемкешу memcached_pass localhost:11211; # устанавливаем обработчик ошибок # если будет получена одна из этих ошибок, то ошибка не будет # возвращена сразу, а будет отправлен запрос дальше на php error_page 404 401 405 502 504 = @php; } location @php { # Отправляем запрос на Apache или типа того (у кого что) proxy_pass http://127.0.0.1:8888; # свой локальный порт proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_connect_timeout 120; proxy_send_timeout 120; proxy_read_timeout 180; } }
Напоследок хочу отметить, что в запросах учитываются все GET-параметры, так что этот метод годится и для постраничности и т.п. Любое изменение параметров запроса — это новая страница, новый кеш.
UPD: Забыл сказать, что в MODX-е еще надо сменить кеш-провайдер на memcached. По умолчанию в настройках MODX-а указано cache_handler=xPDOFileCache, то есть используется файловый кеш-провайдер. Надо поменять на cache.xPDOMemCached.
И еще момент: чтобы сразу есть настройки, которые необходимо и в сам конфиг-файл переносить. Дело в том, что для чтения системных настроек из базы данных требуется инициализация самого MODX-а. Это звучит очень логично, но не все на этом внимание заостряют. Так вот, инициализация требует предварительного чтения конфигов. И вот что получается — пока MODX не получил данные из базы данных, он использует конфиги файловые, в том числе и значения по умолчанию. А так как настройка cache_handler=cache.xPDOMemCached хранится в базе данных, то в момент инициализации MODX еще ничего о ней не знает, и соответственно использует стандартный файловый кеш-провайдер. И получив настройки из БД, он только потом очухивается, и начинает использовать Memcached. Но до этого успевает записать конфиги в файлы, и вообще каждый раз при старте читает конфиги из файлов.
Вот чтобы этого не происходило и чтобы он сразу использовал Memcached, зайди в core/config/config.inc.php и в $config_options пропиши эту настройку тоже:
$config_options = array ( "cache_handler" => "cache.xPDOMemCached", );
Вот тогда он сразу будет мемкешед юзать.
Молодец! все толково расписал. Насчет только для визиток, я бы не был так категоричен. Nginx много чего умеет…
Так я сказал «пока». Докрутить многое можно. Да и на уровне плагина можно разрулить что кешировать так, а что нет (допустим, личный кабинет не кешировать). Но это так, эксперимент. Я более плотно планирую делать это на уровне node.js То есть нода не все еще умеет, чтобы так сразу фрейм заменить, но ее можно использовать как умный кешер. То есть и данные в памяти держать, и логику рулить можно. В общем, планирую nginx+MODX+node.js
memcache зараза страницу компрессирует как ни крути, а вот с memcached все нормально) вот это у тебя
<code>$key = '/index.php?';</code>
вообще лишнее, можно выкинуть… и добавить еще $expire
<code>$modx->cacheManager->set($key , $modx->resource->_output, $expire);</code>
выставить время жизни кеша. и в nginx правильнее наверно будет
"default/$args"
memcache зараза страницу компрессирует как ни крути
А ты смотри phpinfo() раздел memcache. У него тоже свои настройки есть, типа max_chunk_size или типа того. Наверняка тоже можно поднять значение.
вообще лишнее, можно выкинуть…
и в nginx правильнее наверно будет «default/$args»
Да? А кто запрещает создать еще какой-нибудь внутренний php-файл? И что тогда? И тогда нафиг все побьется. А ты предлагаешь все жестко завязать на единственный php-файл. Не, я так не буду делать.
и добавить еще $expire
Это я может позже поковыряю, но у меня есть подозрение, что это не так работает, как хотелось бы. Думаю, что это значение для самого MODX-а, а не для мемкеша. То есть как работает этот параметр для MODX-а? — кеш есть, но время уже не актуальное, и MODX его игнорит. Но если это так, то gnixn-у на это пофигу будет, он получит кеш и все. Хотя это только предположение, проверять надо.
да настройки есть конечно, аналогичные кстати… но не получилось, возможно механизм сжатия по другому работает.
насчет побьется я не совсем уверен, так как неделю назад вообще про nginx не слышал. Но я думаю ты ошибаешся… Пройдись по конфигу nginx и посмотри логику.
$expire работает — уже проверял.
насчет побьется я не совсем уверен, так как неделю назад вообще про nginx не слышал. Но я думаю ты ошибаешся… Пройдись по конфигу nginx и посмотри логику.
Я вчера часов 6 с этим конфигом провозился, так что испробовал различные варианты. Смотри вот правило:
# Именнованная лакация (правило)     location @modx {         # выполняем подмену на index.php         rewrite ^/(.*)$ /index.php?q=$1 last;     }  
Так как ЧПУ MODX-а построено полностью на реврайтах, а не на реальных файлах, именно благодаря ему все УРЛы и преобразуются в index.php Но это происходит только потому что запрашиваемый файл не найден. Но если файл будет найден, то этого преобразования не будет, а останется реальный УРЛ. И в итоге ключ /index.php не будет соответствовать никогда в этом случае. Но это конечно же ситуация исключительная, поэтому конечно ты можешь пренебрегать этим моментом и сделать так, как ты предложил — в 99% случаев оно действительно будет работоспособным. Но у меня свои тараканы в голове и я терпеть не могу неуниверсальность, поэтому у себя оставлю так.
ну в данном случае у тебя
# устанавливаем ключ-переменную для запроса к мемкешу set $memcached_key "default/$uri?$args";
и все! это же касается только memcached. При чем тут реальные файлы? только лишняя переменная=index.php? в ключе
При чем тут реальные файлы?
При том, что здесь используется переменная $uri. Как я писал выше, если файл не найден, то УРЛ меняется на /index.php?q=$1 Повторюсь:
# Именнованная лакация (правило) location @modx { # выполняем подмену на index.php rewrite ^/(.*)$ /index.php?q=$1 last; }
Так как в большинстве случаев при ЧПУ файлов тупо нет, то постоянно и происходит подмена УРЛа на index.php. При этом и переменная $uri и принимает это значение. А если файл будет найден, то $uri не будет изменена, и будет иметь значение реально запрошенного адреса. Теперь понятно? Плагин конечно я тоже упростил. Изначально он был примерно такой:
$query = '/'; $query_str = str_replace('&amp;', '&',($_SERVER['QUERY_STRING'])); if(!empty($query_vars['q'])){ $query .= $query_vars['q']; unset($query_vars['q']); } else{ $query .= 'index.php'; } $query .= "?"; $vars = array(); foreach($query_vars as $k => $v){ $vars[] = "{$k}={$v}"; } if($vars){ $query .= implode("&", $vars); } $modx->log(1, print_r($query, 1)); $modx->cacheManager->set($query , $modx->resource->_output);
И то, что сейчас в плагине осталось — это публичный упрощенный вариант, для которого твои замечания очень обоснованный. Но для меня мой nginx-конфиг более актуальный :-)
ну так бы сразу и сказал, что у тебя свои замуты)
у меня не замуты, а универсальность :-)
как думаешь, имеет смысл memcached на сокеты перекинуть? По идее еще прирост в скорости должен быть… вот эти настройки в обработчике меняю
('memcached_server', $options, 'localhost:11211')
и по идее будет работать? или тут все сложнее устроено?
Честно скажу: сам с сокетами особо не работал. Знаю, что сокеты быстрее всяко, но настройку этого всего не делал.
подскажи как и где переключиться на сокет? пробовал в конфиге прописать как
$config_options = array ( "cache_handler" => "cache.xPDOMemCached", 'memcached_server' => 'unix:///var/run/memcached/mem.socket', 'system_settings_memcached_server' => 'unix:///var/run/memcached/mem.socket', 'db_memcached_server' => 'unix:///var/run/memcached/mem.socket', );
не срабатывает нифига! хелп!)))
Я же говорил, что с сокетами особо не заморачивался. Ты топик напиши как сокеты подключал, с какими подводными камнями столкнулся.
я пока что локально подключился и тесты провел, чтоб узнать будет ли какая выгода от такого перехода. Ты как спец по кешированию подскажи — как перевести всю систему кеширования modx на сокеты теперь? я немного не допонимаю, это именно в файле обработчика кеша нужно сделать? или еще где то нужно пилить? по идее нужна только замена
$memcache->connect('localhost', 11211)
на
$memcache->connect('unix:///var/run/memcached/mem.socket', 0)
и все должно пахать!
Так ты в исходники почаще заглядывай. Вот __construct мемкеша:
$servers = explode(',', $this->getOption($this->key . '_memcached_server', $options, $this->getOption('memcached_server', $options, 'localhost:11211'))); foreach ($servers as $server) { $server = explode(':', $server); $this->memcache->addServer($server[0], (integer) $server[1]); } $compressThreshold = $this->getOption($this->key . '_memcached_compress_threshold', $options, $this->getOption('memcached_compress_threshold', array(), '20000:0.2'));
То есть укажи в системных настройках memcached_server и должно все заработать. Это именно что касается memcache. И, ксттаи, там же ответ на твои проблемы с компрессией:
$compressThreshold = $this->getOption($this->key . '_memcached_compress_threshold', $options, $this->getOption('memcached_compress_threshold', array(), '20000:0.2'));
То же самое и для memcached:
$servers = explode(',', $this->getOption($this->key . '_memcached_server', $options, $this->getOption('memcached_server', $options, 'localhost:11211')));
Только вот с компрессией у него лучше дела обстоят, так как он значение берет из системы:
$this->memcached->setOption(Memcached::OPT_COMPRESSION, (boolean) $this->getOption($this->key . '_memcached_compression', $options, $this->getOption('memcached_compression', $options, $this->getOption(Memcached::OPT_COMPRESSION, $options, true))));
И, кстати, как я и предполагал, должна быть возможность в принципе отключить для него компрессию.
да компрессия бог с ней… на memcached работает и ладно. Исходники я видел, но если прописать туда
'unix:///var/run/memcached/mem.socket', 0
то не пашет! неужели ты думаешь я сначала не попробовал?)) выдает ошибку
Error caching time of next auto publishing event Could not cache context settings for mgr. Could not cache context settings for web.
я в коде не разбираюсь но у меня чувство что explode(':', $server) это разбор строки… и если сокет прописывать то и тут надо как то менять…
поэтому нужна твоя помощь!)
Так ты не пиши 'unix:///var/run/memcached/mem.socket', 0 Это же сразу два параметра. Пиши только unix:///var/run/memcached/mem.socket
я в коде не разбираюсь но у меня чувство что explode(':', $server)
Да, это разбивка строки. Но мне сечас некогда с этим экспериментировать, я пока очень занят.
да я уже и так и сяк пробовал… Как время будет посмотри
а как то можно прописать в обработчике, чтоб он мне логи скинул в журнал чему какая переменная равна?
Так залезь в код и посмотри что чему равно.
все вроде понял что, к чему… только вот эта строчка непонятна
$servers = explode(',', $this->getOption($this->key . '_memcached_server', $options, $this->getOption('memcached_server', $options, 'unix:///var/run/memcached/mem.socket')));
explode разбирает на строки — это ясно. getOption — получает параметр по ключу, но их тут сразу несколько одна в одной… вот это мне не совсем ясно… $options мы его в данном случае получаем, или наоборот формируем? мне кажется все дело в этой строке, так как я пробовал все это убирать и просто делал
$this->memcached->addServer('unix:///var/run/memcached/mem.socket', 0);
но не работает! может эти настройки еще где-то в modx торчат?
$options — это то, что можно передать в инициализацию объекта. В данном случае вообще на это забей. Два раза getOption(), потому что второй раз, это значение по умолчанию. Разбей на два раза, чтобы понятней было.
$servers = explode(',', $this->getOption($this->key. '_memcached_server', $options, $this->getOption('memcached_server', $options, 'unix:///var/run/memcached/mem.socket')));
Это буквально так:
$servers_default = $this->getOption('memcached_server', $options, 'unix:///var/run/memcached/mem.socket'); $servers_str = $this->getOption($this->key . '_memcached_server', $options, $servers_default); $servers = (array)explode(',', $servers_str);
То есть может быть указано несколько серверов. Укажи memcached_server в настройках и все.
ну я логически так и додумал, но ошибка все таже —
Error caching time of next auto publishing event Could not cache context settings for mgr. Could not cache context settings for web.
вот у меня чувство что где то еще — localhost:11211 торчит по умолчанию…
Вот и ковыряй. Думаешь у меня всегда все сразу получается? Нет, иногда часами сидишь воюешь, а иногда и днями.
тогда может на $options всетаки нельзя забивать? он ведь у нас в каждой функции идет
$options= array())
либо еще какая то фигня мешает работе с сокетами либо я хз
у меня к тебе одна просьба будет — можешь в своем обработчике закомментировать строчку вот эту
$this->memcached->setOption(Memcached::OPT_COMPRESSION, (boolean) $this->getOption($this->key . '_memcached_compression', $options, $this->getOption('memcached_compression', $options, $this->getOption(Memcached::OPT_COMPRESSION, $options, true))));
сбрось кеш и посмотри какую ошибку выдаст. спасибо!
Мне реально сейчас не до этого. Так что будь настоящим джедаем и воюй до последнего сам.
все разобрался, все правильно я прописывал, надо было просто чуток подождать. настройки тоже кешировались и мои изменения с кешером не сразу применялись. сейчас все по новой прописал, подождал 5 минут — и все ок! Работает!)
и насчет компрессии ты был прав, можно прям в кешере указать, а не лезть в системные настройки-тоже пашет! p.s. и может конечно это кажется только, но админка вроде еще шустрее стала!)
новый косяк обнаружил. если сайтов несколько, а кеш получается обший, то они в кеше перезаписывают настройки друг друга. Это я имею ввиду не кешированные странички, а именно работу в админке. то есть надо какой то общий ключ вводить для сайта по типу site_name… добавить системную настройку cache_prefix и все работает!
единственное при очистке кеша — очищается кеш сразу у всех сайтов, тогда либо вешать на каждый сайт по отдельному сокету, и все будет работать независимо, либо если сайты небольшие то я думаю можно на это дело забить…
Админка будет шустрее работать, так как каждый Ajax-запрос — это инициализация MODX-а со всеми конфигами и т.п. А раз они в кеше, то хоть чуть-чуть, но быстрее все.
Так про cache_prefix вообще забывать не стОит.
Вот это уже точно не скажу.
<?php switch($modx->event->name){ case 'OnWebPageComplete': if (!empty($modx->resource) && $modx->resource->get('cacheable') && $modx->resource->get('published') && $modx->resource->_output != '' && $_SERVER['REQUEST_METHOD'] == 'GET' ) { $key = 'index.php?'; $key .= str_replace('&', '&',($_SERVER['QUERY_STRING'])); $modx->log(1, "Key: '{$key}'"); $modx->cacheManager->set($key , $modx->resource->_output); } break; default:; }
Проверка на GET чтобы если отправили форму не кешировалась страница с ответом.
$key = 'index.php?';
Чтобы ключ был красивее. Modx его все равно модифицирует. в
$this->key . '/' . $key;
$this->key это системная настройка cache_key Так что если cache_key = site.com Тогда в конфиге nginx пишем
set $memcached_key "site.com$uri?$args";
Проверка на GET чтобы если отправили форму не кешировалась страница с ответом.
Но это как раз и не стоит делать. Ведь GET — это в том числе и постраничность и прочие моменты, включая гет-поиск. Все, что не должно кешироваться — надо выносить в Ajax.
Либо предусматривать какой-то GET-параметр типа nocache=1, чтобы явно указывать, что ее кешировать не надо. Если добавится в запрос этот параметр, для нгинкса это уже будет новый адрес, и он отправит запрос на MODX. MODX не сформирует полный кеш страницы в мемкеш, а значит и нгинкс все время будет отдавать не кешированную страницу.
Приветь! Николай настроил по заметкам! html контент работает но в админке вот таки логи ошибок
[2016-03-12 15:23:52] (ERROR @ /index.php) Key: '/index.php?q=news/1453/' [2016-03-12 15:23:54] (ERROR @ /index.php) Key: '/index.php?' [2016-03-12 15:25:26] (ERROR @ /index.php) Key: '/index.php?'
Привет.
Удали $modx->log(1, «Key: '{$key}'»); Это для отладки было.

Добавить комментарий