Балансировка статических файлов средствами nginx

    Представим, что у нас есть приложение/сайт с достаточно высокой нагрузкой.

    Многие разработчики приложений для «ВК» или «Одноклассников» сталкивались с ситуацией, когда приложение выходит в топ новых приложений и на вас сваливается огромнейшая нагрузка.
    Допустим, в процессе обращения клиента к серверу, генерируется картинка. Серверов у нас много. Каким образом клиенту отдать эту картинку, если у вас нет единой файловой системы и файлы не синхронизируются между серверами?

    Как поступить, когда на сервер ежесекундно приходит большое количество народу? Ответ прост — nginx.



    В этом топике я буду рассматривать балансировку как статических файлов, так и динамических.
    Сразу оговорюсь. Варианты балансировки запросов в сторону MySQL мы сейчас обсуждать не будем.
    То есть, грубо говоря, у нас есть сервера, на которых установлен одинаковый набор php скриптов, установлен и настроен php-fpm. Оба сервера отвечают на запросы клиентов и в конфигурацию DNS вписаны записи, по которым ваш домен отвечает по адресам 173.194.32.2 и 173.194.32.3.

    Итак, поехали.

    Настройки серверов



    Сервер 1:
    Внешний IP адрес: 173.194.32.2
    Внутренний IP адрес: 192.168.0.1

    Сервер 2:
    Внешний IP адрес: 173.194.32.3
    Внутренний IP адрес: 192.168.0.2

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


    Конфигурация nginx. Сервер 1:

    upstream nextserver {
                    server 192.168.0.2;
    }
    upstream backend {
                    server 127.0.0.1;
    }
    
    server {
            listen *:80;
            server_name 173.194.32.2;
    
            location / {
                    root         /var/www/default; # сайт у нас лежит по этому пути.
                    access_log   off; 
                    try_files $uri $uri/ @nextserver;
            }
    
            location @nextserver {
                    proxy_pass           http://nextserver;
                    proxy_connect_timeout      70;
                    proxy_send_timeout         90;
                    proxy_read_timeout         90;
            }
    
            location ~* \.(php5|php|phtml)$ {
                    proxy_pass  http://backend;
            }
    }
    


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

    upstream nextserver


    nextserver — список серверов для отдачи статических файлов
                    server 192.168.0.2;
    


    upstream backend


    backend — список серверов для отдачи динамического контента(например php)
                    server 127.0.0.1;
    


    Кстати, если у вам обязательно надо, чтобы запросы от клиента попадали на один и тот же сервер каждый раз, то в секцию upstream надо добавить директиву ip_hash;

    Location /


    Стандартная директива. Корневой каталог сервера задается директивой root
    root         /var/www/default;
    


    Отключаем access_log. При большой нагрузке запись access лога во первых даст дополнительную нагрузку на диски, во вторых — быстро забьет диск.
    access_log   off; 
    


    В этой строке происходит самое интересное :) Мы проверяем существование файла, потом существование директории. Если файл есть, то nginx отдает его клиенту. Если же файла/директории не существует, клиент перенаправляется на location @nextserver.
    try_files $uri $uri/ @nextserver;
    


    location @nextserver


    Тут все просто. При перенаправлении клиента на этот локейшен, мы перенаправляем запрос клиента посредством директивы proxy_pass на upstream nextserver. В данном случае это 192.168.0.2. Серверов в секции upstream может больше одного, поэтому при желании мы можем перенаправлять клиентов на несколько серверов.
    Если сервера два, то первый клиент попадет на первый сервер, а второй клиент — на второй.
    Кроме этого в данной секции происходит установка настроек перенаправления. В данном случае — тайм-ауты.

                 proxy_pass           http://nextserver;
                 proxy_connect_timeout      70;
                 proxy_send_timeout         90;
                 proxy_read_timeout         90;
    


    location ~* \.(php5|php|phtml)$


    При запросе файлов, которые имеют расширение php5, php, phtml — переадресовываем клиента обработчику — в данном случае на upstream backend

                 proxy_pass  http://backend;
    


    Осталось дело за малым — прописать еще одну секцию server, на этот раз сервер должен слушать адрес 127.0.0.1.
    По этому адресу мы должны будем получать подключения и отрабатывать их с помощью php-fpm.
    Секцию server прописываем по образу и подобию описанного выше, за исключением одной детали. Меняем секцию location ~* \.(php5|php|phtml)$ на указанную ниже.

    location ~* \.(php5|php|phtml)$ {
                fastcgi_pass        unix:/tmp/php-fpm.sock;
                fastcgi_index       index.php;
                fastcgi_param       SCRIPT_FILENAME  /var/www/default$fastcgi_script_name;
                include             fastcgi_params;
    }
    


    Описание данной секции подразумевает, что у вас установлен и запущен демон php-fpm и он слушает unix сокет по адресу /tmp/php-fpm.sock

    Второй сервер настраиваем по аналогии с первым. Меняем адрес в секции upstream nextserver на 192.168.0.1(то есть говорим серверу, что отправлять запросы по несуществующим файлам надо на сервер 1).
    Кроме этого меняем директиву server_name.

    Конфигурация сервера 2 должна выглядеть примерно так:
    upstream nextserver {
                    server 192.168.0.1;
    }
    upstream backend {
                    server 127.0.0.1;
    }
    
    server {
            listen *:80;
            server_name 173.194.32.3;
    
            location / {
                    root         /var/www/default; # сайт у нас лежит по этому пути.
                    access_log   off; 
                    try_files $uri $uri/ @nextserver;
            }
    
            location @nextserver {
                    proxy_pass           http://nextserver;
                    proxy_connect_timeout      70;
                    proxy_send_timeout         90;
                    proxy_read_timeout         90;
            }
    
            location ~* \.(php5|php|phtml)$ {
                    proxy_pass  http://backend;
            }
    }
    


    Таким образом, если клиент запрашивает картинку, которая не существует на одном сервере, будет произведена попытка поиска картинки на втором сервере.
    Единственная проблема в данном случае — если картинки нет на обоих серверах, то запросы будут передаваться по кругу от одного сервера, к другому до тех пор, пока не достигнет тайм-аута. В итоге будет возавращена 503 ошибка.

    Указанная в этой статье конфигурация nginx — упрощенная и может быть улучшена, в том числе могут быть решены проблемы с циклом при отсутствии запрашиваемого файла. Но это отдельный вопрос.
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

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

    Комментарии 19

      +13
      Т.е. судя по вашему конфигу сервера по 90 секунд будут долбиться к друг-другу? Ужас!
        0
        Это если файл будет отсутствовать на обоих серверах. Проще перечислить все сервера в одном upstream'е, и второй пометить как backup. Тогда nginx сначала будет искать на локальном, и только потом на удаленном. (maxfails должно быть равно 0)
          –5
          Согласен. Приведенный конфиг писался не с продакшен серверов и приведен просто как пример, что такие варианты возможны. Вариантов решения проблем, на самом деле, очень много :)
          • НЛО прилетело и опубликовало эту надпись здесь
      0
      Сталкивался и реализовал подобную задачу. В вашем варианте недостаток в том, что на перебор вариантов тоже тратится время. В очень загруженных проектах это может быть существенно.

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

      Другой вариант — использовать скрипт для определения местоположения файла и выдачи его через хидер 'X-Accel-Redirect'. В каких-то случаях тоже может подойти, но по ресурсам он, вероятно, будет похуже чем ваш вариант.
        0
        С определением местоположения по скриптам — будет одназначно затратнее, чем данным вариантом… С локейшеном — да, тоже вариант.

        Еще могу сказать, что указанный в статье вариант усложняет отлов багов.
          0
          Определение по скриптам будет затратнее вашего только при соответствующем количестве серверов. Если их 2-3, то, конечно, ваш вариант предпочтительнее. А если 50?
            0
            Если 50 серверов, то надо продумывать. Естественно это решение не подходит.
        0
        так же можно организовать CDN и все пути к картинкам давать относительно него, а он уже сам будет выплевывать правильный редирект.

        Таким образом, схема будет такая:

        клиент -> cdn -> отдача картинки с сервера
          +3
          отключение access.log при наличии в кампании отделов QA и Support — это, мягко выражаясь, — не правильно.
            +3
            Не очень вас понял.
            Если файлы статические — то положите их на оба фронта и все. И никаких проблем.

            У нас была похожая задача: возможно несколько миллионов вариантов картинок, какие будут заранее не известно. Решили, что будем генерировать по запросу и класть в кеш nginx на срок действия проекта. Сервера друг про друга ничего не знают и работают независимо — это сразу устраняет ряд проблем. Самый большой минус нашего решения — то, что картинка будет генерироваться дважды. Но это гораздо меньшее зло, чем долбежка по кругу.
              +2
              Еще вариант решения
              — вынести картинки на отдельный домен и ip
              — поставить squid-ы, настроить их общение по ICP. Теперь, если сквид может брать картинки их кеша другого сервера
              — profit!
                +1
                Проще статику синхронизировать с помощью lsyncd как в статье
                habrahabr.ru/post/132098/
                А балансировку между серверами делать с помощью ipvs, или с помощью ДНС на худой конец.
                  +3
                  ай яй яй вебсервер начинать с 192.168.0.1 :)
                    0
                    В последний раз делал проще домен files.site.com под файлы, site.com под сайт.
                    Дальше балансируем днсами и/или поддоменами.
                      0
                      Каким образом клиенту отдать эту картинку, если у вас нет единой файловой системы и файлы не синхронизируются между серверами?
                      Залить картинку на сервер вконтакта!

                      На счет динамики… Не знаю, о каких топовых приложениях вы говорите, но виджеты, которые имели 2кк уников в сутки, работали на 1м не очень мощном сервере, на котором хранились все картинки, JS'ки, php файлы, мускуль, и все работало как часики, обрабатывая тысячи заходов в минуту, проводя различные анализы с этими данными. В ВК, на данный момент, нет приложений (про игры молчу) с подобным хайлоадом (если, конечно, в топовых приложениях не говнокод внутри, который при меньшем дау творит больше ЛА).

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

                      Самое читаемое