Интеграция Zend_Cache_Frontend_Page, Nginx и Memcached или 1000 запросов в секунду

Сколько грузится ваш движок? Я имею в виду число врмя генерации скрипта? 0,5 0,6 или может быть аж целую секунду? 🙂 Наш вот срабатывает за 0,8. После последних оптимизаций (см. предыдущие статьи) стал отрабатывать за 0,5. Это примерно 2 запроса в секунду. Хотите раскажу, как довести это число до 1000*?

Что в наличии?

1. Несколько статей по кэшированию:

http://www.opennet.ru/base/net/nginx_memcached.txt.html

http://habrahabr.ru/blogs/nginx/76315/

http://habrahabr.ru/blogs/hi/72539/

http://habrahabr.ru/blogs/hi/79876/

http://highload.com.ua/index.php/2010/04/06/nginx-memcached-ssi-кеширование-страниц-и-блоков-partials/

http://dklab.ru/chicken/nablas/56.html

2. Из ПО: Zend Framework, Nginx, Memcached, Mysql.

Что будем кэшировать?

Картинки в и всю статику в одной из прошлых заметок мы уже поместили на флешку, осталась динамика, а именно, вывод php-движка на Zend Framework и некоторых php-утилит.

HTTP-запросы могут приходить от зарегистрированных пользователей, или от гостей. Информацию зарегенных пользователей кэшировать не будем, из-за большой мороки с инвалидацией кэша.
*Значит будем будем кэшировать запросы только от гостей, и только GET-запросы.

Куда и как кэшировать?

Тут, собственно, альтернатив нет. Memcache, на данный момент самый быстрый, так что будем кэшировать в него. Сохранять данные в кэш будет ZF(PHP), а читать из него – Nginx. Благодаря этому, нам удастся вообще избежать обращения к PHP при выдаче страниц гостем, избежать файловых операций (т.к. страничка находится в памяти), что по идее должно дать нам большой припрост производительности.

Проблемы

Но не всё так просто, есть несколько проблем.

1. Для кэширования HTML-вывода будем использовать Zend_Cache_Frontend_Page. Всё в нём хорошо, за исключением того, что в кэш он кладет не чистую HTML-страничку, а JSON массив, состоящий из странички и заголовков.

Nginx же при чтении просто вытаскивает элемент из кэша и выдает юзеру. А юзеру не очень то понравится, какой-то JSON. Я уж не говорю про поисковых ботов.

2. Второй компонент Zend_Cache_Backend_Memcache тоже добавляет в данные свою информацию, а именно lifetime и текущее время.

В итоге в кэш идёт многомерный JSON-массив.

3. Для составления ключа кэша Zend_Cache_Frontend_Page ипользует md5() от кучи параметров.

А Nginx просто не сможет собрать такой ключ. Ну на самом деле сможет, но для этого надо перекомпилировать его с поддержкой perl-модуля ngx_http_perl_module. И после этого вставить такую вот процедуру в конфиг:

Также вам понадобится модуль для Perl Digest::MD5. Скажу так, у меня не получилось перекомпилить nginx с поддержкой этого модуля. если у вас получится – пишите в комментах.

Второй вариант решения проблемы – это формировать в Zend Framework нормальные ключи кэша, которые понимает NGINX. Казалось бы, – вот оно решение берем ключ вида:

$cacheKey = ‘nginx_’.$_SERVER[‘HTTP_HOST’].$_SERVER[‘DOCUMENT_URI’].’?’.$_SERVER[‘QUERY_STRING’];

и всё готово. А нет. Ключи со слешами в Zend Framework не разрешены, срабатывает валидация в функции _validateIdOrTag() см. закомменченный кусок.

Придётся её вырубить, чтобы не портила всю малину.

Решение

Мы сделаем свой кэш – с преферансом и куртизанками, подумал я… и сделал! Для этого пришлось создать/изменить следующие компоненты:

1. ZendExtra_Cache_Frontend_Page extends Zend_Cache_Frontend_Page

– сделал нормальный ключ кэша, который понимает Nginx

– убрал проверку ключа кэша, чтобы проходили слеши

– переделал сохранение и чтение кэша, чтобы записывались чистые данные, без заголовков

– перенес некоторые функции, которые делали статические вызовы функции валидации

2. ZendExtra_Cache_Backend_RawMemcached extends Zend_Cache_Backend_Memcached

– сделал, чтобы бекэнд сохранял чисты данные, без lifetime и time() внутри них. Выключил сериализацию.

–  сделал чтение данных из кэша без десериазизации

3. ZendExtra_Controller_Plugin_PageCache

– плагин запускает кэширование, только для гостей

4. Прописал новые компоненты в файле загрузки (Bootstrap.php), чтобы грузились мои отнаследованные компоненты, а не стандартные.

Сорцы

ZendExtra_Cache_Frontend_Page

ZendExtra_Cache_Backend_RawMemcached

ZendExtra_Controller_Plugin_PageCache

Bootstrap.php

Конфиг Nginx

Тестирование и бенчмарки

Тестирование проводилось на горчячем кэше (страница уже в памяти). Тестовый стенд: Sony VAIO VGN-SR11MR Core2Duo 2,26 4Gb Ram.

Результаты

Время обработки запроса в среднем: 10 мс., или 1006 запросов в секунду. Думаю это замечательный результат! 🙂 Оптимизация прошла успешно!

Исходники

Для тех кто хочет пощупать у себя этот метод, выкладываю исходники классов, упомянутых в этой статье. nginx_memcached_zf

UPD. При формировании ключа и наличии русских значений переменных, кэширование на работает. Необходимо формировать ключ с учетом кодировки строки запроса:
ZendExtra_Cache_Frontend_Page.php

UPD2 Также по умолчанию не ставится lifetime. Для этого надо добавить фукнцию:
ZendExta_Cache_Frontend_Page

и в бутстрапе делать присовение lifetime:

Архив не правил.

4 Comments

  1. Отличное решение, оценил 🙂

    Подскажите, а как же быть с динамическими частями страницы?
    Например, баннеры, либо для авторизованных пользователей другой блок вместо формы авторизации?

    Можно ли как-то настроить nginx, чтобы он опирался на сессии?

  2. Сотня тысяч хитов в сутки, при пользовательской активности на протяжении 8 бизнес-часов — это 3-4 запроса в секунду. Вы действительно этим гордитесь?

    1. Речь идёт о времени отдачи страницы. И да, я этим горжусь в тех условиях (оборудование, канал и т.д.), что у меня на тот момент были. А в чём собственно проблема? Вот, например данные для Яндекса.

      Мои показатели лучше, чем у Яндекса (у него 91мс, у меня 10мс, у него 107 запросов в секунду, у меня 1006). Таки да, есть чем гордиться.

Leave a Comment