nginx + apache. Кеширование

    Привет, %username%
    Тут я хочу рассказать о том, как я настраивал кеширование на одном сервере, точнее VDS. Характеристики сервера: 2000MHz, 2GB RAM, 80Gb HDD, технология виртуализации — OpenVZ.
    Было решено использовать Nginx версии 0.7.64. На сервере находилось около 200 сайтов. И несколько высоко нагруженных проектов. Вот эти самые проекты и давали ощутимые тормоза и нагрузку на сервер. Мы будем рассматривать DLE в этом примере.
    Итак, основные моменты кеширования:
    • Кешировать «гостей» сайта
    • Залогиненных посетителей отправлять напрямую к апачу


    0. Придумываем «технологию»


    Если в урлах встречаются строки: index.php?action=logout и admin.php то мы будем посылать эти запросы напрямую к апачу. Потом, нам нужно научить nginx отделять залогиненых пользователей от гостей. Разделять мы их будем с помощью кукисов. В моем случае, Кукисы dle_user_id и dle_password означают, что пользователь залогинен и мы не хотим отдавать ему страничку 5-ти минутной давности. И плюс, нам нужно отправлять POST заброс на авторизацию вне кеша, я думаю понятно почему и зачем (:.

    1. Поехали. Пишем конфиг


    Собственно, я не буду описывать полный конфиг, я буду описывать только те моменты, которые необходимы для кеширования. Я не претендую на правильность и оптимальность моего решения, но то что здесь показано, работает, и неплохо работает в боевых условиях. Сильно не пинайте, я не так давно полез в дебри nginx'a, но чувствуется что документации и тех пример очень не хватает!
    Основные директивы конфига — кликабельны, ведут на оф документацию.

    1.1 Сам конфиг



    http {
    proxy_cache_path /var/cache/nginx/cache levels=1:2 keys_zone=one:16m inactive=7d max_size=1024m;
    proxy_temp_path /var/cache/nginx/temp; #эта директива будет наследоваться из http секции, если не задано другое.

    server {
    listen 127.0.0.1:80;
    server_name example.com www.example.com;
    proxy_temp_path /var/cache/nginx/example.com;

    location @nocached {
    proxy_pass 127.0.0.1:8080;
    proxy_redirect example.com:8080/ /;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Real-IP $remote_addr;
    }

    location / {
    proxy_pass 127.0.0.1:8080;
    proxy_redirect example.com:8080/ /;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Real-IP $remote_addr;
    #здесь мы фильтруем наших залогиненых пользователей
    if ($cookie_dle_user_id) { return 412; }
    if ($cookie_dle_password) { return 412; }
    if ($request_method = POST ) {
    return 412;
    }
    error_page 412 = @nocached;
    proxy_cache one;
    proxy_cache_key "$request_method|$is_args|$host|$request_uri";
    proxy_hide_header "Set-Cookie";
    proxy_ignore_headers "Cache-Control" "Expires";
    proxy_cache_valid 200 302 304 5m;
    proxy_cache_valid 301 1h;
    proxy_cache_valid 503 4s;
    proxy_cache_valid any 1m;
    proxy_cache_use_stale http_502 http_503 http_504;
    }

    location ~ (admin.php|index.php?action=logout) {
    proxy_pass 127.0.0.1:8080;
    proxy_redirect example.com:8080/ /;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Real-IP $remote_addr;
    }

    location ~* ^.+\.(jpg|jpeg|gif|png|svg|htm|js|css|mp3|ogg|mpe?g|avi|zip|gz|bz2?|rar)$ {
    root /var/www/example/data/www/example.com;
    expires 1y;
    access_log /var/www/httpd-logs/example.com.access.log;
    error_page 404 = @fallback;
    }
    location @fallback {
    proxy_pass 127.0.0.1:8080;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Real-IP $remote_addr;
    }
    }
    }


    $cookie_name, эта переменная равная cookie name;

    Собственно, далее будем разбирать, что же мы здесь намулевали. Предложения по улучшениям и правкам приветствуются!

    2. Разбор полетов



    Итак, нам необходимо было разграничить пользователей на 2 типа, и в зависимости от типа, давать или не давать ему кеш.
    В location @nocached мы будем пускать тех, кто является владельцем двух кук — dle_user_id, dle_password, следовательно эти пользователи будут у нас ходить напрямую, не кешируясь. Я уже писал, что для авторизации используются POST запросы, как и для регистрации ;), следовательно нам не нужно их кешировать. В этом случае, мы просто добавляем проверку на $request_method.
    error_page 412 = @nocached собственно и посылает обладателей кукисов, или кто пытается залогинится, напрямую к апачу.
    Хотелось бы отметить директиву proxy_cache_key. В ней, для ключа, я указываю лишь «уникальность» страницы, тоесть, если ктонибудь обратился к любой нашей странице «Гостем», то nginx, проксируя первый ответ, сразу кладет его в кеш. После этого, любой другой пользователь, который уложился в отведенное время жизни страницы (proxy_cache_valid 200… 5m) получает ту самую страницу, которую сгенерировал апач для предыдущего пользователя, так будет продолжаться, пока не кончится срок жизни, в нашем случае для ответа 200 выделяется — 5 минут. После этого все будет продолжаться по тому же самому сценарию.
    Дальше, я хотел бы отметить 2 строчки: proxy_hide_header, proxy_ignore_headers которые обязательны при кешировании. Вы ведь не хотите чтобы Вася Пупкин смог получить кукисы Пети Васечкиного, и тем самым делать от его имени… ??? Вторая строчка, proxy_ignore_headers Позволяет нам сохранять ответы апача, которые помечены, как не кешировать или еше чемнибудь в этом роде. Ведь даже для таких вещей нам придется дергать неповоротливый апач, когда можно один раз закешировать, и отдавать быстрым nginx'ом.
    Придавать особого внимания параметру proxy_cache_valid я не буду, все и так хорошо описано в документации.
    proxy_cache_use_stale очень правильная директива, она позволяет нам отдать клиенту закешированную «нормальную» страницу в то время, когда апач или повесился, или сдох или еще чего… 502, и 504 ошибки соответственно.
    В следущем location ~ (admin.php|index.php?action=logout) мы говорим что хотим отправлять запросы в которых содержаться строки напрямую к апачу.
    Два следущих location говорят nginx'у что статику (картинки таблицы стилей видюшечки и т.д.) отдавать самому. И если nginx не нашел такого файла на диске, то может быть он просто находится в .htaccess и апач нам его отдаст.

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

    P.S. Парсер съел мои http://, а nginx'у они нужны, как мне их восстановить? Иначе при копировании моего куска конфига у вас nginx может заругаться, а если нет, то уже не известно что будет.
    Кросспост

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 38

      +20
      У всех видимо новый год, а я тут такими делами занимаюсь… ух...)
        0
        Самое время — пока нагрузки минимальны :). А если по делу, то DLE наконец то научили работать без тяжеловесного и неповоротливого Apache ;)
          +2
          А он когда-то не умел? о_О
            0
            Это точно. Но когда на сервере куча сайтов и гребаные панели, то ты тут уж не причем, чо заказчик сазал, то ты и делаешь.
        • UFO just landed and posted this here
            –4
            Ну к примеру, логаут так не пашет. Ему нужно, что action=logout было напрямую и пост запроса там нет :(
            Ну и пусть будет на всякий случай :) хуже от этого не будет! :)
            • UFO just landed and posted this here
            0
            if ($request_method = POST ) — не нужно, пост не кешируется автоматически.
            Не нужно использовать старый nginx, лучше использовать новый nginx. Он лучше, в нем меньше багов и больше функционала.
            Конфиги заключайте в
            < code >, меня это спасало.
            <зануда> В конфиге куча повторяющихся мест. proxy_add_header лучше вынести на уровень server или вынести вместе с proxy_pass в отдельный файл, подключаемый через include. Получается намного удобнее, честно :) </зануда>
              –1
              Парсер — лох, съел < рrе > как тег :(
              Правильно — так:

              if ($request_method = POST ) — не нужно, пост не кешируется автоматически.
              Не нужно использовать старый nginx, лучше использовать новый nginx. Он лучше, в нем меньше багов и больше функционала.
              Конфиги заключайте в <рrе> < соdе >, меня это спасало.

              <зануда> В конфиге куча повторяющихся мест. proxy_add_header лучше вынести на уровень server или вынести вместе с proxy_pass в отдельный файл, подключаемый через include. Получается намного удобнее, честно :) </зануда>
                0
                Попводу Post не согласен, если бы не кеширвоалось то я бы не стал добавлять эту проверку, ибо почти все писал опытным путем.
                  +2
                  Это баг, от использования старого nginx :-P

                  sysoev.ru/nginx/changes.html — 0.7.48,
                  Исправление: теперь nginx кэширует только ответы на запросы GET.

                  К слову, в дальнейших версиях очень многое по кешированию было исправлено.
                    0
                    * например, 0.7.52
                    Исправление: корректная обработка метода HEAD при кэшировании.
                      +1
                      у всех прошу прошения, исползьовалась версия 0.7.64, всего в одном символе ошибся
                        +2
                        Тогда это баг :(
                        Когда вернусь с отдыха, попробую воспроизвести на последней версии.
                          +2
                          Не стоит надеятся тому, что обещают, лучше самому все сделать! из нать что все работает!
                  0
                  На сервер используется веб панель для работы с клиентами, она почти весь конфиг генерит. Вот и приходится…
                  0
                  Попробовал использовать
                   , не работает, он переносить на новую строку данные. 
                    0
                    В смысле? Пишите так:
                    <рrе>
                    server 
                    {
                        location /
                        {
                            proxy_pass http://ya.ru/;
                        }
                    }
                    
                    </рrе>
                      0
                      Хотелось бы сохранить ссылки. Видимо придется обойтись без ссылок :(
                  +1
                  я предпочитаю вообще апач не ставить на серверы…
                  nginx прекрасно и с php и с cgi скриптами справляется.
                  единственное неудобство для массового хостинга — несовместимость формата описания реврайтов и необходимость прописывать их напрямую в конфиг
                    +3
                    а в чём смысл
                    if ($cookie_dle_user_id) { return 412; }
                    if ($cookie_dle_password) { return 412; }

                    ?
                    достаточно dle_user_id смотреть, если кто-то куки выборочно удалял, то он сам себе злобный буратино
                      0
                      блог «как я настроил nginx на своём сервере».
                        0
                        Я все больше и больше утверждаюсь, что документации мало по nginx'y необходимо больше рабочих примеров его использования. Вот и поделился…
                        +4
                        А в чем фишка хостить 200+ сайтов на таком железе? Они что все абсолютно не коммерческие?
                          +2
                          У вас конфиг неправильный — http секция рано закрывается. Так работать не будет.
                          sysoev.ru/nginx/docs/http/ngx_http_core_module.html#server
                          Директива server должна быть внутри http.
                          Дальше не читал.
                            0
                            Спасибо за замечение, исправил!
                            +1
                            интересен расчет кеша в 5 минут…
                            тоисть зависимость длины кеша от количества сайтов.
                            получается если в 5 минут больше 300 запросов по всем сайтам — тогда кеш еффективен, если меньше — тогда он избыточен… (натянутый расчет, но в принципе правильный, если учесть лавинообразность обращений)

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

                            Почему спрашиваю? да потому, что множетсво кеширования делает сам dle — минуя запуск длинных скриптов — он отдает статические обьекты-страницы.

                            Случаются ли наложения кеширования CMS на кеширование на уровне nginx? если dle — 5 минут + nginx — 5 минут — ето уже 10, а 10 минут иногда могут тем же анонимусам причинить неудобство в виде пропажи комментария либо наличия на сайте старого контента…
                              +1
                              упс… увидел в конфиге «статику»
                                0
                                Кеширование включено только на одном сайте, ибо только один сайт дает почти все нагрзку на сервер. Вот именно для него и делалось кеширование.
                                  +1
                                  Там нет никаких обиженных анонимусов. На таких сайтах анонимусы — это поисковики и случайно забредшие по ошибке. Они просто закрывают браузер через 3 секунды.
                                    0
                                    ну в тексте ничего о «таких сайтах» не написано, соответственно предполагал все варианты…
                                  0
                                  Чёт не ясно зачем @fallback при наличии @nocached
                                    0
                                    @fallback генерируется ISPManager :(
                                    Вот и приходится оставлять куски чужих конфигов
                                    0
                                    Как быть с капчей на DLE?
                                    /engine/modules/antibot/antibot.php
                                    Как оставить ее без кеширования? Ибо она не меняется
                                      0
                                      Добавить location с правильным адресом, который будет обрабатываться на бэкенде, и не попадать в кеш
                                        0
                                        Я понимаю что нужно так добавить, если не затруднит напишите как.
                                        Нужно по крайней мере 2 адреса, это
                                        /index.php?do=register и /index.php?do=feedback
                                        Не кешировать только капчу по идее не выйдет. Я так понимаю гостю отдается кеш всей страницы?
                                        Конфиг выше действительно сильно снимает нагрузку отдавая гостям кеш, но те же гости испытывают трудности при регистрации
                                        0
                                        <a href="/antibot.php?__randomString__">капча</a>
                                        0
                                        то есть <img src="/antibot.php?__randomString__">

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