Fi1osof 26 августа 2013 0 44
В продолжение темы. Очень кратко опишу свой опыт с экспериментом сохранения кода страницы в 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",
);


Вот тогда он сразу будет мемкешед юзать.
44 комментария
vgrish1
vgrish 26 августа 2013г в 00:32 #
Молодец! все толково расписал. Насчет только для визиток, я бы не был так категоричен. Nginx много чего умеет…
Fi1osof1
Fi1osof 26 августа 2013г в 00:35 #
Так я сказал «пока». Докрутить многое можно. Да и на уровне плагина можно разрулить что кешировать так, а что нет (допустим, личный кабинет не кешировать).
Но это так, эксперимент. Я более плотно планирую делать это на уровне node.js То есть нода не все еще умеет, чтобы так сразу фрейм заменить, но ее можно использовать как умный кешер. То есть и данные в памяти держать, и логику рулить можно. В общем, планирую nginx+MODX+node.js
vgrish1
vgrish 26 августа 2013г в 13:28 #
memcache зараза страницу компрессирует как ни крути, а вот с memcached все нормально)
вот это у тебя
<code>$key = '/index.php?';</code>
вообще лишнее, можно выкинуть…
и добавить еще $expire
<code>$modx->cacheManager->set($key , $modx->resource->_output, $expire);</code>
выставить время жизни кеша.
и в nginx правильнее наверно будет
"default/$args"
Fi1osof1
Fi1osof 26 августа 2013г в 13:37 #
memcache зараза страницу компрессирует как ни крути
А ты смотри phpinfo() раздел memcache. У него тоже свои настройки есть, типа max_chunk_size или типа того. Наверняка тоже можно поднять значение.

вообще лишнее, можно выкинуть…
и в nginx правильнее наверно будет
«default/$args»
Да? А кто запрещает создать еще какой-нибудь внутренний php-файл? И что тогда? И тогда нафиг все побьется. А ты предлагаешь все жестко завязать на единственный php-файл. Не, я так не буду делать.

и добавить еще $expire
Это я может позже поковыряю, но у меня есть подозрение, что это не так работает, как хотелось бы. Думаю, что это значение для самого MODX-а, а не для мемкеша. То есть как работает этот параметр для MODX-а? — кеш есть, но время уже не актуальное, и MODX его игнорит. Но если это так, то gnixn-у на это пофигу будет, он получит кеш и все. Хотя это только предположение, проверять надо.
vgrish1
vgrish 26 августа 2013г в 14:11 #
да настройки есть конечно, аналогичные кстати… но не получилось, возможно механизм сжатия по другому работает.

насчет побьется я не совсем уверен, так как неделю назад вообще про nginx не слышал. Но я думаю ты ошибаешся… Пройдись по конфигу nginx и посмотри логику.

$expire работает — уже проверял.
Fi1osof1
Fi1osof 26 августа 2013г в 14:27 #
насчет побьется я не совсем уверен, так как неделю назад вообще про nginx не слышал. Но я думаю ты ошибаешся… Пройдись по конфигу nginx и посмотри логику.
Я вчера часов 6 с этим конфигом провозился, так что испробовал различные варианты. Смотри вот правило:
# Именнованная лакация (правило)
    location @modx { 
        # выполняем подмену на index.php
        rewrite ^/(.*)$ /index.php?q=$1 last;
    }  
Так как ЧПУ MODX-а построено полностью на реврайтах, а не на реальных файлах, именно благодаря ему все УРЛы и преобразуются в index.php Но это происходит только потому что запрашиваемый файл не найден. Но если файл будет найден, то этого преобразования не будет, а останется реальный УРЛ. И в итоге ключ /index.php не будет соответствовать никогда в этом случае.
Но это конечно же ситуация исключительная, поэтому конечно ты можешь пренебрегать этим моментом и сделать так, как ты предложил — в 99% случаев оно действительно будет работоспособным. Но у меня свои тараканы в голове и я терпеть не могу неуниверсальность, поэтому у себя оставлю так.
vgrish1
vgrish 26 августа 2013г в 14:43 #
ну в данном случае у тебя
# устанавливаем ключ-переменную для запроса к мемкешу
set  $memcached_key       "default/$uri?$args";

и все! это же касается только memcached. При чем тут реальные файлы?
только лишняя переменная=index.php? в ключе
Fi1osof1
Fi1osof 26 августа 2013г в 15:05 #
При чем тут реальные файлы?

При том, что здесь используется переменная $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-конфиг более актуальный :-)
vgrish1
vgrish 26 августа 2013г в 15:20 #
ну так бы сразу и сказал, что у тебя свои замуты)
Fi1osof1
Fi1osof 26 августа 2013г в 16:23 #
у меня не замуты, а универсальность :-)
vgrish1
vgrish 26 августа 2013г в 17:56 #
как думаешь, имеет смысл memcached на сокеты перекинуть? По идее еще прирост в скорости должен быть…
вот эти настройки в обработчике меняю
('memcached_server', $options, 'localhost:11211')
и по идее будет работать? или тут все сложнее устроено?
Fi1osof1
Fi1osof 26 августа 2013г в 19:16 #
Честно скажу: сам с сокетами особо не работал. Знаю, что сокеты быстрее всяко, но настройку этого всего не делал.
vgrish1
vgrish 26 августа 2013г в 23:41 #
подскажи как и где переключиться на сокет? пробовал в конфиге прописать как
$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',

);

не срабатывает нифига!
хелп!)))
vgrish1
vgrish 27 августа 2013г в 00:00 #
Fi1osof1
Fi1osof 27 августа 2013г в 00:34 #
Я же говорил, что с сокетами особо не заморачивался. Ты топик напиши как сокеты подключал, с какими подводными камнями столкнулся.
vgrish1
vgrish 27 августа 2013г в 07:17 #
я пока что локально подключился и тесты провел, чтоб узнать будет ли какая выгода от такого перехода.
Ты как спец по кешированию подскажи — как перевести всю систему кеширования modx на сокеты теперь? я немного не допонимаю, это именно в файле обработчика кеша нужно сделать? или еще где то нужно пилить?
по идее нужна только замена
$memcache->connect('localhost', 11211)

на
$memcache->connect('unix:///var/run/memcached/mem.socket', 0)

и все должно пахать!
Fi1osof1
Fi1osof 27 августа 2013г в 13:53 #
Так ты в исходники почаще заглядывай. Вот __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))));


И, кстати, как я и предполагал, должна быть возможность в принципе отключить для него компрессию.
vgrish1
vgrish 27 августа 2013г в 15:28 #
да компрессия бог с ней… на 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)
это разбор строки… и если сокет прописывать то и тут надо как то менять…
vgrish1
vgrish 27 августа 2013г в 15:29 #
поэтому нужна твоя помощь!)
Fi1osof1
Fi1osof 27 августа 2013г в 16:22 #
Так ты не пиши 'unix:///var/run/memcached/mem.socket', 0
Это же сразу два параметра. Пиши только unix:///var/run/memcached/mem.socket
vgrish1
vgrish 27 августа 2013г в 16:51 #
да я уже и так и сяк пробовал… Как время будет посмотри
Fi1osof1
Fi1osof 27 августа 2013г в 16:23 #
я в коде не разбираюсь но у меня чувство что explode(':', $server)
Да, это разбивка строки.
Но мне сечас некогда с этим экспериментировать, я пока очень занят.
vgrish1
vgrish 27 августа 2013г в 16:58 #
а как то можно прописать в обработчике, чтоб он мне логи скинул в журнал чему какая переменная равна?
Fi1osof1
Fi1osof 27 августа 2013г в 17:05 #
Так залезь в код и посмотри что чему равно.
vgrish1
vgrish 27 августа 2013г в 19:55 #
все вроде понял что, к чему… только вот эта строчка непонятна
$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 торчат?
Fi1osof1
Fi1osof 27 августа 2013г в 20:26 #
$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 в настройках и все.
vgrish1
vgrish 27 августа 2013г в 20:33 #
ну я логически так и додумал, но ошибка все таже —
Error caching time of next auto publishing event
Could not cache context settings for mgr.
Could not cache context settings for web.

вот у меня чувство что где то еще — localhost:11211 торчит по умолчанию…
Fi1osof1
Fi1osof 27 августа 2013г в 20:41 #
Вот и ковыряй. Думаешь у меня всегда все сразу получается? Нет, иногда часами сидишь воюешь, а иногда и днями.
vgrish1
vgrish 27 августа 2013г в 20:44 #
тогда может на $options всетаки нельзя забивать? он ведь у нас в каждой функции идет
$options= array())

либо еще какая то фигня мешает работе с сокетами
либо я хз
vgrish1
vgrish 27 августа 2013г в 20:47 #
у меня к тебе одна просьба будет — можешь в своем обработчике закомментировать строчку вот эту
$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))));

сбрось кеш и посмотри какую ошибку выдаст.
спасибо!
Fi1osof1
Fi1osof 27 августа 2013г в 20:54 #
Мне реально сейчас не до этого. Так что будь настоящим джедаем и воюй до последнего сам.
vgrish1
vgrish 27 августа 2013г в 20:55 #
порадовал)))
vgrish1
vgrish 28 августа 2013г в 09:38 #
все разобрался, все правильно я прописывал, надо было просто чуток подождать. настройки тоже кешировались и мои изменения с кешером не сразу применялись. сейчас все по новой прописал, подождал 5 минут — и все ок! Работает!)
vgrish1
vgrish 28 августа 2013г в 10:00 #
и насчет компрессии ты был прав, можно прям в кешере указать, а не лезть в системные настройки-тоже пашет!
p.s. и может конечно это кажется только, но админка вроде еще шустрее стала!)
Fi1osof1
Fi1osof 28 августа 2013г в 14:34 #
Админка будет шустрее работать, так как каждый Ajax-запрос — это инициализация MODX-а со всеми конфигами и т.п. А раз они в кеше, то хоть чуть-чуть, но быстрее все.
Fi1osof1
Fi1osof 28 августа 2013г в 14:33 #
Вот и славно.
vgrish1
vgrish 28 августа 2013г в 11:39 #
новый косяк обнаружил. если сайтов несколько, а кеш получается обший, то они в кеше перезаписывают настройки друг друга. Это я имею ввиду не кешированные странички, а именно работу в админке. то есть надо какой то общий ключ вводить для сайта по типу site_name…
добавить системную настройку cache_prefix и все работает!
vgrish1
vgrish 28 августа 2013г в 12:00 #
единственное при очистке кеша — очищается кеш сразу у всех сайтов, тогда либо вешать на каждый сайт по отдельному сокету, и все будет работать независимо, либо если сайты небольшие то я думаю можно на это дело забить…
Fi1osof1
Fi1osof 28 августа 2013г в 14:35 #
Вот это уже точно не скажу.
Fi1osof1
Fi1osof 28 августа 2013г в 14:34 #
Так про cache_prefix вообще забывать не стОит.
h
husband 23 декабря 2013г в 01:10 #
<?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";
Fi1osof1
Fi1osof 23 декабря 2013г в 01:23 #
Проверка на GET чтобы если отправили форму не кешировалась страница с ответом.
Но это как раз и не стоит делать. Ведь GET — это в том числе и постраничность и прочие моменты, включая гет-поиск. Все, что не должно кешироваться — надо выносить в Ajax.

Либо предусматривать какой-то GET-параметр типа nocache=1, чтобы явно указывать, что ее кешировать не надо. Если добавится в запрос этот параметр, для нгинкса это уже будет новый адрес, и он отправит запрос на MODX. MODX не сформирует полный кеш страницы в мемкеш, а значит и нгинкс все время будет отдавать не кешированную страницу.
T
TITAN-UZ 12 марта 2016г в 13:32 #
Приветь!
Николай настроил по заметкам! 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?'
Fi1osof1
Fi1osof 12 марта 2016г в 13:50 #
Привет.

Удали $modx->log(1, «Key: '{$key}'»);
Это для отладки было.
Авторизуйтесь или зарегистрируйтесь (можно через соцсети ), чтобы оставлять комментарии.