Ускоряем Joomla в 1000 раз

Опубликовано admin в Пнд, 25/04/2011 - 11:31
Целевая аудитория: программисты, администраторы Joomla и другие пользователи имеющие элементарные навыки работы с PHP.

Joomla — медленная, очень медленная. Joomla «из коробки» редко может выдавать более 4 запросов в секунду. Включим кеш, поставим PHP accelerator, займемся оптимизацией и возможно мы сможем получить 20 запросов в секунду.

А что дальше, менять CMS? Конечно менять, но слишком часто пользователи просятся назад на Joomla. Сразу оговорюсь, задача статьи не повлиять на выбор CMS, обсуждать скорости работы различных CMS решений или недостатки архитектуры Joomla.

Данные рекомендации будут полезны, если:
— у вас или вашего клиента есть вебсайт на Joomla
— ваш сайт состоит в основном из статического контента
— вам недостаточно получать от сервера 20 запросов в секунду, вам надо 2000-3000.
— вы задумались насколько время отклика вебсайта влияет на рейтинг в google. Немного информации на эту тему тут http://googlewebmastercentral.blogspot.com/2010/04/using-site-speed-in-web-search-ranking.html

Joomla работает под PHP, даже если используется кеширование на уровне модулей Joomla, все равно используется PHP и происходит рендеринг страницы при каждом обращении. Это означает, что скорости в 2000 запросов в секунду будут недостижимы. Заставим Joomla сохранять копию сгенерированной страницы на диск, а Apache считывать эту копию напрямую, минуя PHP для всех последующих пользователей. Это старый добрый подход, который кстати может быть применен на других CMS, но только для статического контента. На Joomla используем тип Article, который как раз и представляет страницу со статическим контентом.

Создаем новый плагин для Joomla, всего два файла:

htmlcache.xml — файл описания плагина
<?xml version="1.0" encoding="utf-8"?>
<install version="1.5" type="plugin" group="system">
<name>System - HTML cache</name>
<author></author>
<creationDate>March 2010</creationDate>
<copyright></copyright>
<license>http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL</license>
<authorEmail></authorEmail>
<authorUrl></authorUrl>
<version>0.6</version>
<description>Creates static HTML versions of content pages. It will only cache SEO URLs, *.php pages will not be cached. </description>
<files>
<filename plugin="htmlcache">htmlcache.php</filename>
</files>
<params>
<param name="html_cache_dir" type="text" default="" label="HTML Cache dir" description="Set the joomla HTML cache directory."/>
<param name="cache_view_1" type="text" default="article" label="View to cache 1" description="Set view type to be cached. Only HTTP content can be cached."/>
<param name="cache_view_2" type="text" default="" label="View to cache 2" description="Set view type to be cached. Only HTTP content can be cached."/>
<param name="cache_view_3" type="text" default="" label="View to cache 3" description="Set view type to be cached. Only HTTP content can be cached."/>
<param name="cache_view_4" type="text" default="" label="View to cache 4" description="Set view type to be cached. Only HTTP content can be cached."/>
</params>
</install>



Теперь создаем обработчик события onAfterRender. Задача обработчика перехватывать только статьи типа Article, создавать в каталоге html_cache_dir структуру из папок в соответствии со структурой вебсайта и сохранять туда контент страницы. Дополнительно перед сохранением можно прогонять страницу через оптимизатор HTML (например этот Minify_HTML (http://code.google.com/p/minify/source/browse/trunk/min/lib/Minify/HTML.php)), которые оптимизирует размер страницы, убирая лишние комментарии и пробелы и тем самым увеличивая эффективность работы вебсайта.

Отдельно отметим, что данный код работает для вебсайтов с включенным SEO и не кеширует страницы с любыми расширениями типа php или html. Кешироваться будут только URLs вида:
/about
/software/catalog


Код придется поправить, если вы не используете SEO.

htmlcache.php — непосредственно код.
<?php
defined( '_JEXEC' ) or die( 'Restricted access' );
jimport( 'joomla.plugin.plugin' );
class plgSystemHtmlcache extends JPlugin
{
function plgSystemHtmlcache(& $subject, $config)
{
parent::__construct($subject, $config);
}

function onAfterRender()
{
global $mainframe;
if($mainframe->isAdmin()) { return; } //do not cache admin pages

$document =& JFactory::getDocument();
$doctype = $document->getType();
$user =& JFactory::getUser();
if($user->get('guest') != 1) { return; } //Only cache for non logged in users
if ( $doctype != 'html' ) { return; } // Only render for HTML output
$html_cache_dir=$this->param('html_cache_dir');
if($html_cache_dir=='') { return; } //Exit if no html_cache_dir specified
if(!file_exists($html_cache_dir)) { mkdir($html_cache_dir); } //try to create folder if it does not exist
// Only render for provided views
if ((JRequest :: getVar('view')) != $this->param('cache_view_1') &&
(JRequest :: getVar('view')) != $this->param('cache_view_2') &&
(JRequest :: getVar('view')) != $this->param('cache_view_3') &&
(JRequest :: getVar('view')) != $this->param('cache_view_4')) { return; }
$relativePath=$this->request_uri();
if (strpos($relativePath, '.')) { return; } //exit if found DOT in the request_uri, we do not want to cache anything other than SEO
$relativePath=str_replace('/',DS,$relativePath);
//$body = Minify_HTML::minify(JResponse::getBody());
$body =JResponse::getBody();
$fullPath=$html_cache_dir.$relativePath;
$parts=explode(DS,$relativePath);
$currentPath=$html_cache_dir.DS;

foreach( $parts as $p){
if($p==''){
continue;
}
$currentPath.=$p;
if((!file_exists($currentPath))&&(!is_file($currentPath))){
mkdir($currentPath);
}
$currentPath.=DS;
}//end for each
$indexFile=$currentPath.DS.'index.html';
if(!file_exists($indexFile)){ $this->writeToFile($indexFile,$body); }
}

function writeToFile($fileName,$content){
$handle=fopen($fileName,'w');
fwrite($handle,$content);
fclose($handle);
}

function request_uri(){
if (isset($_SERVER['REQUEST_URI'])){
$uri = $_SERVER['REQUEST_URI'];
}else{
if (isset($_SERVER['argv'])){
$uri = $_SERVER['PHP_SELF'] .'?'. $_SERVER['argv'][];
}else{
$uri = $_SERVER['PHP_SELF'] .'?'. $_SERVER['QUERY_STRING'];
}
}
return $uri;
}

function param($name){
static $plugin,$pluginParams;
if (!isset( $plugin )){
$plugin =& JPluginHelper::getPlugin('system', 'htmlcache');
$pluginParams = new JParameter( $plugin->params );
}
return $pluginParams->get($name);
}
}


Оба файла можно закачать в /plugins/system или поместить в htmlcache.zip и установить через административный интерфейс Joomla. После установки задаем html_cache_dir=/opt/www/html/cache/content в настройках плагина и включаем плагин (Enabled: Yes).
При первом обращении к странице в папке /opt/www/html/cache/content должны появиться папки и файлы — это означает что плагин работает и все в порядке с разрешениями на запись.

Осталось заставить Apache отдавать эти файлы без обращения к PHP коду. В .htaccess, сразу после RewriteEngine on добавляем:
RewriteCond %{REQUEST_URI} (/|/[^.]*)$ [NC]
RewriteCond %{DOCUMENT_ROOT}/cache/content/%{REQUEST_URI}/index.html -f
RewriteRule (.*) /cache/html/$1/index.html [L]

Перезапускаем apache и проверяем через браузер. Для того чтобы проверить производительность запустим (под Linux):
ab -n 10000 -c 50 -k www.azati.com/
...
Concurrency Level: 50
Time taken for tests: 4.294054 seconds
Complete requests: 10000
Failed requests: 0
Write errors: 0
Keep-Alive requests: 10000
Total transferred: 71155755 bytes
HTML transferred: 67695750 bytes
Requests per second: 2328.80 [#/sec] (mean)
Time per request: 21.470 [ms] (mean)
Time per request: 0.429 [ms] (mean, across all concurrent requests)
Transfer rate: 16182.38 [Kbytes/sec] received
...

Тест лучше запускать несколько раз, чтобы обеспечить «прогрев» для Apache. Запускать можно на самом вебсервере, чтобы исключить задержки в сети или по сети, но числа будут другие.

www.azati.com замените адресом своего сервера, www.azati.com можно тестировать для сравнения. Не боимся, сайт не падает и статистика google не портится.

К сожалению, скорость не дается даром:
— Пропадает возможность редактировать статьи (Articles) через Joomla front-end, только через административный интерфейс.
— После редактирования статьи, необходимо очищать кеш через административный интерфейс Tools -> Сlean cache

Взвешиваем и решаем оправдан ли подход для вашего вебсайта.

P.S. Если у вас есть динамический контент, можно организовать периодическую очистку кеша (например раз в час), и тем самым получить быстрый сайт, который достаточно часто обновляется.



Источник: http://habrahabr.ru/blogs/personal/92783/