Yvision.kzYvision.kz
kk
Разное
Разное
399 773 постов41 подписчиков
Всяко-разно
0
09:10, 16 августа 2010

Кеширование блоков с помощью nginx

nginx + SSIМногим разработчикам знакома ситуация когда кешировать страницы сайта, скажем, на 5-10 минут нельзя всего из-за одного небольшого блочка, актуальность которого нужно поддерживать если не в реальном времени, то с временем «старения» не больше 5-10 секунд. При этом посещаемость сайта продолжает расти, растет время генерации страниц и c этим надо что-то делать…

  • Вариант решения 1: Подкрутить то, до чего не доходили руки последнее полгода. Все Вас поймут и передвинут сроки на другие задачи. Вы будете в роли «Супермена» один спасать сайт от непомерной нагрузки, решая проблему «бесплатно» (без доп. вливаний в оборудование). Вам может пригодиться статья «Тюнинг nginx».

     
  • Вариант решения 2: Улучшить техническую базу (докупить мозгов на сервер, улучшить дисковую систему, поставить под БД отдельный сервер). В принципе проблема не решена, а скорее отложена. Теперь у Вас есть время «окопаться» и подготовиться ко второй волне наплыва нагрузки, она будет больше и накроет сильнее.

     
  • Вариант решения 3: Ваш вариант, о котором я, вероятно, узнаю из комментариев.

     

Позвольте предложить и мне проверенное и относительно простое решение на базе одной из старейших технологий в Web-разработке.




Как это должно работать



Cайт всегда можно разбить на некоторое число независимых блоков, генерацией которых может заниматься (при необходимости) разные сервера.



При этом сборкой блоков в единое целое занимается некий «сборщик» и если любой из блоков по какой-то причине не создан за отведенное ему время, то это еще не повод выдавать клиенту «Gateway timeout» или «Internal Server Error». Можно собрать успешно созданные блоки, а на месте «сбойных» показывать устаревший контент из кеша.



Для реализации такой модели нам понадобиться технология-ветеран Web-разработки: ssi. В качестве «сборщика», как ясно из названия статьи, выступает nginx. «Чудеса» станут возможны благодаря модулю fastcgi_cache.



Итак, поехали:




Исключаем лишнее звено



Нам не пригодиться apache, наличие которого, как правило объясняется использованием RewriteRules. В nginx есть аналог mod_rewrite или комбинация location/alias с регулярными выражениями, возможности которых позволяют написать аналог любому RewriteRule от apache. Кроме того в современных фреймворках разбором входного URL может заниматься сам движек (например Zend_Controller_Router_Rewrite в Zend Framework)



В качестве fastcgi-бекенда может использоваться любая платформа. Примеры будут на php, но это не означает что нельзя написать аналогичный код на python-е или perl-е.



Запускаем php в режиме fastcgi:

# /bin/su -m www_user -c "PHP_FCGI_CHILDREN=8 /usr/bin/php-cgi -q -b 127.0.0.1:7777 &"



Можно еще прописать путь к лог-файлу в php.ini (error_log = /var/log/fastcgi/fastcgi.log), но при этом придется перезагружать php-cgi.



Делаем:

# killall php-cgi

и запускаем все по-новой



Более продвинутый вариант запуска fastcgi — установка php-fpm.




Устанавливаем nginx



Можно ставить стандартный из репозитория/портов… Но если хотите чтоб работала возможность «почистить» любой файл в кеше, придется компилировать.



Нам понадобиться модуль: ngx_cache_purge



Я подробно опишу, как это можно сделать для redhat-подобной системы, а вы уж по аналогии компилируйте под вашу систему.



# cd ~/rpmbuild/SRPMS
# yumdownloader --source nginx
# rpm -ivh nginx-0.7.65-1.fc12.src.rpm


редактируем файл nginx.spec, где-нибудь в список ./configure вставляем строчку--add-module=/root/rpmbuild/BUILD/ngx_cache_purge-1.0 \. Тут же можно удалить строчки с ненужными модулями (например --with-ipv6 \, --with-http_dav_module \, --with-mail \, --with-mail_ssl_module \ ...)



теперь распаковываем содержимое http://labs.frickle.com/files/ngx_cache_purge-1.0.tar.gz в папку /root/rpmbuild/BUILD/ngx_cache_purge-1.0.

Все можно компилировать:



# cd ~/rpmbuild/SRPMS
# rpmbuild -ba nginx.spec


Это не совсем красивый способ, т.к. полученный в результате .src.rpm не будет содержать файла с модулем ngx_cache_purge. Если для вас, все же, это критично, то здесь можете загрузить «правильный» вариант nginx .src.rpm для ветки 8.xx. Правда я часть ненужных мне модулей закомментировал.



Устанавливаем пересобранный nginx на наш сервер:



# rpm -ivh nginx-0.7.65-1.fc12.x86_64.rpm




Настройка nginx для проекта на php



В файл /etc/hosts (добавляем):



# Virtual hosts 
127.0.0.1 myproject


В основном конфиге /etc/nginx/nginx.conf в секцию http добавляем:



fastcgi_cache_path /var/spool/nginx/cache levels=1:2 keys_zone=mycache:64m;
include /etc/nginx/conf.d/*.conf;


(Не забудьте создать папку /var/spool/nginx/cache и установить для нее пользователя, под которым запускается nginx)



В папке /etc/nginx/conf.d/ создаем конфиги для виртуальных хостов



Пример кофига (/etc/nginx/conf.d/myproject.conf):



server {
        listen       80;
        server_name  myproject;

        root   /var/www/myproject/public;
        ssi on;

        # Включаем кеш если есть такая необходимость
        fastcgi_cache mycache;
        fastcgi_cache_min_uses 1;
        # Время кеширования равно нулю. кеш включен но кеширования нет
        # Время кеширования для конкретных страниц указиваем в заголовке "Cache-Control"
        fastcgi_cache_valid 200 0m;
        fastcgi_cache_valid 404 1m;
        fastcgi_cache_valid 500 0m;
        fastcgi_cache_use_stale updating error timeout invalid_header http_500; # Используем вариант из кеша (даже если он устарел) в случае ошибки
        fastcgi_cache_key $uri$is_args$args;

        # Раскоментируйте эту секцию если nginx собран с модулем ngx_cache_purge
#         location ~ ^/purge(/.*) {
#               fastcgi_cache_purge   mycache   $1$is_args$args;
#         }

        location ~ /(img|css|js|assets) {
#               access_log  off;
                access_log  /var/log/nginx/myproject_img_access.log  main;
                expires 1h;
        }

        location / {
                access_log  /var/log/nginx/myproject_main_access.log  main;
                error_log  /var/log/nginx/myproject_error.log;

                fastcgi_pass 127.0.0.1:7777;
                fastcgi_index    index.php;

                include         fastcgi.conf;
        }
}


Устанавливаем тестовый проект на php в /var/www/myproject. Исходный код примера можно посмотреть и скачать здесь.



Запускаем nginx. Для RedHat-подобных систем это выглядит приблизительно так:



# service nginx start


Все, система готова к работе! Пробуем запустить http://myproject/




Учим backend управлять временем кеширования



Дело в том, что в nginx время кеширования указывается в параметре fastcgi_cache_valid 200 0m; и распространяется на все страницы, в которых заголовком оно не переопределено.



В конфиге «по умолчанию» время кеширования я указал равным 0, т.е. кеширование отключено. Но если бекенд сгенерирует заголовок приблизительно такого вида:



Cache-Control: public, max-age=20
либо
Expires: Thu, 18 Mar 2010 20:57:07 GMT


То страница nginx-ом будет закеширована на 20 секунд. В php заголовок можно поменять с помощью функции header() (Со слов автора nginx самым приоритетным является «X-Accel-Cache-Control», потом «Cache-Control», потом «Expires»).



Напишем небольшую функцию. котрая будет управлять временем кеширования:

function cacheHeaders($lifetime=0) {
#        $date = gmdate("D, d M Y H:i:s", time() + $lifetime);
#        header('Expires: ' . $date . ' GMT');
        header('Cache-Control: public, max-age=' . $lifetime);
}





Мастерим блоки



Блоком будем называть любую логически выделенную часть html-кода без стандартных заголовков html-старницы, например:



<div>
    Это простой блок
</div>


Чтоб визуально контролировать состояние свежести каждого из блоков добавим код, наших тестовых блоков вывод времени.



<?php echo date('G:i:s')?>


Смотрим рабочий пример с использованием SSI блоков.




Удаляем страницы из кеша



К сожалению у nginx-а пока что нету родного (штатного) способа удаления страничек из кеша. Иногда это может создавать неудобства.



Если вы добавили при компиляции модуль ngx_cache_purge, то в конфиг (/etc/nginx/conf.d/myproject.conf) добавим приблизительно такую секцию, перед секцией «location / {...» :



location ~ ^/purge(/.*) {
        #allow     127.0.0.1;
        #allow     10.1.1.0/24;
        #deny     all;
        fastcgi_cache_purge   mycache   $1$is_args$args;
}


Для того чтоб удалить закешированную страницу: http://myproject/mypage.php?lang=ru, мне достаточно загрузить страницу http://myproject/purge/mypage.php?lang=ru



В php это можно сделать командой file_get_contents(«http://myproject/purge/mypage.php?lang=ru»);



С помощью директив allow и deny можно ограничить круг хостов с которых можно «чистить» кеш.




Тестируем



Напоминаю, ссылка для тестов http://linux.ria.ua/SsiBlocks/src/bin/index.php.



Обратите внимание, «каркас» страницы обновляется раз в 10 секунд, остальные блоки обновляются согласно примечаниям под временем создания блока.



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



Кроме того, помните, что вы не одни сейчас проводите эксперименты с этой страничкой, если хотите поэкспериментировать — самостоятельно настройте локальную копию примера.




Делаем выводы



Даже если такой подход покажется Вам примитивным, и функциональность его сильно ограниченной, обратите внимание на то, что это работает не просто быстро, а очень быстро!



Узким местом может быть только дисковая система, если кеш «распухнет» до больших размеров и не будет помещаться в дисковый кеш.



PS: Если эта статья будет интересна читателям, я планирую написать вторую часть о применении описанного подхода к кешированию блоков на Zend Framework.

0
517
0