Кешируем блоки 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.
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

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

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

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

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

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

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

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

                                            Всё-таки, в данном случае проще всего просто поменять идентификатор сессии у пользователя в куке.
                                      • 0
                                        Да, меня этот вопрос тоже очень интересует.

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

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

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

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

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

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

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

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

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