Программист
0,0
рейтинг
22 июля 2011 в 09:58

Администрирование → Прозрачное кэширование в nginx для всех и каждого

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

С версии 0.8.46 в nginx появились опции, позволяющие легко и просто настроить прозрачное кэширование для анонимных пользователей.

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

Научим сайт начинать сессию только когда она нужна


Для широкораспространенных сайтов на PHP это можно сделать выставив в ноль session.auto_start в php.ini или, лучше, в настройках виртуального хоста Apache. Также нам нужно задать подходящие имя для сессионной куки:

php_admin_value session.name "session"
php_admin_value session.auto_start 0

Если сайт сам стартует сессию в коде, то session_start() следует заменить на:

if (!empty($_COOKIE[session_name()]) || $_SERVER['REQUEST_METHOD'] == 'POST') {
	session_id() || session_start();
}

Так, сессия будет начата только если
  • куки с сессией уже есть или если
  • нам отправляют форму, например, с логином и паролем.
Что нам и требовалось.

Настроим nginx на кэширование запросов без сессионной куки


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

В любом удобном месте, например, в конфиге виртуального хоста nginx перед блоком server, добавим строки:

proxy_cache_path /var/lib/nginx/cache levels=1:2 keys_zone=cache:30m max_size=1G;
proxy_temp_path /var/lib/nginx/proxy 1 2;
proxy_ignore_headers Expires Cache-Control;
proxy_cache_use_stale error timeout invalid_header http_502;
proxy_cache_bypass $cookie_session;
proxy_no_cache $cookie_session;

Не забываем создать соответствующие каталоги:

mkdir -p  /var/lib/nginx/cache
chown -R www-data /var/lib/nginx/cache
chmod 700 /var/lib/nginx/cache

В соответствующий блок location добавим выделенные три строки:

location / {
	....
	proxy_cache cache;
	proxy_cache_valid 10m;
	proxy_cache_valid 404 1m;
	....
	proxy_pass http://backend;
}

Перезапускаем nginx, удаляем куки из браузера и наслаждаемся скоростью работы сайта.

Тонкая настройка кэширования


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

header("X-Accel-Expires: $seconds");

Для полного отключения кэширования, например, счетчиков, используйте:

header("X-Accel-Expires: 0");


Проверка на прочность


Проверим главную страницу сайта на прочность в сто потоков:

ab -n 1000 -c 100 http://www.example.com/

C использованием кэширования тестовый сайт легко выдавал более 100 rps, оно и понятно.
Без кэша — предсказуемо показывал 503 ошибку на ~70% запросов.

Принудительное обновление кэша


Если в директиву proxy_cache_bypass изменить так:

proxy_cache_bypass $cookie_session $http_x_update;

То можно будет принудительно обновлять кэш страницы при необходимости:

curl -s -o /dev/null  -H "X-Update: 1" www.example.com

Заголовок лучше заменить на любой другой, известный только вам.
Алексей @alexkbs
карма
81,0
рейтинг 0,0
Программист
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Администрирование

Комментарии (65)

  • +1
    Спс. А как удобнее сбросить кеш, например, при изменении страницы через админку?
    • +4
      Для этого нужно proxy_no_cache заменить на

      proxy_no_cache $cookie_session $http_x_update;

      и при обновлении страницы через админку делать внутренний запрос к nginx без куки, но с заголовком X-Update: 1
      • 0
        Имхо при этом страница будет не закэширована, но сам кэш при этом не измениться
        • 0
          Согласно вики на английском кеш изменится. В русской документации написано ровно об обратном. Возможно, вы правы и вместо proxy_no_cache следует использовать proxy_cache_bypass. Проверю.
        • 0
          Если использовать не proxy_no_cache, а

          proxy_cache_bypass $cookie_session $http_x_update;

          то кеш будет обновляться. Только что проверил на 1.0.5.
    • 0
      Если у вас апдейты несрочные, то ставьте время жизни кеша в одну минуту.
      • 0
        так не интересно :)))
    • +1
      Если Вы хотите удалять кэш из админки, Вы также можете добавить в секцию локейшн строку:
      fastcgi_cache_key "$host$request_uri" или proxy_cache_key "$host$request_uri", смотря что Вы используете, и в указанной папке хранения кэша, кэш страниц будет лежать под именем:
      md5($host.$request_uri).

      Пример:
      зайдя по ссылке: habrahabr.ru/blogs/nginx/124684/
      кэш будет лежать в указанной папке с именем: md5('habrahabr.ru/blogs/nginx/124684/');
    • 0
      Сысоев обещал это приделать, но похоже руки так и не дошли.
      Я лично убиваю кэш и рестартую nginx
      • 0
        Что значит не сделал? Я так делаю.
        • +1
          Как так? Удаляете файлы?
          Речь шла о более удобных и правильных способах управлением кэшем — сигналом хотя бы или хитрым запросом.
          • 0
            А что Вам мешает реализовать свою схему очереди? Сделайте стек очередей и отправьте туда сигнал на удаление кэша, что Вам это мешает сделать?

            А по поводу сигнала nginx-у, как? Он веть хранит файлы под хэшем, он не может пробежаться даже регулярке: site.zone/news/*.

            Поэтому самому удалять это самый простой и не сильно уж и долгий процесс, тем более если Вы кэш будете хранить в памяти.
            • 0
              очереди чего?

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

              Тут мне пришла в голову мысль что может быть таймеры на самом деле — это время модификации файла/ключа. Надо будет попробовать их состарить в качестве сброса кэша.

              Но все равно это не снимает вопроса в «первона#е» — как через админку сайта сбросить ему кэш. Приходиться рыться в конфигах и вытаскивать путь под doc_root-ом, что уже плохо. А потом там шерудить, что совсем не хорошо. Было бы намного удобнее иметь возможность задать ключ для сброса кэша в пределах действия proxy_cache — для location или server.
              Типа пусть каждый занимается своим делом. Управлять кэшем — дело как раз для proxy модуля nginx, а не всяких приблудных скриптов.
              • 0
                Я не хочу и не буду с Вами спорить, я только озвучил как это делаю я.
                Просто при администрировании у меня не нужно удалять весь кэш сайта, а всего кэш 1-10 страниц, которые лежат в памяти, как вы понимаете — это почти мгновенная операция.
    • +1
      Если страницы надо удалять из кэша поштучно, то есть модуль, который это дело делает (директива proxy_cache_purge), у меня настроена хитрая комбинация символов, их надо дописать в урл, чтобы удалить из кэша конкретную страницу.

      Если массово, то зная список ссылок можно узнать список путей на диске (директива proxy_cache_key) и удалить их скриптом.
    • 0
      Правильно будет использовать директиву не proxy_no_cache, а

      proxy_cache_bypass $cookie_session $http_x_update;

      Например, можно поставить обновление главной страницы раз в минуту через curl:

      curl -H "X-Update: 1" www.example.com
  • +6
  • +2
    Что-то вы все переврали.
    Объясню как есть на самом деле:
    1. Nginx кеширует ответы от бэкэнда, которые он разрешил кешировать. Nginx смотрит на заголовки Cache-control, Expires.
    2. В php, если стартует сессия, то автоматом добавляются заголовки
    • 0
      (само отправилось)
      автоматом добавляются заголовки:
      Expires: Thu, 19 Nov 1981 08:52:00 GMT
      Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
      Соответственно nginx такой ответ не закеширует.

      3. Соответственно, как вы правильно сказали, чтобы кеш работал, не надо стартовать сессию.
      А строчку «proxy_ignore_headers Expires Cache-Control;» как раз нужно убрать, тогда всё будет работать правильно — там где нет сессии и нет заголовков закешируется, там где есть заголовки будет без кеша.

      Кстати
      • 0
        Если у вас есть время влезать в исходники какого-то случайно выбранного сайта, написанного бог знает когда бог знает кем — убирайте proxy_ignore_headers и ищите, где там в коде сайта задаются заголовки Cache-Control и Expires чтобы их убрать.

        У меня на это времени и сил нет, в жизни есть занятия интересней, чем копаться в чужом коде без явной необходимости, которой здесь нет.
        • +1
          Если мы говорим о случайном проекте, в котором нет времени разбираться, тогда вы правы.
          Можно использовать различные костыли.
          • 0
            спасибо за правильное объяснение
      • 0
        Вы не совсем правы, дело в том, что если у Вас отдаются изображения через тот же nginx, то у Вас возникнет такая ситуация, что все картинки будут тоже закешированны, т.к. он сам добавляет эти заголовки для браузера.
        Сам столкнулся с подобной проблемой, не мог понять почему у меня уменьшался объем на ШДД, а nginx просто параллельно еще кешировал и сами изображения, тоесть просто копировал их в папку для кеша.
        • 0
          Для картинок лучше отдельный location без кеширования но с expires и всем, что нужно:
          location ~* \.(jpg|jpeg|gif|png|ico|css|js|swf)$ {
          expires max;
          root /var/www;
          }
          • 0
            Для статики вообще желательно использовать отдельный субдомен, чтобы не заставлять его бегать по регуляркам, я лично так делаю.

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

            Что касается приведенного Вами примера, он тоже не всегда хорош, у меня есть изображения которые гинерятся скриптом.
        • 0
          Про статику никто не говорил :)
          Если статика на том же сервере, то её нужно выносить в отдельный локейшен, чтобы она бралась напрямую с диска, если она на другом серваке, то можно и закешировать, чтобы не гонять между серваками и не напрягать бэкендовый апач.
      • 0
        Поэтому proxy_ignore_headers Expires Cache-Control; желателен.
    • 0
      Прочитайте, пожалуйста, документацию в разделе про proxy_ignore_headers и ещё раз скажите мне: где я что переврал?
      • +1
        Вы пишете: нужно не стартовать сессию — это не совсем верно.
        Т.к. дело не в сессии, а в заголовках, которые автоматом шлются при старте сессии.
        Но ваш пример тоже работает.
  • 0
    На самом деле можно и не указывать proxy_cache_bypass $cookie_session; proxy_no_cache $cookie_session; т.к. nginx не кэширует если видит заголовок Set-Cookie. Да, в документации про это ни слова :)
    • 0
      Эти заголовки нужны чтобы клиенты, у которых в запросе есть заголовок Cookie с куки session проходили мимо кеша. К заголовку Set-Cookie эти директивы отношения не имеют.
      • 0
        еще раз. nginx по дефолту не будет кэшировать если увидит в ответе клиенту заголовок Set-Cookie внезависимости от того что там в proxy_no_cache. это поведение можно изменить добавив proxy_ignore_headers «Set-Cookie»;
        • 0
          И правильно делает. Зачем оно нужно — кешировать запросы в которых есть Set-Cookie? Вдруг это вы, пытаетесь зайти на сайт, чтобы оставить комментарий?
          • 0
            а может вы гуглобот и вам побоку на всякие куки, но вы приходите регулярно за одним и тем же :)
            в общем я лишь хотел указать на избыточность proxy_no_cache $cookie_session; и на неполноту документации.
            • 0
              По этой схеме куки отправляются только при POST-запросах, а те по-умолчанию не кэшируются. Следовательно, мы ничего не теряем.
        • 0
          А если такая ситуация:
          * Захожу на какую-то страницу типа about. Она кешируется
          * Затем я логинюсь, страница логина не кешируется из-за Set-Cookie
          * захожу снова на about — и получаю кешированную струницу??

          А это не всегда желательный эффект, ведь где нибудь может быть написано Hello Guest / Hello yngvie
          • 0
            Тут уже проверяйте кукисы в конфиге nginx и если они есть, определенная кукиса, то не юзайте кэш.
          • 0
            Сложнее. Если кэшировать страницы с Set-Cookie, то вы получите куки другого пользователя, который входил на сайт до вас. Потому nginx по-умолчанию не кэширует запросы в которых есть Set-Cookie.
          • 0
            Так можно сделать кеш с куками для каждого отдельного юзера.
            Сейчас мсто на хостингах резиновое, ssd, все дела.
    • 0
      proxy_cache_bypass $cookie_session; нужен
  • +1
    Старт сессии при POST-запросе? Ужасный совет. Сколько же проблем будет, когда об этом все забудут.
    • 0
      Конечно, особенно когда об этом забудут те разработчики, что писали пять лет назад некий сайт, внезапно ставший популярным. Если вы на своём сайте можете сделать правильно и если у вас есть время сделать как надо — этот совет не для вас.
      • –1
        Правильно нужно делать всегда. Отладка плохонаписанного кода или внесение в него новой функциональности занимеат куда больше веремени нежели написание правильного кода.
        POST-запрос не означает начало новой сессии. Это допущение не верно. Кроме того, аутентификация может осуществляться и GET-запросом (напрмер OpenID).

        А задача решается 2-мя фунциями/методами: get*/set*. get* получает значение из $_SESSION[], при этом, если сессия не существует, то ничего не стартуется, а возвращается какое-то пустое значение. set* устанавливает значение, при этом стартует сессию.
        • 0
          Задача так не решается если перед вами сайт, который написан бог знает когда, бог знает кем, а заказчик не заинтересован оплачивать ваше время на глубокую переработку сайта. Выше я писал что в жизни есть занятия интересней, чем копаться в чужом коде без явной необходимости, которой здесь нет, и тем более без оплаты. Вы готовы работать бесплатно? Я — нет.
          • +1
            Такие условия очень желательно оговаривать в статье.
            • 0
              В начале статьи написано что «редкий сайт нельзя довести.» Довести — значит изменить до определенного состояния (словарь Ушакова). Дальше написано о том, что этот метод применим к большинству сайтов. Большинство сайтов на PHP — сами знаете какое. По-моему всё должно быть понятно.
  • –2
    А зачем не стартовать сессию? Можно же просто при логине добавить куки LOGINED или там ADMIN и если она есть отдавать напрямую? По моему легче в метод авторизации добавить установку куки чем из всего сайта выпилить сессии — если я не админ это не значит что они мне не нужны.
    • 0
      Если вы не стартуете сессию, то как в вашем интернет-магазине будет работать корзина?
      Этот рецепт подходит для любых сайтов, использующих штатные сессии.
      • 0
        Не совсем понял. Я же и спрашиваю как без сессий быть? хотя с другой стороны — изменилась сессия вероятнее всего и вывод изменился так что в принипе логично
        • +2
          Не надо быть без сессий. Надо не использовать их без нужды.
    • 0
      «Logined» — это на каком языке?
      • 0
        Gramar nazi? А мне так нравится:) оно из за того разве не скомпилится или не выполнится?
        • 0
          Ну, это как говорить, что безграмотно написанное и так понятно.
          Впрочем, не мне Вас судить, Вы правы.
          www.flickr.com/photos/highglosshighs/2491131525/
          • 0
            ну видите, не я один такой, уверен тут тоже половину это не удивило :)
        • НЛО прилетело и опубликовало эту надпись здесь
          • 0
            Уговорили :) Теперь буду использовать только рассово верную форму LOGGEDIN. Скажите, а вы пользуетесь словом «гуглить», ведь «to google» сам не так давно стал verb'ом?
            • НЛО прилетело и опубликовало эту надпись здесь
              • 0
                да и я без претензий :) кстати на гугловском кодесерче огромный перевес в сторону Loggedin :)
  • 0
    Вы бы объяснили без привязки к пхп, а то не очень понятно, что в nginx-овом понимании называется сессия, и как ее воспроизвести на какой-нибудь джанге, для примера.
    • +1
      Для Django замените везде $cookie_session на $cookie_sessionid. Этого достаточно.
  • 0
    Есть один неприятный баг — если значение куки начинается с 0, то нгинкс ошибочно считает ее пустой. Для решения проблемы существует патч. Другой способ — просто генерировать правильный PHPSESSID.
    • 0
      В исходниках nginx 1.0.4 я вижу что эта проблема уже исправлена.
  • +1
    Нам нужно защитить его от всплесков посещаемости

    Всплески посещаемости — это же хорошо, не надо от них защищать, надо к ним готовить =)
  • 0
    Это просто чудо какое-то!
    Правда, не сразу получилось настроить — был затык на уровне прав записи юзера www-data в папку с кешем.
    Но когда заработало, то всё залетало.

    pS. Важно помнить (было выше в коментах), что ключ кеша строится как MD5(Url), поэтому если такой кеш настроить сразу на несколько сайтов, одиноковый УРЛ на разных сайтах будет давать одинаковый результат — ту страницу, что первой попала в кеш (например, индесная страница с УРЛом "/")
    • 0
      Хорошее замечание. Ключ хеша задается директивой proxy_cache_key

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