Высокая производительность

индекс
188,45

nginx + apache. Кеширование

Привет, %username%
Тут я хочу рассказать о том, как я настраивал кеширование на одном сервере, точнее VDS. Характеристики сервера: 2000MHz, 2GB RAM, 80Gb HDD, технология виртуализации — OpenVZ.
Было решено использовать Nginx версии 0.7.64. На сервере находилось около 200 сайтов. И несколько высоко нагруженных проектов. Вот эти самые проекты и давали ощутимые тормоза и нагрузку на сервер. Мы будем рассматривать DLE в этом примере.
Итак, основные моменты кеширования:
  • Кешировать «гостей» сайта
  • Залогиненных посетителей отправлять напрямую к апачу


0. Придумываем «технологию»


Если в урлах встречаются строки: index.php?action=logout и admin.php то мы будем посылать эти запросы напрямую к апачу. Потом, нам нужно научить nginx отделять залогиненых пользователей от гостей. Разделять мы их будем с помощью кукисов. В моем случае, Кукисы dle_user_id и dle_password означают, что пользователь залогинен и мы не хотим отдавать ему страничку 5-ти минутной давности. И плюс, нам нужно отправлять POST заброс на авторизацию вне кеша, я думаю понятно почему и зачем (:.

1. Поехали. Пишем конфиг


Собственно, я не буду описывать полный конфиг, я буду описывать только те моменты, которые необходимы для кеширования. Я не претендую на правильность и оптимальность моего решения, но то что здесь показано, работает, и неплохо работает в боевых условиях. Сильно не пинайте, я не так давно полез в дебри nginx'a, но чувствуется что документации и тех пример очень не хватает!
Основные директивы конфига — кликабельны, ведут на оф документацию.

1.1 Сам конфиг



http {
proxy_cache_path /var/cache/nginx/cache levels=1:2 keys_zone=one:16m inactive=7d max_size=1024m;
proxy_temp_path /var/cache/nginx/temp; #эта директива будет наследоваться из http секции, если не задано другое.

server {
listen 127.0.0.1:80;
server_name example.com www.example.com;
proxy_temp_path /var/cache/nginx/example.com;

location @nocached {
proxy_pass 127.0.0.1:8080;
proxy_redirect example.com:8080/ /;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
}

location / {
proxy_pass 127.0.0.1:8080;
proxy_redirect example.com:8080/ /;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
#здесь мы фильтруем наших залогиненых пользователей
if ($cookie_dle_user_id) { return 412; }
if ($cookie_dle_password) { return 412; }
if ($request_method = POST ) {
return 412;
}
error_page 412 = @nocached;
proxy_cache one;
proxy_cache_key "$request_method|$is_args|$host|$request_uri";
proxy_hide_header "Set-Cookie";
proxy_ignore_headers "Cache-Control" "Expires";
proxy_cache_valid 200 302 304 5m;
proxy_cache_valid 301 1h;
proxy_cache_valid 503 4s;
proxy_cache_valid any 1m;
proxy_cache_use_stale http_502 http_503 http_504;
}

location ~ (admin.php|index.php?action=logout) {
proxy_pass 127.0.0.1:8080;
proxy_redirect example.com:8080/ /;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
}

location ~* ^.+\.(jpg|jpeg|gif|png|svg|htm|js|css|mp3|ogg|mpe?g|avi|zip|gz|bz2?|rar)$ {
root /var/www/example/data/www/example.com;
expires 1y;
access_log /var/www/httpd-logs/example.com.access.log;
error_page 404 = @fallback;
}
location @fallback {
proxy_pass 127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
}
}
}


$cookie_name, эта переменная равная cookie name;

Собственно, далее будем разбирать, что же мы здесь намулевали. Предложения по улучшениям и правкам приветствуются!

2. Разбор полетов



Итак, нам необходимо было разграничить пользователей на 2 типа, и в зависимости от типа, давать или не давать ему кеш.
В location @nocached мы будем пускать тех, кто является владельцем двух кук — dle_user_id, dle_password, следовательно эти пользователи будут у нас ходить напрямую, не кешируясь. Я уже писал, что для авторизации используются POST запросы, как и для регистрации ;), следовательно нам не нужно их кешировать. В этом случае, мы просто добавляем проверку на $request_method.
error_page 412 = @nocached собственно и посылает обладателей кукисов, или кто пытается залогинится, напрямую к апачу.
Хотелось бы отметить директиву proxy_cache_key. В ней, для ключа, я указываю лишь «уникальность» страницы, тоесть, если ктонибудь обратился к любой нашей странице «Гостем», то nginx, проксируя первый ответ, сразу кладет его в кеш. После этого, любой другой пользователь, который уложился в отведенное время жизни страницы (proxy_cache_valid 200… 5m) получает ту самую страницу, которую сгенерировал апач для предыдущего пользователя, так будет продолжаться, пока не кончится срок жизни, в нашем случае для ответа 200 выделяется — 5 минут. После этого все будет продолжаться по тому же самому сценарию.
Дальше, я хотел бы отметить 2 строчки: proxy_hide_header, proxy_ignore_headers которые обязательны при кешировании. Вы ведь не хотите чтобы Вася Пупкин смог получить кукисы Пети Васечкиного, и тем самым делать от его имени… ??? Вторая строчка, proxy_ignore_headers Позволяет нам сохранять ответы апача, которые помечены, как не кешировать или еше чемнибудь в этом роде. Ведь даже для таких вещей нам придется дергать неповоротливый апач, когда можно один раз закешировать, и отдавать быстрым nginx'ом.
Придавать особого внимания параметру proxy_cache_valid я не буду, все и так хорошо описано в документации.
proxy_cache_use_stale очень правильная директива, она позволяет нам отдать клиенту закешированную «нормальную» страницу в то время, когда апач или повесился, или сдох или еще чего… 502, и 504 ошибки соответственно.
В следущем location ~ (admin.php|index.php?action=logout) мы говорим что хотим отправлять запросы в которых содержаться строки напрямую к апачу.
Два следущих location говорят nginx'у что статику (картинки таблицы стилей видюшечки и т.д.) отдавать самому. И если nginx не нашел такого файла на диске, то может быть он просто находится в .htaccess и апач нам его отдаст.

Ну вот, надеюсь что все. Я очень надеюсь, что мой хабра топик вам помог, или поможет в будущем избежать некоторых неприятных моментов.
Спасибо за внимание.

P.S. Парсер съел мои http://, а nginx'у они нужны, как мне их восстановить? Иначе при копировании моего куска конфига у вас nginx может заругаться, а если нет, то уже не известно что будет.
Кросспост
+53
2 января 2010, 19:44
178

комментарии (33)

+20
Bkmz #
У всех видимо новый год, а я тут такими делами занимаюсь… ух...)
0
Orenlab #
Самое время — пока нагрузки минимальны :). А если по делу, то DLE наконец то научили работать без тяжеловесного и неповоротливого Apache ;)
+2
borisko #
А он когда-то не умел? о_О
0
Bkmz #
Это точно. Но когда на сервере куча сайтов и гребаные панели, то ты тут уж не причем, чо заказчик сазал, то ты и делаешь.
+3
P_r_i_m_a_t #
Если в урлах встречаются строки: index.php?action=logout и admin.php то мы будем посылать эти запросы напрямую к апачу.

Зачем эти условия? Просто все POST-запросы не кешировать.
–4
Bkmz #
Ну к примеру, логаут так не пашет. Ему нужно, что action=logout было напрямую и пост запроса там нет :(
Ну и пусть будет на всякий случай :) хуже от этого не будет! :)
+3
P_r_i_m_a_t #
Так при логауте же ещё кука будет.
0
borisko #
if ($request_method = POST ) — не нужно, пост не кешируется автоматически.
Не нужно использовать старый nginx, лучше использовать новый nginx. Он лучше, в нем меньше багов и больше функционала.
Конфиги заключайте в
< code >, меня это спасало.
<зануда> В конфиге куча повторяющихся мест. proxy_add_header лучше вынести на уровень server или вынести вместе с proxy_pass в отдельный файл, подключаемый через include. Получается намного удобнее, честно :) </зануда>
–1
borisko #
Парсер — лох, съел < рrе > как тег :(
Правильно — так:

if ($request_method = POST ) — не нужно, пост не кешируется автоматически.
Не нужно использовать старый nginx, лучше использовать новый nginx. Он лучше, в нем меньше багов и больше функционала.
Конфиги заключайте в <рrе> < соdе >, меня это спасало.

<зануда> В конфиге куча повторяющихся мест. proxy_add_header лучше вынести на уровень server или вынести вместе с proxy_pass в отдельный файл, подключаемый через include. Получается намного удобнее, честно :) </зануда>
0
Bkmz #
Попводу Post не согласен, если бы не кеширвоалось то я бы не стал добавлять эту проверку, ибо почти все писал опытным путем.
+2
borisko #
Это баг, от использования старого nginx :-P

sysoev.ru/nginx/changes.html — 0.7.48,
Исправление: теперь nginx кэширует только ответы на запросы GET.

К слову, в дальнейших версиях очень многое по кешированию было исправлено.
0
borisko #
* например, 0.7.52
Исправление: корректная обработка метода HEAD при кэшировании.
+1
Bkmz #
у всех прошу прошения, исползьовалась версия 0.7.64, всего в одном символе ошибся
+2
borisko #
Тогда это баг :(
Когда вернусь с отдыха, попробую воспроизвести на последней версии.
+2
Bkmz #
Не стоит надеятся тому, что обещают, лучше самому все сделать! из нать что все работает!
0
Bkmz #
На сервер используется веб панель для работы с клиентами, она почти весь конфиг генерит. Вот и приходится…
0
Bkmz #
Попробовал использовать
 , не работает, он переносить на новую строку данные. 
0
borisko #
В смысле? Пишите так:
<рrе>
server 
{
    location /
    {
        proxy_pass http://ya.ru/;
    }
}
</рrе>
0
Bkmz #
Хотелось бы сохранить ссылки. Видимо придется обойтись без ссылок :(
+1
ColorPrint #
я предпочитаю вообще апач не ставить на серверы…
nginx прекрасно и с php и с cgi скриптами справляется.
единственное неудобство для массового хостинга — несовместимость формата описания реврайтов и необходимость прописывать их напрямую в конфиг
+3
alfa #
а в чём смысл
if ($cookie_dle_user_id) { return 412; }
if ($cookie_dle_password) { return 412; }

?
достаточно dle_user_id смотреть, если кто-то куки выборочно удалял, то он сам себе злобный буратино
0
akirsanov #
блог «как я настроил nginx на своём сервере».
0
Bkmz #
Я все больше и больше утверждаюсь, что документации мало по nginx'y необходимо больше рабочих примеров его использования. Вот и поделился…
+4
AstonMartin #
А в чем фишка хостить 200+ сайтов на таком железе? Они что все абсолютно не коммерческие?
+2
kpblca #
У вас конфиг неправильный — http секция рано закрывается. Так работать не будет.
sysoev.ru/nginx/docs/http/ngx_http_core_module.html#server
Директива server должна быть внутри http.
Дальше не читал.
0
Bkmz #
Спасибо за замечение, исправил!
+1
podarokua #
интересен расчет кеша в 5 минут…
тоисть зависимость длины кеша от количества сайтов.
получается если в 5 минут больше 300 запросов по всем сайтам — тогда кеш еффективен, если меньше — тогда он избыточен… (натянутый расчет, но в принципе правильный, если учесть лавинообразность обращений)

Рассматривался ли вариант разделения кеширования по статике и динамике?
Большинство проблем ведь не изза скриптов, а изза наличия на странице сотен елементов ака картинки — каждая из которых — дополнительный запрос к серверу.

Почему спрашиваю? да потому, что множетсво кеширования делает сам dle — минуя запуск длинных скриптов — он отдает статические обьекты-страницы.

Случаются ли наложения кеширования CMS на кеширование на уровне nginx? если dle — 5 минут + nginx — 5 минут — ето уже 10, а 10 минут иногда могут тем же анонимусам причинить неудобство в виде пропажи комментария либо наличия на сайте старого контента…
+1
podarokua #
упс… увидел в конфиге «статику»
0
Bkmz #
Кеширование включено только на одном сайте, ибо только один сайт дает почти все нагрзку на сервер. Вот именно для него и делалось кеширование.
+1
pwlnw #
Там нет никаких обиженных анонимусов. На таких сайтах анонимусы — это поисковики и случайно забредшие по ошибке. Они просто закрывают браузер через 3 секунды.
0
podarokua #
ну в тексте ничего о «таких сайтах» не написано, соответственно предполагал все варианты…
0
slimlv #
Чёт не ясно зачем @fallback при наличии @nocached
0
Bkmz #
@fallback генерируется ISPManager :(
Вот и приходится оставлять куски чужих конфигов

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.