Nginx

индекс
267,67

Кешируем блоки HTML при помощи nginx

Не секрет, что пользователи любят, когда контент на сайте обновляется чаще, чем раз в год. Эту любовь пользователей к динамическим страничкам разделяют и поисковики. Google, например, умеет определять наличие обновляющихся блоков на страничке и добавляет ей немного кармы (читай, PR).

Однако динамический контент довольно плохо сочетается с большими нагрузками. Для веб-сервера, отдача статической странички — намного более простая задача, чем запуск кода, который сгенерит эту страничку динамически. В некоторых случаях может выручить прегенерация всех возможных вариантов странички, но это не спасёт, если их слишком много, или страница обновляется слишком часто.



Простой пример — предположим у вас был замечательный статический HTML, который отдавался nginx-ом со скоростью 2000 раз в секунду (это достижимо без особых проблем). И тут добрый дизайнер нарисовал новый вариант странички, где для залогиненных пользователей присутствует мааааленький блок, содержащий логин, имя-отчество, и, например, аватарку.

Всё, приехали. Статикой тут уже не обойдёшься, а это означает запуск PHP (Perl/Python) на каждый запрос, проверка сессии, ползание по файловой системе (или, ещё хуже, БД) для того, чтобы найти по SID логин и другую инфу о пользователе. Производительность проседает в десять раз. Или нет. :)

Проблему эту можно решить довольно легко, если использовать две полезные фичи, которые предоставляет веб-сервер nginx.

Фича номер раз, которая в нём есть с незапамятных времён — это SSI. Для того, чтобы её использовать, делаем небольшой скрипт, который умеет принимать из куки идентификатор сессии, и отдавать только тот самый небольшой блок с информацией о пользователе. То есть, на запрос вида GET /get_user_info.php отдаёт фрагмент HTML, вида <div class=«login»>Здравствуйте, Василий Пупкин</div>.

Соответственно, в самой страничке пишем SSI-include: <!--# include virtual="/get_user_info/" -->.

Для того, чтобы эта конструкция заработала, в конфиг-файле nginx надо описать соответствующий location:

location /get_user_info/ {
proxy_pass 127.0.0.1:12345/get_user_info.php;
proxy_pass_header Cookie; # для передачи sid в Cookie
}


Однако, как вы, наверное, уже заметили, подобная конструкция не решает проблемы с производительностью кода, а лишь позволяет вынести динамическую часть в отдельный скрипт. Окей, как раз для этого нужна фича номер два — кеширование прокси-запросов. В production-ветке она появилась сравнительно недавно, но уже позволяет делать очень многое. :)

Итак, для начала, скажем nginx, где мы хотим хранить наш кеш.
proxy_cache_path /var/nginx/cache
levels=1:2 keys_zone=my_cache:64m max_size=1024m
inactive=1d;


Эта строчка означает, что мы создаём новую зону для кеша, с названием my_cache, файлы кеша будут лежать в /var/nginx/cache, занимая не более чем 1 гигабайт, и храниться не более чем 1 день.

Теперь скажем nginx, что мы хотим кешировать запросы к нашей локации.

location /get_user_info/ {
proxy_pass 127.0.0.1:12345/get_user_info.php;
proxy_pass_header Cookie; # для передачи sid в Cookie

proxy_cache my_cache;
proxy_cache_valid 200 3h; # раз в три часа будем позволять себе обновлять кеш :)
proxy_cache_valid any 0; # не кешируем 500 и 400 ошибки
proxy_cache_use_stale updating error timeout invalid_header http_500 http_502 http_503 http_504 http_404; # если ваш скрипт отдал одну из описанных ошибок, использовать вариант из кеша, если он есть, даже если он уже заэкспайрился
proxy_cache_key "$scheme$proxy_host$uri$is_args$args$cookie_sid";
}


Обратите внимание на последнюю строчку. Именно она позволит нам отдавать каждому отдельному пользователю правильный вариант закешированного блока. Точнее, важен её последний параметр — переменная $cookie_sid, значение которой всегда будет равно значению куки с именем «sid», которую вам должен отдать пользователь.

Всё. Теперь nginx сам будет, по мере необходимости, обращаться к вашему скрипту, и кешировать результат его работы. При этом, производительность почти не просядет, и нагрузка на сервер вырастет не очень значительно.

Всех, кого интересуют подробности, отправляю на сайт автора nginx за документацией: sysoev.ru/nginx/docs/.

UPD: Разобрался с настройками, перенёс в блог nginx.
+79
30 июля 2009, 21:50
154

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

+5
AxL1232 #
Теперь точно поставлю nginx попробовать :)
+8
Rebus #
Я описал только пару фич nginx, а на самом деле — тысячи их. :) Почему-то все думают, что основная ценность этого сервера в том, что он очень быстрый. А это по моему мнению не так. Самое важное — что он очень-очень гибкий.
–1
nekt #
Апач какбэ тоже очень гибкий. Но нгинкс быстрее :)
0
SMiX #
В нжинксе к тому же гибкость достигается куда меньшими трудозатратами. Всё очень лоогичноЮ удобно и превосходно документировано. В сравнении с апачем он чудо. Мне там не хватает лишь пользовательской настройки а-ля .htaccess, но всё равно это неудачное решение, по крайней мере в таком виде, как это реализовано в апаче.
0
pav #
Поясните пожалуйста про неудачное решение пользовательской настройки через .htaccess в апаче.
0
SMiX #
Может быть, я не иправ, но постоянно обращаться к диску за проверкой наличия .htaccess в директориях — не дело. Конечно, это вопрос правильной настройки, но на большинстве серверов он включен везде
0
questor #
Задать диапазон ip-адресов (например, стрим-москва) уже можно?
0
el777 #
Апач гибкий вообще. А нгинкс гибкий как раз там где нужно для высоких нагрузок.
+4
LMik #
nginx это пораждение BSD-шного склада ума :)

Он до ужаса логичен, прост и быстр. Надо было начинать прбовать уже давно ;)
+1
AxL1232 #
Лень природная мешает :) Но теперь я буду с ней бороться :)
0
mstarrr #
Из за лени и я долго не ставил — но пришлось поставить из-за того, что апач начал падать.

Очень доволен, хотя конечно трохи с конфигами для ссл и виртуал хостов пришлось поковыряться. Работает стабильно, вопросов по памяти и процеccору никаких, изредка в топе появляется процесс.
–2
SowingSadness #
Рискую нарваться на кучи минусов, но почему все в Рунете так сходят с ума по nginx'у(да и по sphinx'y)?

Посомтрите на список Tiny Web Servers:
en.wikipedia.org/wiki/Comparison_of_lightweight_web_servers
+1
AxL1232 #
Ну лично мне мой надежный информатор советовал именно nginx. Потому это будет мой выбор :)
+3
Rebus #
А вы попробуйте выкинуть из этого списка:

а) Всё что closed source (цена и возможность посмотреть как оно работает);
б) Всё, что не работает под Linux и FreeBSD (самые популярные платформы хостинга);
в) Всё, что написано на C#, Ruby, PHP и Python (производительность);
г) Всё что не поддерживается и не обновляется больше года (к кому идти, если нашёл баг?);
д) Всё, что не использовалось в реальных больших и нагруженных проектах (мало ли, что автор обещает);

И что у вас останется? Выбор из, от силы, 3-4 вариантов, среди которых nginx — один из лучших.
0
SowingSadness #
Close Source — не обязательно платное, как и Open Source — не обязательно бесплатное!
А зачем смотреть исходники? Должно стабильно работать и выполнять поставленные задачи.

lighttpd, чем не вариант?
Да и возможностей поболее, например давно уже работает с FastCGI
+2
SMiX #
nginx тоже давно fastcgi умеет
0
fStrange #
хорошая статья, а у меня как раз есть проект где можно оное отлично применить :)
+24
Rebus #
Спасибо. Если народ заинтересуется, я постараюсь продолжить рассказывать про возможности Nginx. Так получилось, что мне приходится использовать этот веб-сервер на довольно нагруженном проекте, так что материалов набралось немало. :)
+9
ruskar #
Народ интересуется.
0
collapse #
Очень бы хотелось. Сам вот недавно поставил пока тока на тестовом. Собираюсь на продакшн кидать. Тема очень интересная.
+1
Rebus #
Поскольку про обычную настройку и тюнинг сервера, в интернете, да и на хабре, материалов довольно много, я хочу описывать те фичи, которые используются реже. Возможно, просто потому, что ими мало кто интересовался. :)
0
collapse #
Все верно. nginx был первой программой которую я скомпилировал ( в смысле удаленно и на CentOS). Однако про конфигурирование я бы почитал побольше. Материал сильно разрознен.
0
grossu #
nginx рельсы вообще круто разгоняет. Прямо чувствуется.
+1
t0os #
да-да, такие материалы нужны!
+1
popotam2 #
Да-да, народ интересуется. Ждем-с
0
Rulez #
да просьба написать материалов на практическое использование и как можно более подробных
0
chetzof #
Добавьте в блог nginx для порядка. За статью спасибо, продолжайте в том-же духе:)
+4
Rebus #
Я бы с удовольствием, но пока, к сожалению, могу писать только в свой личный блог. Я тут всего два дня. :)
0
chetzof #
Странное ограничение по дате регистрации, ну да ладно… :)
+1
Rebus #
Мне кажется, ограничение связано с кармой, хабрасилой, или каким-то ещё параметром, в которых я пока не очень разобрался. Как только разберусь, перенесу запись. :)
+3
expolit #
А если вы работаете на FastCGI то
fastcgi_cache merge;
fastcgi_cache_valid 240h;
fastcgi_cache_key "$hash";
+3
benone #
А если пользователь изменил имя в профиле? Ему надо куки рефрешить?
+2
Rebus #
Спасибо, что заметили. Это представляет собой некоторую проблему. Действительно, в этом случае надо, либо изменить идентификатор сессии в куке (самый простой и логичный вариант), либо какой-то ещё параметр, входящий в строку proxy_cache_key (например args).
0
alexeyshockov #
Либо руками удалять файл кеша для данной страницы? Или это не самый лучший (простой) вариант?
+1
DarkestMaster #
Вариант не самый лучший, потому как кешем nginx заведует, причём под кеш отдельный процесс выделен — cache manager. Полагаю, что он может обидеться, если в его кеш кто-то со стороны полезет :)
+2
Rebus #
Руками удалять файл из кеша nginx — это не наш метод. :) Тем более, что пути к закешированным файлам могут измениться, причём даже не теоретически. Например, вы решите, что 2-х уровней каталогов для вашего кеша мало, и решите добавить третий.

Всё-таки, в данном случае проще всего просто поменять идентификатор сессии у пользователя в куке.
+1
alexeyshockov #
Согласен :)

Просто раньше не задавался вопросом смены SID'а в PHP-приложениях, а теперь нашёл простое решение.
0
alexeyshockov #
Да, меня этот вопрос тоже очень интересует.

Если пользователь вышел, поменял имя, ещё что-нибудь сделал? Это как-то можно учесть при кешировании? Или придётся самому сбрасывать кеш?
+1
Rebus #
Если пользователь вышел, то вы, вероятно, очистили ему куку с идентификатором сессии, а значит, более никаких специальных действий предпринимать не надо. Если пользователь поменял имя (или любую другую информацию, которая показывается в закешированном блоке), то вы должны ему поменять идентификатор сессии в куке (ну, и предпринять другие соответствующие действия на сервере), иначе, некоторое время (до 3х часов) ему может показываться блок со старыми данными.
0
mihailt #
полезно конечно, спасибо, но на мой взгляд подходит только контент-сайтам.
+1
standov #
все хорошо только я не понял каким боком первое относится ко второму… мне кажется что без первого (ну т.е. всю страницу отдаем а не кусок) при применении второго нииче не поменяется, я не думаю что чуть возросший объем кешируемых данных сильно скажется на производительности, а галаздо проще и надежнее, не?
+2
Rebus #
Да, можно кешировать страничку целиком, а не только один блок. В данном примере, действительно нет большой разницы, кешировать всю страничку, или только отдельный блок.

Но дело в том, что динамических блоков может быть несколько, с большим количеством вариантов содержимого. И тут уже становится выгоднее кешировать именно блоки по-отдельности. :)
–1
mitrych #
Google, например, умеет определять наличие обновляющихся блоков на страничке и добавляет ей немного кармы (читай, PR).
И откуда же информация такая взялась?
+1
Rebus #
Я не специалист по SEO, но мне казалось, что этот факт общеизвестен. Можно посмотреть, например, тут: webest.info/seo/google/google-pr-ratings.php (пункты 27,28,30).
–1
mitrych #
А я успешно занимался SEO с 1997 года по 2007. Поэтому к таким статьям настроен скептически, как и к высказыванию, которое процитировал.
Смотрим вниметельно — во-первых, в статье по этой ссылке написано:
«38 факторов, которые, предположительно, позитивно влияют на позицию при ранжировании Google». (видите слово «предположительно»?).

Во вторых, эти 38 чудо-факторов, которые предположительно влияют, выделены полужирным, а ни один из пунктов 27, 28, 30 не выделен.

Прошу прощения за оффтопик, но глаз режет, когда между делом упоминается какая-нибудь ерунда.
+1
aib #
А можно ли например такой блок хранить во внешнем кеше типа memcached? Например генерить набор блоков и кидать их в кеш, а nginx будет их собирать и вставлять в шаблон.
0
aracost #
можно memcached на бэкэнд поставить, и из него проверять наличие блоков в кэше =)
0
Rebus #
В принципе, возможно, хотя лично я такую конфигурацию не пробовал. Возможно, больше про неё сможет рассказать мой друг и хабрачеловек bambr. :)
+2
chetzof #
Неожиданная ссылка я бы сказал :)
0
Rebus #
Ну, просто, насколько я знаю, он как-раз делал работающую конфигурацию, когда бэкенд сам кладёт прегенерённые блоки в мемкеш, а их оттуда уже забирает nginx, которому, соответственно, на бэкенд ходить практически не надо, разве что только по большим праздникам. :)
0
seriyPS #
Не, просто вы сами попробуйте на эту ссылку нажать))
А про такую конфигурацию было-бы очень интересно почитать, ибо сам хочу применить нечто подобное…
0
Rebus #
Кхм… ой… одна маленькая опечатка, а такой результат. Жаль, что нельзя редактировать комментарии.
0
seriyPS #
Я пытался у юзера tigrenokразузнать про такой способ работы нгинкса с мемкешем (он писал тут habrahabr.ru/blogs/startup/60915/ ) но что-то молчит)))
+1
aracost #
интересно, что быстрее работать будет — кэширование средствами nginx или через memcached?
0
khekkly #
Тестирование, главный аргумент!
+1
res #
Все просто.

Мемкеш гоняет данные по сети (loopback тоже сеть и тоже ест CPU ПЭВМ), а значит есть накладные расходы.
Nginx отдает статику из локальной fs. А часто отдаваемые данные из fs вообще попадают в active область памяти и отдаются так быстро, что слышно свист.

Истина посередине: кешировать в бекенде (php, perl, python) тяжело доставаемые данные мемкешем, например, на «минуты», а уже фронтендом (nginx) кешировать всю выдачу бекенда на «секунды».

Это решает вопрос смены имени юзера:
— инвалидация кеша мемкеша проще реализуется на уровне бизнес-логики (php,perl,python)
— бекенд (php, perl, python) делает трудоемкие вычисления для этого юзера всего раз в «минуты» (но при смене имени кеш дропается мгновенно)
— фронтенд (nginx) сделает из 100 запросов в «секунды» всего один-единственный инвалидирующий запрос к бекенду

Я, конечно, не разбираюсь в математике и компюторах, но кажется последние два пункта говорят о снижении нагрузки на два порядка.
0
standov #
классический вариант на нагруженном ресурсе именно для это задачи — главная страница чистая статика, а хеадер подтягивается аджаксом — я понимаю что из другой оперы но тоже вариант решения озвученной задачи. Модифицированный варинт но менее надежный — запихивать в куку «имя (логин)» я прямо в статике скриптом выгребать
0
SMiX #
В случае с аяксом увеличивается количество соединений клиент-сервер
0
standov #
я не говорю что мой метод лучше указанного, я так просто, в тему как один из вариантов и более простой в реализации
0
DjOnline #
Зато можно не каждый раз клиенту отдавать всю страницу, а возвращать 304, не тратя трафик, и догружать динамические компоненты на js.
Разве не именно этот вариант используется на ya.ru?

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