nginx, ещё раз про кэширование

    Иногда скорость роста проекта несколько выше чем скорость оптимизации веб-приложения или приобретение более мощного оборудования под backend.

    Наиболее простая схема «распараллеливания» нагрузки — вынос основной нагрузки на несколько frontend. Раньше приходилось мучиться (или наслаждаться, кому как) с webdav'ами, кластерными ФС и прочими хитростями чтобы обеспечить актуальную информацию, так было до тех пор, пока не появился nginx, а точнее proxy_store и proxy_cache в нём.



    В общем случае, схема работы выглядит следующим образом:
    Несколько frontend (дешевые сервера без raid и прочих взрослых примудростей) через round robin отдаются клиентам.
    frontend отдаёт с себя статику, проксирует и отдаёт с себя кэшированную динамику для гостей и осуществляет прозрачное проксирование для зарегистрированных пользователей.
    ТТХ backend в данном случае большого значения не имеет, вопрос правильной конфигурации его нужно обсуждать индивидуально. Дополнительное кэширование всего чего можно на backend приветствуется.

    Преимущества такого подхода — в стоимости данной «кластеризации» за счёт бюджетных характеристик frontend. Отказ любого frontend не критичен, достаточно поднять его ipшку (при наличие такой возможности) на другом frontend или изменить конфигурацию DNS (что может чревато недоступностью для ряда пользователей). На практике, городской портал где видео\фото хранилище на 4Тбайт (занято на backend) на frontend'ах лежит не более 300Гбайт того, что реально запрашивают пользователи (чистить можно по atime но учитывая, что на frontend ФС монтируется с noaime то по дате создания файла). Статичное содержимое на backend генерируется с новым именем, модификация файла также приводит к созданию нового имени файла, чтобы frontend корректно работал.

    http {
    proxy_cache_path /var/www/cache levels= keys_zone=mycache:150m;
    ...
    }

    server {
    #устанавливаем дефолтовый флаг, не кэшировать
    set $cached 0;
    listen server;
    server_name server *.server;
    #нужно чтобы отдавать красивую 500Ашипку с себя
    error_page 502 503 504 509 /500.html;
    #если где-то что-то забыли, то будет работать схема прозрачного проксирования
    error_page 404 = @nocached;

    expires epoch;
    root /var/www/html;

    location = /500.html {
    }

    #динамику будем брать с frontend и если отсутствует, то скачивать
    location ~* ^.+\.(jpg|jpeg|gif|gz|zip|flv|rar|wmv|avi|css|swf|png|htc|ico|mpeg|mpg|txt|mp3|mov|js)$ {
    expires 1y;
    error_page 404 = @fetch;
    }

    #кэшируем статику на себя
    location @fetch {
    proxy_pass httр://backend;
    proxy_store on;
    proxy_temp_path /var/www/_fetch;
    proxy_set_header Host mydomain;
    proxy_set_header If-Modified-Since "";
    }

    #для зарегистрированных проксируем прозрачно
    location @nocached {
    proxy_pass httр://backend;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    #гостям проксируем и кэшируем
    location @cached {
    proxy_pass httр://backend;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_cache mycache;
    proxy_cache_valid 200 301 302 304 5m;
    proxy_cache_key "$request_method|$http_if_modified_since|$http_if_none_match|$host|$request_uri";
    proxy_hide_header "Set-Cookie";
    proxy_ignore_headers "Cache-Control" "Expires";
    }

    #иначе морда домена не будет работать
    location = / {
    return 404;
    }

    location / {
    #если нет нашей куки
    if ($http_cookie !~ "userid" ) {
    set $cached 1;
    }

    if ($request_method = POST) {
    set $cached 0;
    }

    if ($request_method != GET) {
    set $cached 0;
    }

    if ($cached = 1) {
    error_page 404 405 502 504 = @cached;
    break;
    }

    if ($cached = 0) {
    error_page 404 405 502 504 = @nocached;
    break;
    }

    }

    }


    Всё это дело служит на благо моего новосибирского сайта
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 27

      0
      Отличная статья, жаль что таких полезных мало…

      Очистку делать по крону, тупо перебирая файлы и сравнивая время создания?
        +1
        А что в ней полезного? Немного полезного текста и огромный конфиг, по моему должно быть наоборот :)
          +3
          Конфиг говорит сам за себя, так что его можно считать за текст:)

          По сравнению с 90% последних топиков этот — мегаполезный.
          0
          Достаточно заменить в
          #динамику будем брать с frontend и если отсутствует, то скачивать

          expires 1y;

          на нужое значение
            0
            А если мне нужно, чтобы клиент кешировал в браузере на очень долго, а на сервере обновлять неактуальную информация чаще?

            К примеру, я обновил CSS. Мне надо убить кеш на фронтенде, а клиенту отдать другой URL, по которому он закеширует таблицу стилей на всегда и больше не будет ее запрашивтаь.
              0
              Пардон, чушь сморозил.
              Давно в nginx не копался, старость, склероз )
                0
                Ну достаточно в веб-приложении отдавать стиль вида вася.123.css и сделать ченить типа реврайта

                  0
                  rewrite ^(.*)/css/(.*).([0-9]).css$ $1/css/$2.css last;

                  т.е. на вебсервере лежать у вас будут вася.css, а сервера-кэши будут забирать уже новое имя. Собственно это задача уже системы публикации, а не серверов-кэшей. Они работают всё-же тупо на обработке 404.
                    0
                    видимо все-же rewrite ^(.*)/css/(.*).([0-9]*).css$ $1/css/$2.css last; :)
                      0
                      :) ну если ревизий будет не больше 10 то пойдет и предыдущий вариант :)
                      0
                      Так и будет. Только nginx оставит в директории кеширования два файла. Один от 123, другой от 124. Вот поэтому я и спрашиваю надо ли по крону очищать директорию. И если надо, то как это сделать просто и эффективно?
                        0
                        Если раздел монтирован без noatime, вы можете на atime смотреть, это как бы самый простой способ. Но тут зависит от количества файлов, если их много-много десятков миллионов то иногда проще хранить чем удалять мелочь всякую.
                0
                Очистка статики да, по крону, время модификации старше Х дней и всё. В принципе, можно не монтировать с noatime и смотреть на atime и по ней удалять. Объем как говорил небольшой, меньше полтеррабайта того, что реально запрашивают люди. Хотя вот яндекс поиск по картинкам повадился иногда заглядывать самые древние посты, но не критично, яндекса поиска по видео пока нет :)
                0
                Отступы не помешали бы, а вообще спасибо :)
                  +5
                  Спасибо за рассказ и конфиг. Действительно, очень полезно почитать такие статьи.

                  Однако читать было как-то немного щекотно. «Несколько frontend… отдаются… ». Я так понимаю, вам не хотелось писать слово «фронтенд». Ну хорошо, в английском нет падежей, но есть множественное число.

                  «ТТХ backend» я понял только потому, что являюсь программистом и лейтенантом РВиА одновременно :-)

                  И получается, вы боретесь за чистоту русского от английского, но в логах пишете «Ашипка».

                  Будьте проще :-) Как nginx :-)

                    0
                    Там подразумевалась картинка со сносками, но потом я её нечаянно удалил.
                  • UFO just landed and posted this here
                      0
                      А не дублируются ли эти if-ы?

                      if ($request_method = POST) {
                      set $cached 0;
                      }

                      if ($request_method != GET) {
                      set $cached 0;
                      }

                      Может стоит оставить только последний?
                        0
                        Не совсем дублируют :) там на самом деле на боевом ещё пара проверок, чтобы не запутаться. Хотя да, эта проверка лишняя.
                          0
                          Там ещё один косячек есть в конфиге, если кто не заметил
                          location = / {
                          return 404;
                          }

                          отдастся не из кэша, ну это на внимательность.
                        +1
                        в чём смысл
                        if ($request_method = POST) {
                        set $cached 0;
                        }
                        
                        if ($request_method != GET) {
                        set $cached 0;
                        }
                        
                          0
                          не в чем. ненужный оверхед.

                          начиная с 0.7.48 Исправление: теперь nginx кэширует только ответы на запросы GET.
                          так же нет смысла в proxy_hide_header «Set-Cookie»; nginx по умолчанию прячит этот заголовок.
                          0
                          а зачем
                            0
                            Сорри, не сюда=)
                            0
                            Для таких целей был и есть squid. Им прекрасно решались подобные задачи. Ну разве что теперь в большинстве случаев можно использовать один инструмент вместо нескольких.
                              0
                              Я сравнивал года полтора назад сквида с nginx, всё-же в пользу nginx:
                              0) он менее прожорлив к ресурсам;
                              1) иногда на фронтендах можно ченить запускать phpшное если сильно хочется;
                              2) не хочется городить огород из софта, чем проще конфигурация, тем надёжнее.
                              0
                              Ошибочка у Вас, помимо вышеописанных мной незначительных дополнений

                              В разделе отдачи сохраненной статики, отсутствует директива root (наследуется с предыдущего уровня, но это не поможет), соответственно все запросы уйдут на @fetch по 404

                              и сама метка с ошибкой. вероятно, имелось в виду "динамикустатику будем брать с frontend и если отсутствует, то скачивать"

                              так же опечатка в разделе сервер
                              location = /500.html {

                              а за труд респект

                              Only users with full accounts can post comments. Log in, please.