Кэширование и оптимизация работы сайта на базе Zend Framework
После того, как вы создали сайт, его хорошо бы заоптимизировать и настроить кэширование. Выгода от всего этого, безусловно, огромная:
- у пользователей сайт загружается гораздо быстрее;
- уменьшается количество запросов к серверу, вследствие чего уменьшается нагрузка на него;
- многие ресурсоемкие запросы к БД не выполняются каждый раз заново, а просто берутся из кэша и возвращаются запросившему их скрипту, благодаря чему снижается нагрузка на базу данных;
- если на странице нету никаких изменяющихся данных, то закэшировав ее целиком вы добьетесь того, что не будет грузиться даже сам огромный Zend Framework с кучей библиотек, хелперов, моделей и контроллеров, а браузеру отдастся сразу же готовый html-код.
Этот список можно продолжать и продолжать, потому — приступаем к оптимизации. На готовом сайте делать оптимизацию я рекомендую именно в том порядке, в котором она описана в статье.
Объединение и сжатие всех стилей и скриптов
Обычно на более-менее крупных сайтах используется несколько файлов со стилями на каждой странице. А уж для яваскриптов это вообще абсолютно обычная ситуация: файл с jquery, пара расширений к нему, свои скрипты и либы и т.д. На некоторых наших сайтах доходит до подключения 4 файлов стилей и 8 яваскриптов. Разумеется, это очень удобно для разработки, так как все разбито по файлам и структурировано, но очень плохо для посетителей Ваших сайтов. Ведь в приведенном выше примере браузеру пользователя придется направить целых 12 запросов к серверу, чтобы получить все нужные файлы. К тому же, в яваскриптах и стилях всегда есть разные отступы, лишние пробелы, комментарии. Это, опять же, очень удобно для разработчика, но совсем не нужно пользователям — вес файла то увеличивается.
Для выхода из ситуации видится одно прекрасное решение — объединить все файлы стилей в один, а скриптов — в другой, удалив из них предварительно все отступы, комментарии и т.д. Можно это делать и вручную, если на всех страницах сайта используется один и тот же набор стилей и скриптов и вы знаете, что он точно не будет меняться. Но если ваш проект сделан на Zend Framework и вы частенько дорабатываете свои сайты, то гораздо лучше сделать этот процесс автоматическим с помощью специальных помощников вида MagicHeadScript и MagicHeadLink и библиотек для сжатия js и css файлов.
Со статьей автора этих библиотек вы можете ознакомиться по адресу Я же немного доделал эти библиотеки для большего, как мне кажется, удобства использования. Полный набор всех библиотек, используемых в статье, можно скачать по ссылке в конце статьи.
В статье автором описан принцип их работы, но я тоже расскажу о нем в двух словам и поясню их использование конкретным примером. Итак, чтобы эти хелперы заработали корректно, должны быть выполнены следующие условия:
- все скрипты и стили должны быть подключены с помощью помощников вида headScript() и headLink()
- нужно подключить классы с MagicHeadLink, MagicHeadScript, а также классы Minify и JSMin
- нужно создать папки для хранения кэшей и указать их скрипту, соответственно, строчками: My_MagicHeadScript::setConfig(’/public/cache/scripts’) и My_MagicHeadLink::setConfig(’/public/cache/styles’)
- требуется в шаблоне вызвать $this->magicHeadLink() и $this->magicHeadScript()
Все довольно просто. А теперь, для лучшего понимания, приведу пример и расскажу, как это делаю я. При создании приложения я всегда, первым делом, создаю базовый абстрактный контроллер, называю его firstController и все последующие контроллеры наследую от него. Это довольно удобно, особенно когда во всех контроллерах есть какой-то одинаковый функционал. В этом случае его очень удобно выносить в этот firstController и потом просто вызывать из других контроллеров. Но, повторюсь, выносить туда стоит только тот функционал, который Вам требуется во всех остальных контроллерах. В методе этого же контроллера (я его называю initMain) очень удобно будет добавить через
01
02$this->view->headScript()->appendFile('/public/scripts/script.js');
$this->view->headLink()->appendStylesheet('/public/styles/style.css');
Основные стили и скрипты, которые используются на всех страницах сайта. Например, библиотеку jQuery. Далее, в этом же методе, задаем папки для хранения кэшей скриптов и стилей:
01
02My_MagicHeadScript::setConfig('/public/cache/scripts');
My_MagicHeadLink::setConfig('/public/cache/styles');
Все, теперь все настроено и остается только во всех остальных контроллерах не забывать вызывать этот базовый метод. Если в каких-то других контроллерах потребуется добавить еще скрипты или стили, то можете точно так же добавлять их через помощники headScript() и headLink(). В результате, при выводе они так же подхватяться помощниками magicHeadScript и magicHeadLink и соберутся в один файл. Ну и чтобы все выводилось, надо в файле сосновным шаблоном (у меня это \application\views\scripts\index.phtml) вставить строчки My_MagicHeadScript::setConfig(’/public/cache/scripts’); My_MagicHeadLink::setConfig(’/public/cache/styles’); Сами файлы скриптов magicHeadScript и magicHeadLink можно хранить в любом месте, но я предпочитаю папку /libs/My/ Таким образом, если вы хотите, чтобы библиотеки подключались автоматически при вызове, вы должны добавить папку /libs в основные пути.
Настройка серверного кэширования на базе Zend Framework
За кэширование в Zend’е отвечает модуль Zend_Cache. Модуль довольно гибкий и удобный, обладает кучей возможностей, про которые очень хорошо написано в официальном мануале. В статье же рассмотрим конкретные примеры и один подводный камень, который встречается при использовании этого компонента.
Грубо говоря, кэшировать с помощью Zend можно двумя путями: либо страницу целиком, либо какие-то ее части/блоки. Целиком хорошо кэшировать, когда страница создается динамически из разных данных, с использованием запросов к БД, но реально изменяется довольно редко. Хороший пример — это страница с Портфолио на нашем сайте. Работы добавляются не очень часто и чтобы не грузить базу данных лишними запросами можно закэшировать страницу и отдавать пользователю готовый html-код. При этом даже не будет грузиться весь Zend. Но бывает, что целиком кэшировать — не очень удобно. Допустим, когда на странице есть блок с редко изменяющейся информацией и есть блок с новостями. Тогда кэширование целой страницы нам уже не подходит. Но можно же сделать это только для того редко изменяющегося блока, а новости будут так же браться каждый раз из БД динамически. При этом нагрузка все равно снизится, ведь к той же базе данных будет идти все равно меньше запросов. Теперь разберемся на конкретных примерах, как это реализовывать.
Кэширование страницы целиком
Для этого мы будем использовать фронтенд Page. В , казалось бы, все очень хорошо расписано про этот случай. Есть даже пример. Одна беда — пример этот не работает. И проблема заключается в том, что когда мы вызываем $cache->start(), то Zend, почему-то, не может подставить сам идентификатор и на основе его создать кэш. Потому если вы попробуете применить пример из руководства, то все запуститься, код отработает, но файл в папочке с кэшами не появится. Почему так происходит — не известно. Поэтому я предлагаю передавать Zend’у этот идентификатор вручную. Он должен быть уникальным, поэтому в случае кэширования страниц целиком, будет вполне логично передавать хэш урла страницы. Для каждой страницы ведь используется свой урл. Таким образом, на каждую страницу у нас будет свой файлик с кэшем, причем только один — как раз то, чего мы и хотим. Но возникает небольшая проблемка — адрес может быть набран как http://site.ru/about, так и http://site.ru/about/ Хотя, фактически, это одно и то же, но урлы различаются на один символ и в кэше будет лежать уже два одинаковых файла для этих урлов, но с разными именами. Нам это не нужно, потому при создании идентификатора будем просто отбрасывать последний символ «/» (если он есть).
Вроде со всем разобрались и остается только один вопрос — где и как подключать это кэширование? Тут вариантов несколько, но мне больше всего нравится подключение в плагине Front контроллера. Если вы не работали с ними, то советую ознакомиться с этой . Вообще говоря, плагины Front-контроллера штука очень мощная и интересная, может много где помочь. Но вернемся к нашей теме: в папочке /lib/My/ создаете файл Cache.php и пишете в него следующее:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27class My_Cache extends Zend_Controller_Plugin_Abstract{ public function dispatchLoopShutdown(){ $frontendOptions = array( 'lifetime' => 2592000, 'debug_header' => false, 'default_options' => array( 'cache' => true ) ); $backendOptions = array( 'cache_dir' => './tmp/', ); $cache_id=$_SERVER['REQUEST_URI']; $lastSymbol = $cache_id{strlen($cache_id)-1}; if($lastSymbol=='/'){ $cache_id = substr($cache_id, 0, -1); } $cache = Zend_Cache::factory('Page', 'File', $frontendOptions, $backendOptions); $cache->start(md5($cache_id)); } }
А в Вашем Bootstrap-файле вызываете этот плагин следующим образом:
01
02$front = Zend_Controller_Front::getInstance();
$front->registerPlugin(new My_Cache());
Также не забудьте создать в корне папку /tmp.
В моем примере кэшироваться будет весь сайт. Но если Вам нужны какие-то отдельные урлы, то можете воспользоваться опцией regexps и перечислить их. Такой пример уже есть в руководстве и описывать его я не буду.
Кэширование отдельных блоков
Тут будем использовать фронтенд Output. В , в принципе, написано про него, но его использование лучше будет пояснить на конкретном примере. Итак, в нужном вам контроллере пишете следующее:
01
02
03$frontendOptions = array('lifetime' => 604800, 'automatic_serialization' => true); $backendOptions = array('cache_dir' => './tmp/'); $cache = Zend_Cache::factory('Output', 'File', $frontendOptions, $backendOptions);
Этим мы настроили кэширование. Теперь о том как его использовать. Допустим, у Вас есть какой-то кусок кода, который запрашивает модель, получает от нее массив с данными и присваивает его переменной:
01$this->view->newsArray=$modelNews->getAllNews() ;
Чтобы закэшировать результат обращения к модели меняем этот кусок на следующее:
01
02
03
04
05if (!($newsArray = $cache->load('news'))) { $newsArray=$modelNews->getAllNews(); $cache->save($newsArray); } $this->view->newsArray=$newsArray;
Здесь ’news’ — это уникальная метка для кэша (в предыдущем примере мы использовали в качестве нее урл страницы). Таким образом, скрипт смотрит, есть ли в кэше данные с идентификатором «news». Если есть — сразу присваивает их переменной $this->view->newsArray, а если нету — делает запрос к модели.
В руководстве описываются еще и другие фронтенды, но в 95% случаев для оптимального кэширования хватит этих двух.
Настройка клиентского кэширования
Этот пункт проще всего в реализации. Как известно, у каждого браузера имеется свой собственный кэш, в который он может помещать как отдельные картинки и файлы скриптов/стилей, так и сам html-код. При этом загрузка всего этого добра из кэша происходит мгновенно, и к тому же не идет нагрузка на сервер — браузер к нему вообще не обращается, а берет все ресурсы у самого себяы. Но по умолчанию браузер этого не делает — надо ему об этом явно сообщить. Будем рассматривать случай, когда сайт стоит на сервере Apache. Тогда для наших целей нам отлично подходят модуль mod_expires и файл .htaccess, где мы, собственно, и будем прописывать настройки для него.
Теперь определимся с тем, что именно будем кэшировать и последствия этого. Клиентское кэширование очень сильно отличается от серверного — тут мы уже не сможем очистить темповую директорию для отображения исправленной страницы пользователю. Если браузеру дали команду закэшировать картинку logo.jpg на 90 дней, то она так и будет у него лежать 90 дней. И что бы вы ни делали на сайте, как бы ни изменяли этот самый logo.jpg, пользователь эти 3 месяца будет видеть старую картинку, однажды загруженную его браузером. Если, конечно, сам не решит очистить свой кэш. Точно такая же ситуация со стилями, скриптами, иконками и т.д. Но из этой ситуации есть выход. Пользователю все же можно показать новую картинку вместо закэшированной старой. Для этого, надо ее просто переименовать. Ну и, разумеется, прописать на сайте ссылку на новую картинку. То есть, если вы назовете исправленную картинку logo1.jpg, и пропишете на сайте путь к ней, то пользователь увидит уже ее, а не старую, так как кэширование идет именно по именам файлов! Как видите, не очень удобный способ, но в крайних случаях вполне подойдет.
С кэшированием же яваскриптов и стилей, если вы будете использовать описанную выше методику их сжатия и компоновки, такой проблемы не возникнет. Как я писал, имена сжатых файлов — это хэш, зависящий от имен группруемых файлов и их содержимого. Таким образом, если вы измените хоть одну буковку в каком-нибудь из скриптов или стилей, то хэш изменится и файл новой скомпанованной версии будет уже иметь другое имя, а следовательно и не будет браться из кэша браузера. А вот с кэшированием html-кода так не пройдет — придется менять имя страницы, чтобы выкинуть его из кэша. А это, очень часто, просто недопустимо.
Итак, наиболее целесообразным я вижу клиентское кэширование абсолютно всей графики, стилей, скриптов и иконок. Чтобы включить это кэширование, надо прописать в корневом файле .htaccess следующее:
01
02
03
04
05
06
07ExpiresActive On ExpiresByType text/javascript A7776000 ExpiresByType image/gif A7776000 ExpiresByType image/jpeg A7776000 ExpiresByType image/png A7776000 ExpiresByType text/css A7776000 ExpiresByType image/x-icon A7776000
Здесь мы включаем кэширование на 90 дней (7776000 секунд) для нужных файлов исходя из их MIME-типа, который задается сервером. Иногда этот метод работает криво, потому можно задать кэширование еще и по расширению файлов с помощью директивы FilesMatch:
01
02
03
04
05<FilesMatch \.(css|js|ico|png|jpg|jpeg)$ > Header append Cache-Control public ExpiresActive On ExpiresDefault "access plus 90 days" </FilesMatch >
Какой из этих методов использовать — решать Вам. Попробуйте их по очереди и протестируйте сайт с помощью плагина Yslow к браузеру Firefox. Какой покажет лучшие результаты — тот и применяйте. Хотя я, на своих проектах, всегда использую их оба — где не поможет первый метод, там сработает второй (или наоборот) и в результате закэшируются все нужные файлы.
Заключение
Итак, мы рассмотрели наиболее важные и базовые методики, которые сделают работу с сайтом для Ваших пользователей быстрой и приятной, а нагрузку на сервер снизят до чисто условных значений. Если вы дочитали до этого момента, то поздравляю Вас, так как тема это, все же, довольно непростая, особенно для новичков. И если у Вас появятся какие-то вопросы по статье — задавайте их в комментариях, постараюсь все объяснить.
Скачать библиотеки в .rar (14 Кб)
- Рубрики
- Проектирование интерфейсов (1)
- Из жизни студии (1)
- Оптимизация (1)
-
Программирование (2)
- Zend Framework (1)
- jQuery (1)