Программист
0,0
рейтинг
25 декабря 2012 в 13:19

Администрирование → Контролируемое кэширование страниц в nginx из песочницы

Введение

Как известно, nginx умеет кешировать ответ сервера, и выдавать его по запросу вместо обращения к бэкенду, экономя тем самым ресурсы сервера. Скорость отдачи таких закешированных страниц иногда поражает, ради таких скоростей иногда не жалко переносить на javascript многие функции сайта только для того, чтобы иметь возможность закешировать ещё 1 страницу целиком (Например, вынести отрисовку плашки с авторизацией юзера на js, чтобы иметь возможность кешировать страницу, которая идентична для всех пользователей, за исключением этой самой плашки).

Я много раз использовал возможность кэширование nginxом страниц, и натыкался на пару неудобных для себя вещей:
  • Можно легко закешировать вообще все страницы, но для динамических сайтов или для сайтов с авторизацией нужно ли это?
  • Можно закешировать отдельно несколько url, вида /album/*, но не переписывать же конфиг nginx каждый раз при появлении новых разделов сайта?


Если проект в дополнение ко всему выкладывается админами, раскладывается по нескольким серверам, обрабатывается несколькими nginx — переписывание конфигов становится нетривиальной задачей.

Для себя эту задачу я решил при помощи заголовка X-Accel-Expires и нескольких строк в конфигурации nginx.

Задача

Для своего решения по кешированию страниц я сформировал следующие требования:
1. Управлять включением или выключением кеширования каждой страницы из php.
2. Управлять кешированием по полному url.
3. Управлять временем кеша каждой отдельной страницы или раздела сайта.
4. Иметь возможность поменять время кеширование и все настройки кеша в любой момент без перезаписывания конфигурации nginx.

Решение

В моем случае используется fastcgi (php для отдачи контента, nginx для отдачи статики)

Решим задачу в несколько шагов:
1. Зададим в конфигурации nginx новую зону для кеширования. Строка прописывается перед разделом «server» с конфигурацией нашего сайта, например.
fastcgi_cache_path /tmp/mycache levels=2 keys_zone=mycachename:5m inactive=2m max_size=1500m;


2. В разделе server задаем кеширование страниц следующим образом:
location / {
                        root $PROJECT_ROOT/data/ ;
                        fastcgi_cache mycachename;
                        fastcgi_cache_valid 200 301 302 304 30m;
                        fastcgi_cache_key "$request_method|$http_if_modified_since|$http_if_none_match|$host|$request_uri";
                        fastcgi_cache_use_stale error timeout invalid_header;
                        fastcgi_pass_header "X-Accel-Expires";
                        fastcgi_pass   127.0.0.1:9900;
                        fastcgi_index  index.php;
                }


Здесь обращаем внимание на следующие параметры:
fastcgi_cache — имя заданной в пункте 1 зоны
fastcgi_cache_valid — для каких кодов ответа включаем кеширование. Последний параметр — время кеширования — фактически будет максимальным временем кеша для страницы. Т.е. мы сможем управлять временем кеша от 1 секунды до 30 минут.
fastcgi_cache_key — чтобы не закешировать действительно разные запросы в одном файле
fastcgi_pass_header «X-Accel-Expires» — необязательно, но полезно при отладке — смотреть, на сколько в итоге мы закешировали страницу.

3. В самом php дописываем следующие вещи:
Ставим заголовок по умолчанию:
<?php
header("X-Accel-Expires: 0");


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

function flushResponse() {
        if (self::$cache_time) {
            // nginx cache
            header("X-Accel-Expires: " . (self::$cache_time), true);
        }
        ob_flush();
    }

Здесь мы проверяем, можно ли кешировать страницу (в моем случае по некоторой логике или из конфигурации каждой страницы сайта мы получаем время кеша страницы в секундах) и выставляем заголовок X-Accel-Expires еще 1 раз, перезаписывая выставленный ранее со значением 0.

Что происходит?

Таким образом, nginx при обработке запроса делает следующее:
1. Проверяет, не лежит ли запрашиваемая страница в кеше (имя файла кеша определяется по параметру fastcgi_cache_key)
2. Если лежит — отдает контент без обращения к php и рвет коннект.
3. Выполняет php скрипт, получает в ответ заголовок X-Accel-Expires. Если он равен 0 — ничего не кеширует, иначе кеширует на заданное в заголовке количество секунд. Максимальное время в нашем случае — 30 минут, определяется последним аргументов параметра fastcgi_cache_valid.

Заключение

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

Я не описывал всех прелестей именно механизма кеширования nginx, которое обсуждалось уже множество раз. Статья призвана помочь начать использовать эту технологию людям, для которых легче писать логику на своем языке программирования, а не посредством конфигурации nginx.
Михаил Чубарь @rasstroen
карма
3,0
рейтинг 0,0
Программист
Реклама помогает поддерживать и развивать наши сервисы

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

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

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

  • 0
    Спасибо за решение, выглядит более чем удобно!
  • +5
    не жалко переносить на javascript многие функции сайта только для того, чтобы иметь возможность закешировать ещё 1 страницу целиком

    Посмотрите на SSI.
    • 0
      На самом деле очень заманчиво, в планах освоить. Меня очень привлекают ajax запросы своей экономией траффика, но юзеров с отключенным javascript по-прежнему достаточно, так что ssi меня ждёт.
      • 0
        А сколько у вас таких?
      • +2
        покажите свою статистику по отключенным JS — сейчас невозможно так жить! Половинасайтов просто не будут работать!
        • –1
          е6 ничем не отличаются от денег остальных пользователей, верно для js.
          • +2
            у меня ие6 — 0,2% — по деньгам давно просто невыгодно думать про них
            • 0
              Криво откромментировал, правильно «деньги пользователей ие6 ничем...»

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

              Из личного опыта — система микрокредитования, пара тройка бабушек из глубинок России не смогли вернуть кредит, т.к. форма для банка генерировалась на лету ajaxом и перегенерировалась на лету при изменении суммы. Форму бабушки видели, но форма всегда сабмитилась неизмененная с начальной суммой, которую заплатить бабушки не могли. Объяснить им про js оказалось нереально, а потеряли на этом несколько тысяч. Да и процент таких на сайтах ориентированных на старшее поколение много выше, и куки у многих отключены, и ие6 встречается чаще.
              • 0
                я тоже наверно «потерял» много тысяч. На самом деле сэкономил конечно. Если по честному учесть еще сеансы психотерапевтов для верстальщиков — то уж точно. :)
                • –1
                  Как-то мы плавно на ие со скрипта перешли)

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

                  Я до сих пор теряюсь — для себя делаю на js, на серьезных проектах стараюсь не надеяться на браузер пользователя, особенно если пара лишних часов разработки принесет не только эстетическое удовлетворение, но и плюсик к кроссплатформенности
              • 0
                А не проще/дешевле бабушкам по запросу несколько тыщ просто тупо перевести куда просят?
  • 0
    использование $request_uri ведет к тому, что запросы вида /product/art12/ и /product/art12/?utm_source=market&… будут закешированы дважды. Это не проблема, если вы никак не отслеживаете utm метки, иначе смысл в кэшировании немного теряется, так как, если с маркета на один товар ходят не часто, то все они будут работать БЕЗ кэширования.
    Более того, не все роботы шлют $http_if_modified_since $http_if_none_match, поэтому очень часто запрос будет без кэша.
    • 0
      Такие url конечно у нас никак не кешируются (передается X-Accel-Expires: 0).

      Насчет $http_if_modified_since $http_if_none_match — могу ошибаться, но также закешируется, даже если переданы не будут (ключ кеша будет просто другой), но не уверен, потестирую.

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

      Ваш комментарий натолкнул на мысль попробовать задавать имя кеша отбрасывая GET параметры на некоторых страницах, (чтобы site.ru/?asd и site.ru/?asdasd кешировались в один файл), тем более кейсы для этого у меня уже есть, спасибо за идею!
      • 0
        проблема не то что «не будет кэшироваться», а то, что эффективность кэша будет стремится к 0.
        • 0
          Да, спасибо, понял, роботы придут и закешируют половину страниц сайта, при этом кеш будет никак не использован простыми пользователями с нормальными заголовками. Над fastcgi_cache_key думать и думать, мы не думали по причине отсутствия роботов как класса в приложении (мы не индексируемся)
          • +2
            Если у вас приложение, то кэш в том виде который предложен — не имеет смысла, он будет только мешать.
            Если обращения редкие (и база небольшая) — то нужно повысить скорость выполнения самого скрипта, а не кэшировать на 30 минут.
            А если приложение супер популярно, то нужно кэш перенести из файлов в memcache (поддерживается nginx), и генерить и обновлять кэш своим скриптом в зависимости от изменения контента, а не по времени.

            • 0
              У нас страница вида «самые популярные активности за 5 минут», на ней же еще несколько блоков с контентом, валидным в течение минут 5-10. Конечно, второй уровень кеша, как раз memcache, работает и разгружает бд (выборки довольно тяжелые, оптимизировать их дальше уже не получается). кеш nginx понадобился уже чтобы эту самую страницу не дергать (даже обращение в мемкеш и отрисовка с учетом нескольких бэкендов не давала нужного rps), а nginx кеширует эту страничку на 5 минут и высвобождает очень много ресурсов. Т.е. вместо обращения к php, а оттуда в memcache мы отдаем с диска уже сохраненную страницу.

              Или имеется в виду хранить кеш nginx в memcache вместо диска?
              Up. перечитал комментарий, даже не думал о ручном формировании кеша nginx Оо. Вы определили моё занятие на несколько ночей вперёд.
              • 0
                конечно последнее, но конкретно в данной ситуации это ничего не изменит.

                Так у вас приложение или сайт?
                • 0
                  Приложение в iframe в соцсети, не идексируется волевым решением поисковиками.
  • –2
    Остается мелочь — приделать к nginx и Битриксу нечто, что сбрасывало бы кеш страницы по команде из Битрикса, и кеш всего сайта при нажатии в Битриксе ссылки «сбросить кеш». Цены б не было!
    • 0
      Вот тут habrahabr.ru/post/124684/ написано про сброс кеша nginx, правда по 1 странице, что наверное не очень удобно. Возможно, можно делать reload nginx изменив ключ кеша (подмешать туда текущую версию сайта, например), но затирать кеш ни одним из возможных способов мне еще не доводилось.
      • –1
        Я потому и говорю, что надо это решать и на стороне nginx, и на стороне Битрикса. API-то там есть, но надо понимать, как и что и зачем там…
        • 0
          да что там понимать, при нажатии в битриксе «очистить» — создавайте файл в корне (наприер cache.lock).
          Сделайте баш скрипт, который будет смотреть наличие этого файла, и при его наличие удалять папку /tmp/cache… (какая указанна в конфиге для домена в _cache_path) и удалять этот файл.
          Одна строка на php
          И 3 строки на bash
          В крайнем случае можно эту проверку засунуть в php. А можно вместо создания файла сразу удалять папку с кэшем из php.
          Вопрос может быть только в прав на файлы и папки кэша.
          • –2
            Я бы сделал, но… я не умею понимать, когда в Битриксе срабатывает ссылка «очистить кеш» (мы же говорим об API, а не грязном хаке кода, которой, быть может, пропадет при следующем обновлении ядра). И еще меньше в курсе, как различать, что сбрасывать, кеш одной страницы, или кеши связанных с ней по данным страниц, или кеш всего сайта…
            • +2
              я бы на вашем месте отказался от bitrix, так как если вы на этапе проектирования «я не умею понимать», то в дальнейшем будут сложности с быстродействием, которые средствами API не решить.
              Видимо вы не видели код API, чтобы говорить «грязном хаке кода».
              • –4
                Я бы отказался, если бы решение было за мной. Я и не буду делать сайт, просто очень хочется подружить кеширование средствами битрикса, и кеширование на уровне веб-сервера.

                Битрикс поставляется как веб-приложение (читай — голый php-код), и идет (из коробки) не очень дружащим с их же собственным скриптом настройки сервера под задачу хостинга (в частности, скрипт настраивает схему работы apache + mod_php + nginx, при этом битрикс по умолчанию делает gzip-сжатие страниц — т.е. апач страницы жмет, затем их разжимает nginx, сжимает уже сам, и отдает на-гора; почему бы скрипту не настроить схему nginx + php-fpm — так далеко я даже не смотрю). Так вот я бы был рад видеть, что Битрикс в кои-то веки подружится с веб-сервером в плане кеширования, ибо веб-сервер явно смог бы кое-что делать эффективнее php-приложения.
          • +1
            Одна строка на php
            И 3 строки на bash

            Так бы и сказали: php люблю, баш — нет. А то сразу «три строки» ;-)

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