Nginx админка не работает при включенном кэшировании

Кэширование ответа от backend с помощью NGINX

NGINX позволяет значительно ускорить процесс загрузки страницы, не обращаясь к бэкэнду, а выдавая готовый html из кэша. Для работы данной функции необходимо, чтобы веб-сервер был версии 0.7.44 и выше (проверить можно командой nginx -v).

Для FastCGI кэш задается с помощью опций fastcgi_cache_*. Для запросов к бэкэнду с помощью proxy_pass — proxy_cache_*.

Рекомендации и противопоказания

Кэш — это данные не первой свежести. Это важно учитывать, если мы хотим настроить эффективное кэширование средствами nginx. Мы можем столкнуться с различными проблемами, например, неспособность сервера продлить сессию авторизованного пользователя или отображение старой информации на портале с динамично меняющимся контентом. Чтобы избежать данных проблем, настройка nginx должна быть тесно сопряжена с разработкой сайта. Идеальная работа кэша возможна только при полном понимании внутренних механизмах работы портала, такие как, отправка запросов на авторизацию, продление сессии, обновлении контента — программист может написать для этого запрос по определенным URL, для которого администратор NGINX может отключить кэширование.

Кэширование динамики будет полезно для сайтов с высокой посещаемостью, из-за которой создается высокая нагрузка на сервер и он не может успеть выполнить обработку запросов. Также оно будет полезно для отдельных страниц, содержимое которых меняется очень редко, но отработка скриптов выполняется долго.

Читайте также:  Битрикс не работает регистрация

Из всего этого можно сделать вывод, что кэширование на стороне сервера NGINX стоит применять только в случае высоких нагрузок и медленной работы сайта из-за долгой работы backend’а.

Настройка кэширования для proxy_pass

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

Включение кэширования

Открываем конфигурационный файл nginx:

В секцию http добавляем:

http <
.
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=all:64m inactive=2h max_size=2g;
.
>

* в данном примере мы задали глобальную настройку для кэширования:

  • /var/cache/nginx — путь хранения кэша.
  • levels — уровень вложенности каталогов. В данном примере мы задаем настройку, при которой в каталог с кэшем будет создан каталог, а в ней — еще один каталог.
  • keys_zone — имя зоны в разделяемой памяти, где будет храниться кэш, а также ее размер.
  • inactive — задает время, после которого кэш будет автоматически чиститься.
  • max_size — максимальный размер данных под кэш. Когда место заканчивается, nginx сам удаляет устаревшие данные.

Создаем каталог для хранения кэша и задаем владельца:

chown nginx:nginx /var/cache/nginx

Настройка хостов

Чтобы определенный сайт или отдельная страница кешировала запрос, открываем конфигурационный файл с настройками виртуального домена или хоста, например:

. и добавим к proxy_pass кэширование — мы получим что-то на подобие:

location / <
if ($http_cookie

* «.+» ) <
set $cookie_cache_bypass 1;
>
proxy_cache_bypass $cookie_cache_bypass;
proxy_pass http://localhost:3000;
.
proxy_cache all;
proxy_cache_valid 404 502 503 5m;
proxy_cache_valid any 1h;
proxy_cache_use_stale error timeout invalid_header updating;
>

  • set $cookie_cache_bypass 1 — задаем значения переменной $cookie_cache_bypass, если передаются куки. Необходимо для предотвращения отдачи устаревших данных.
  • proxy_cache_bypass — не отдавать данные из кэша. В нашем случае, применяется при куках.
  • proxy_pass — передает запросы на бэкэнд.
  • proxy_cache — включаем кэширование.
  • proxy_cache_valid — задает время кеширования. В нашем примере первый параметр задает кэширование страниц с кодами ответов 404, 502, 503 на 5 минут, второй — для всего остального на 1 час.
  • proxy_cache_use_stale — указывает, в каких случаях можно отдать устаревший кэш.

Применение настроек

NGINX настроен. Проверим корректность настроек:

Если ошибок нет, применяем их:

systemctl restart nginx

Теперь заходим на сайт и смотрим в каталог с кэшем — в нем должны появиться каталоги и файлы:

Мы должны увидеть что-то на подобие:

drwx——. 3 nginx nginx 4096 Jan 25 16:09 0
drwx——. 5 nginx nginx 4096 Jan 25 16:09 2
drwx——. 5 nginx nginx 4096 Jan 25 16:15 3
drwx——. 3 nginx nginx 4096 Jan 25 16:09 4
drwx——. 4 nginx nginx 4096 Jan 26 05:08 5
drwx——. 3 nginx nginx 4096 Jan 25 16:09 6
drwx——. 3 nginx nginx 4096 Jan 26 04:18 7
drwx——. 3 nginx nginx 4096 Jan 25 16:10 8
drwx——. 5 nginx nginx 4096 Jan 25 16:15 a
drwx——. 3 nginx nginx 4096 Jan 25 16:09 b
drwx——. 5 nginx nginx 4096 Jan 26 04:19 e
drwx——. 3 nginx nginx 4096 Jan 25 19:55 f

Настройка кэширования для fastcgi_pass

Настройка fastcgi_cache аналогична proxy_cache и настройки можно сделать по подобию последнего, заменив proxy_ на fastcgi_. Мы разберем основные настройки без подробностей и комментариев.

Открываем конфиг nginx:

Добавляем в секцию http:

http <
.
fastcgi_cache_path /var/cache/fastcgi levels=1:2 keys_zone=fastcgi:64m inactive=2h max_size=2g;
.
>

* обратите внимание, что мы задали другой каталог и keys_zone.

Создаем каталог для кэша:

chown nginx:nginx /var/cache/fastcgi

location / <
if ($http_cookie

* «.+» ) <
set $cookie_cache_bypass 1;
>
fastcgi_cache_bypass $cookie_cache_bypass;
fastcgi_pass http://localhost:9000;
.
fastcgi_cache all;
fastcgi_cache_valid 404 502 503 5m;
fastcgi_cache_valid any 1h;
fastcgi_cache_use_stale error timeout invalid_header updating;
>

Проверяем настройки и применяем их:

systemctl restart nginx

Кэширование статики

Кэширование статики не имеет никакого отношения к кэшированию ответа от бэкэнда, однако, это тоже позволяет разгрузить сервер. Суть в том, что nginx сам умеет отдавать статические файлы + задавать инструкцию для браузера по ее кэшированию.

Настройка выполняется для каждого виртуального хоста (секция server):

* ^.+\.(css|js)$ <
root /var/www/site
expires modified +1w;
>
location

* в данном примере мы задаем для файлов изображений (jpg, jpeg, gif, png) кэш до 31 декабря 2037 23:55:55 (max), для файлов css и js — 1 неделя с момента их модификации. Данные настройки мы задаем при помощи параметра expires, который, в свою очередь, задает заголовок cache-control. NGINX будет искать статические файлы в корневом каталоге /var/www/site.

После применяем настройки:

systemctl restart nginx

Отключение кэширования

Как говорилось выше, в некоторых случаях, кэширование может навредить и на некоторых страницах его стоит отключить. В настройках виртуального хоста, мы можем создать отдельный location, для которого отключиться кэш, например:

location /proxy_nocache <
proxy_pass http://localhost:3000;
.
proxy_cache off;
>

location /fastcgi_nocache <
fastcgi_pass http://localhost:9000;
.
fastcgi_cache off;
>

* обратите внимание, что в данном примере мы отключаем кэширование как для запросов proxy_pass, так и fastcgi_pass.

При отключении кэширования для статики используем следующую конфигурацию:

* ^.+\.(jpg|jpeg|gif|png|css|js)$ <
root /var/www/site
expires epoch;
>
>

* expires epoch задаст заголовок сache-control с временем окончания кэша «1 января 1970 00:00:01».

Сброс кэша

Удалить кэш на стороне сервера nginx мы можем только для бэкэнда. Для статики нужно удалять кэш в самом браузере (например, в настройках или с помощью дополнений).

Для сброса кэша мы просто должны удалить содержимое каталога, который прописан в опции proxy_cache_path или fastcgi_cache_path — в нашем случае, это /var/cache/nginx и /var/cache/fastcgi соответственно.

а) для proxy_cache_path:

б) для fastcgi_cache_path:

Хранение кэша в оперативной памяти

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

Создаем обычный каталог, в который будем монтировать часть оперативной памяти:

. и дадим на него полные права:

chmod 777 /var/ramdisk

Монтируем часть оперативной памяти как обычный каталог:

mount -t tmpfs -o size=2G ramdisk /var/ramdisk

* в данном примере мы монтируем 2 Гб оперативной памяти как папку /var/ramdisk. Возможно, правильнее будет заранее убедиться в наличие свободной памяти командой free.

Для автоматического монтирования открываем на редактирование fstab:

ramdisk /var/ramdisk tmpfs nodev,nosuid,noexec,nodiratime,size=2G 0 0

Теперь, когда у нас есть каталог, данные которого хранятся в оперативной памяти, редактируем параметр proxy_cache_path или fastcgi_cache_path в NGINX:

proxy_cache_path /var/ramdisk levels=1:2 keys_zone=all:64m inactive=2h max_size=2g;

fastcgi_cache_path /var/ramdisk levels=1:2 keys_zone=fastcgi:64m inactive=2h max_size=2g;

Источник

Подводные камни при использовании кэширования в nginx

В web-сервер и reverse-proxy nginx встроены очень мощные возможности по кэшированию HTTP-ответов. Однако в ряде случаев документации и примеров не хватает, в результате не все получается так легко и просто, как хотелось бы. Например, мои конфиги nginx-а местами написаны кровью. Этой статьей я попробую немного улучшить ситуацию.

В этой статье: а) подводные камни при полностраничном кэшировании; б) кэширование с ротацией; в) создание динамического «окна» в закэшированной странице.

Я буду предполагать, что вы используете связку nginx+fastcgi_php. Если вы применяете nginx+apache+mod_php, просто замените имена директив с fastcgi_cache* на proxy_cache*

Если выбирать, кэшировать ли страницу на стороне PHP или на стороне nginx, я выбираю nginx. Во-первых, это позволяет отдавать 5-10 тыс. запросов в секунду без каких-либо сложностей и без умных разговоров о «высокой нагрузке». Во-вторых, nginx самостоятельно следит за размером кэша и чистит его как при устаревании, так и при вытеснении нечасто используемых данных.

Кэширование всей страницы целиком

Если на вашем сайте главная страница хоть и генерируется динамически, но меняется достаточно редко, можно сильно снизить нагрузку на сервер, закэшировав ее в nginx. При высокой посещаемости даже кэширование на короткий срок (5 минут и меньше) уже дает огромный прирост в производительности, ведь кэш работает очень быстро. Даже закэшировав страницу всего на 30 секунд, вы все равно добьетесь значительной разгрузки сервера, сохранив при этом динамичность обновления данных (во многих случаях обновления раз в 30 секунд вполне достаточно).

Например, закэшировать главную страницу можно так:

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

fastcgi_cache_path: простота отладки тоже важна

fastcgi_cache_path /var/cache/nginx levels= keys_zone=wholepage:50m;

В директиве fastcgi_cache_path я выставляю «пустое» значение для levels. Хотя это немного снижает производительность (файлы будут напрямую создаваться в /var/cache/nginx, без разбиения по директориям), но зато на порядок облегчает отладку и диагностику проблем с кэшем. Поверьте, вам еще не раз придется руками залезать в /var/cache/nginx и смотреть, что там хранится.

fastcgi_cache_valid: кэшируем код ответа 304 тоже

fastcgi_cache_valid 200 301 302 304 5m;

В директиве fastcgi_cache_valid мы заставляем кэшировать не только стандартные коды 200 ОК, 301 Moved Permanently и 302 Found, но также и 304 Not Modified. Почему? Давайте вспомним, что означает 304. Он выдается с пустым телом ответа в двух случаях:

  • Если браузер послал заголовок «If-Modified-Since: date», в котором date больше либо равна значению заголовка ответа «Last-Modified: date». Т.е. клиент спрашивает: «Есть ли новая версия с момента date? Если нет, верни мне 304 и сэкономь трафик. Если есть, отдай мне тело страницы».
  • Если браузер послал заголовок «If-None-Match: hash», где hash совапдает со значением заголовка ответа «ETag: hash». Т.е. клиент спрашивает: «Отличается ли текущая версия страницы от той, что я запросил в прошлый раз? Если нет, верни мне 304 и сэкономь трафик. Если да, отдай тело страницы».

В обоих случаях Last-Modified или ETag будут взяты, скорее всего, из кэша nginx, и проверка пройдет очень быстро. Нам незачем «дергать» PHP только для того, чтобы скрипт выдал эти заголовки, особенно в свете того, что клиентам, которым уйдет ответ 200, он будет отдан из кэша.

fastcgi_cache_key: внимательно работаем с зависимостями

Особого внимания заслуживает значение в директиве fastcgi_cache_key. Я привел минимальное рабочее значение этой директивы. Шаг вправо, шаг влево, и вы начнете в ряде случаев получать «неправильные» данные из кэша. Итак:

  • Зависимость от $request_method нам нужна, т.к. HEAD-запросы в Интернете довольно часты. Ответ на HEAD-запрос никогда не содержит тела. Если убрать зависимость от $request_method, то может так совпасть, что кто-то до вас запросил главную страницу HEAD-методом, а вам потом по GET отдастся пустой контент.
  • Зависимость от $http_if_modified_since нужна для того, чтобы кэш с ответом 304 Not Modified не был случайно отдан клиенту, делающему обычный GET-запрос. Иначе клиент может получить пустой ответ из кэша.
  • То же самое и с $http_if_none_match. Мы должны быть застрахованы от выдачи пустых страниц клиентам!
  • Наконец, зависимость от $host и $request_uri не требует комментариев.
fastcgi_hide_header: решаем проблемы с безопасностью

Директива fastcgi_hide_header очень важна. Без нее вы серьезно рискуете безопасностью: пользователи могут получить чужие сессии через сессионную Cookie в кэше. (Правда, в последних версиях nginx что-то было сделано в сторону автоматического учета данного фактора.) Понимаете, как это происходит? На сайт зашел Вася Пупкин, ему выдалась сессия и сессионная Cookie. Пусть кэш на тот момент оказался пустым, и в него записалась Васина Cookie. Затем пришел другой пользователь, получил ответ из кэша, а в нем — и Cookie Васи. А значит, и его сессию тоже.

Можно, конечно, сказать: давайте не будем вызывать session_start () на главной странице, тогда и с Cookies проблем не будет. В теории это так, но на практике данный способ очень неустойчив. Сессии часто стартуют «отложено», и достаточно какой-либо части кода «случайно» вызвать функцию, требующую доступа к сессии, как мы получим дыру в безопасности. А безопасность — такая штука, что если в той или иной методике может возникнуть дыра по неосторожности, то эта методика считается «дырявой» по определению. К тому же есть и другие Cookies, кроме сессионной; их тоже не надо записывать в кэш.

fastcgi_ignore_headers: не даем сайту «лечь» от нагрузки при опечатке

fastcgi_ignore_headers «Cache-Control» «Expires»;

Сервер nginx обращает внимание на заголовки Cache-Control, Expires и Pragma, которые выдает PHP. Если в них сказано, что страницу не нужно кэшировать (либо что она уже устарела), то nginx не записывает ее в кэш-файл. Это поведение, хотя и кажется логичным, на практике порождает массу сложностей. Поэтому мы его блокируем: благодаря fastcgi_ignore_headers в кэш-файлы попадет содержимое любой страницы, независимо от ее заголовков.

Что же это за сложности? Они опять связаны с сессиями и функцией session_start (), которая в PHP по умолчанию выставляет заголовки «Cache-Control: no-cache» и «Pragma: no-cache». Здесь существует три решения проблемы:

  • Не пользоваться session_start () на странице, где предполагается кэширование. Один из минусов этого способа мы уже рассмотрели выше: достаточно одного неосторожного движения, и ваш сайт, принимающий тысячи запросов в секунду на закэшированную главную страницу, моментально «ляжет», когда кэш отключится. Второй минус — нам придется управлять логикой кэширования в двух местах: в конфиге nginx и в PHP-коде. Т.е. эта логика окажется «размазанной» по совершенно разным частям системы.
  • Выставить ini_set (‘session.cache_limiter’, »). Это заставит PHP запретить вывод каких-либо заголовков, ограничивающих кэширование при работе с сессиями. Проблема здесь та же: «размазанность» логики кэширования, ведь в идеале мы бы хотели, чтобы все кэширование управлялось из единого места.
  • Игнорировать заголовки запрета кэширования при записи в кэш-файлы при помощи fastcgi_ignore_headers. Кажется, это беспроигрышное решение, поэтому я его и советую.

Кэширование с ротацией

Статическая главная страница — это не так уж и интересно. Что делать, если на сайте много материалов, а Главная выступает в роли своеобразной «витрины» для них? На такой «витрине» удобно отображать «случайные» материалы, чтобы разные пользователи видели разное (и даже один пользователь получал новый контент, перезагрузив страницу в браузере).

Решение задачи — кэширование с ротацией:

  1. Мы заставляем скрипт честно выдавать элементы главной странице в случайном порядке, выполняя необходимые запросы в базу данных (пусть это и медленно).
  2. Затем мы сохраняем в кэше не одну, а, скажем, 10 вариантов страницы.
  3. Когда пользователь заходит на сайт, мы показываем ему один из этих вариантов. При этом, если кэш пуст, то запускается скрипт, а если нет, то результат возвращается из кэша.
  4. Устанавливаем время устаревания кэша малым (например, 1 минута), чтобы за день разные пользователи «отсмотрели» все материалы сайта.

В итоге первые 10 запросов к скрипту-генератору выполнятся «честно» и «нагрузят» сервер. Зато потом они «осядут» в кэше и в течение минуты будут выдаваться уже быстро. Прирост производительности тем больше, чем больше посетителей на сайте.

Вот кусочек конфига nginx, реализующий кэширование с ротацией:

Вы можете заметить, что по сравнению с предыдущим примером мне пришлось добавить еще 6 директив в location. Они все очень важные! Но не будем забегать вперед, рассмотрим все по порядку.

perl_set: зависимость-рандомизатор

С директивой perl_set все просто. Мы создаем переменную, при использовании которой nginx будет вызывать функцию встроенного в него Perl-интерпретатора. По словам автора nginx, это достаточно быстрая операция, так что мы не будем «экономить на спичках». Переменная принимает случайное значение от 0 до 9 в каждом из HTTP-запросов.

fastcgi_cache_key: зависимость от рандомизатора

Теперь мы замешиваем переменную-рандомизатор в ключ кэша. В итоге получается 10 разных кэшей на один и тот же URL, что нам и требовалось. Благодаря тому, что скрипт, вызываемый при кэш-промахе, выдает элементы главной страницы в случайном порядке, мы получаем 10 разновидностей главной страницы, каждая из которой «живет» 1 минуту (см. fastcgi_cache_valid).

add_header: принудительно выключаем браузерный кэш

Выше мы говорили, что nginx чувствителен к кэш-заголовкам, выдаваемым PHP-скриптом. Если PHP-скрипт возвращает заголовки «Pragma: no-cache» или «Cache-Control: no-store» (а также еще некоторые, например, «Cache-Control: не-сохранять, не-выдавать, меня-тут-не-было, я-этого-не-говорил, чья-это-шляпа»), то nginx не будет сохранять результат в кэш-файлах. Специально чтобы подавить такое его поведение, мы используем fastcgi_ignore_headers (см. выше).

Чем отличается «Pragma: no-cache» от «Cache-Control: no-cache»? Только тем, что Pragma — наследие HTTP/1.0 и сейчас поддерживается для совместимости со старыми браузерами. В HTTP/1.1 используется Cache-Control.

Однако есть еще кэш в браузере. И в некоторых случаях браузер может даже не пытаться делать запрос на сервер, чтобы отобразить страницу; вместо этого он достанет ее из собственного кэша. Т.к. у нас ротация, нам такое поведение неудобно: ведь каждый раз, заходя на страницу, пользователь должен видеть новые данные. (На самом деле, если вы все же хотите закэшировать какой-нибудь один вариант, то можно поэкспериментировать с заголовком Cache-Control.)

Директива add_header как раз и передает в браузер заголовок запрета кэширования. Ну а чтобы этот заголовок случайно не размножился, мы вначале убираем из HTTP-ответа то, что записал туда PHP-скрипт (и то, что записалось в nginx-кэш): директива fastcgi_hide_header. Ведь вы, когда пишете конфиг nginx-а, не знаете, что там надумает выводить PHP (а если используется session_start (), то он точно надумает). Вдруг он выставит свой собственный заголовок Cache-Control? Тогда их будет два: PHP-шный и добавленный нами через add_header.

expires и Last-Modified: гарантируем перезагрузку страницы

Еще один трюк: мы должны выставить Last-Modified равным текущему времени. К сожалению, в nginx нет переменной, хранящей текущее время, однако она магическим образом появляется, если указать директиву expires -1.

Хотя это сейчас (октябрь 2009 г.) не задокументировано, nginx создает переменные вида $sent_http_XXX для каждого заголовка ответа XXX, отданного клиенту. Одной из них мы и пользуемся.

Почему же так важно выставлять текущим временем этот заголовок? Все довольно просто.

  1. Давайте представим, что PHP выдал заголовок «Last-Modified: некоторая_дата».
  2. Данный заголовок будет записан в кэш-файл nginx (можете проверить: в нашем примере файлы хранятся в /var/cache/nginx), а потом отдан в браузер клиенту.
  3. Браузер запомнит страницу и дату ее модификации.
  4. … поэтому при следующем заходе пользователя на сайт в HTTP-запросе будет заголовок-вопрос «If-Modified-Since: некоторая_дата».
  5. Что же сделает nginx? Он достанет страницу из своего кэша, разберет ее заголовки и сравнит Last-Modified с If-Modified-Since. Если значения совпадут (или первое окажется меньше второго), то nginx вернет ответ «304 Not Modified» с пустым телом. И пользователь не увидит никакой ротации: он получит то, что уже видел раньше.

На самом деле, большой вопрос, как поведет себя браузер при наличии одновременно Last-Modified и Cache-Control no-cache. Будет ли он делать запрос If-Modified-Since? Кажется, что разные браузеры ведут тут себя по-разному. Экспериментируйте.

Есть и еще один повод выставлять Last-Modified вручную. Дело в том, что PHP-функция session_start () принудительно выдает заголовок Last-Modified, но указывает в нем… время изменения PHP-файла, который первый получил управление. Следовательно, если у вас на сайте все запросы идут на один и тот же скрипт (Front Controller), то ваша Last-Modified будет почти всегда равна времени изменения этого единственного скрипта, что совершенно не верно.

Динамическое «окно» в закэшированной странице

Ну и напоследок упомяну одну технику, которая может быть полезна в свете кэширования. Если вам хочется закэшировать главную (или любую другую) страницу сайта, однако мешает один маленький блок, который обязательно должен быть динамическим, воспользуйтесь модулем для работы с SSI.

В ту часть страницы, которая должна быть динамической, вставьте вот такой «HTML-комментарий»:

С точки зрения кэша nginx данный комментарий — обычный текст. Он будет сохранен в кэш-файле именно в виде комментария. Однако позже, при прочтения кэша, сработает модуль SSI nginx, который обратится к динамическому URL. Конечно, по адресу /get_user_info/ должен быть PHP-обработчик, который выдает содержимое данного блока. Более подробно данный способ описан в этой статье с Хабра.

Ну и, естественно, не забудьте включить SSI для этой страницы или даже для всего сервера:

Директива SSI include имеет еще одно, крайне важное свойство. Когда на странице встречаются несколько таких директив, то все они начинают обрабатываться одновременно, в параллельном режиме. Так что, если у вас на странице 4 блока, каждый из которых загружается 200мс, в сумме страница будет получена пользователем через 200мс, а не через 800.

Исходный текст данной статьи можно прочитать тут: http://dklab.ru/chicken/nablas/56.html

Читайте другие интересные статьи

Понравилась статья, расскажи о ней друзьям, нажми кнопку!

Источник

Оцените статью