MAATRIX / Блог / Балансировку нагрузки на сервере: частые ошибки и решения

Балансировку нагрузки на сервере: частые ошибки и решения

Балансировку нагрузки на сервере: частые ошибки и решения

MAATRIX

Балансировщик поднят, трафик делится между бэкендами — а пользователей разлогинивает, один сервер загружен вдвое сильнее других, и в пуле висит мёртвый узел, который всё ещё получает запросы. Настроить балансировку нагрузки на сервере несложно, а вот довести её до предсказуемой работы мешают типовые ошибки. Разберём их по схеме «симптом — причина — решение».

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Как диагностировать проблемы балансировки

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

log_format lb '$remote_addr -> $upstream_addr [$status] $request_time';
access_log /var/log/nginx/lb.log lb;

После перезагрузки Nginx смотрите, как раскидываются запросы:

tail -f /var/log/nginx/lb.log

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

for ip in 10.0.0.11 10.0.0.12 10.0.0.13; do curl -s -o /dev/null -w "$ip %{http_code}\n" http://$ip:3000; done

Эта пара приёмов отвечает на главный вопрос диагностики: сбоит балансировщик, конкретный бэкенд или само приложение. Дальше — разбор конкретных симптомов.

Пользователей постоянно разлогинивает

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

Есть два решения. Быстрое — привязать клиента к одному серверу методом ip_hash:

upstream backend {
    ip_hash;
    server 10.0.0.11:3000;
    server 10.0.0.12:3000;
}

Правильное и более надёжное — вынести сессии в общее хранилище, доступное всем бэкендам, например в Redis. Тогда любой сервер приложения знает о любом пользователе, и распределение остаётся равномерным. У ip_hash есть минус: пользователи за одним корпоративным NAT попадают на один бэкенд, перекашивая нагрузку, а при добавлении сервера привязки перестраиваются. Поэтому для серьёзных проектов общее хранилище сессий предпочтительнее.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Нагрузка распределяется неравномерно

Симптом: один бэкенд загружен под завязку, остальные почти простаивают. Причин несколько, и они складываются. Если вы используете ip_hash, а основная часть трафика идёт из-за нескольких крупных NAT, все эти пользователи оседают на одном-двух серверах — это врождённая особенность метода.

Если же перекос при round-robin, скорее всего дело в разной стоимости запросов: одни быстрые, другие висят секундами, и на «невезучем» сервере они накапливаются. Здесь помогает метод least_conn, который отдаёт запрос наименее занятому узлу:

upstream backend {
    least_conn;
    server 10.0.0.11:3000;
    server 10.0.0.12:3000;
    server 10.0.0.13:3000;
}

Ещё одна причина — разные по мощности серверы в пуле. Если один бэкенд слабее, равное распределение перегружает именно его. Задайте веса, отдав мощным машинам больше трафика через параметр weight. И проверьте очевидное: иногда «неравномерность» оказывается тем, что часть бэкендов просто выпала из ротации и весь поток лёг на оставшиеся.

Мёртвый бэкенд всё ещё в пуле

Опасный сценарий: сервер приложения упал, но балансировщик продолжает слать на него запросы, и часть пользователей ловит ошибки. Обычно это значит, что не настроена пассивная проверка здоровья — Nginx не знает, за сколько неудач исключать узел.

Добавьте параметры контроля сбоев каждому серверу:

server 10.0.0.11:3000 max_fails=3 fail_timeout=30s;
server 10.0.0.12:3000 max_fails=3 fail_timeout=30s;

Теперь после трёх неудачных попыток подряд бэкенд на 30 секунд выводится из пула, а затем Nginx осторожно пробует его снова. Важная тонкость: проверка пассивная, то есть узел признаётся мёртвым только когда на него уже пришёл и провалился запрос. Несколько пользователей всё же успеют получить ошибку, прежде чем сервер исключат. Чтобы ловить падения раньше, держите внешний мониторинг, который стучится на каждый бэкенд и оповещает вас до того, как проблему заметят пользователи.

Приложение видит IP балансировщика вместо клиента

После включения балансировки в логах приложения и в его логике безопасности у всех посетителей один адрес — адрес балансировщика. Ломается геолокация, ограничение по частоте запросов и бан по IP. Причина стандартная для любого прокси: не проброшены заголовки с реальным адресом клиента.

Добавьте их в блок проксирования:

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

Стоит отдельно понимать разницу между X-Real-IP и X-Forwarded-For. Первый содержит один адрес — того, кто соединился с балансировщиком. Второй накапливает цепочку адресов по мере прохождения через прокси, и настоящий клиент в ней стоит первым. Если перед вашим балансировщиком есть ещё и внешний CDN, реальный IP посетителя будет не в конце цепочки, а ближе к её началу, и приложение должно уметь это правильно разобрать. Неверная трактовка приводит к тому, что под баном оказывается адрес CDN, а не нарушителя.

Одного проброса мало — приложение должно доверять этим заголовкам. Во многих фреймворках для этого включают режим работы за доверенным прокси, иначе они из соображений безопасности игнорируют X-Forwarded-For. И будьте осторожны: доверять заголовку можно только когда балансировщик действительно ваш и стоит перед приложением, иначе клиент сможет подделать свой IP. Поэтому бэкенды должны принимать соединения только из приватной сети, а не из интернета напрямую.

Балансировщик сам стал узким местом

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

ss -s
top -b -n1 | head -n5

Если балансировщик спокоен, а тормозит всё сразу, узкое место — общий ресурс за бэкендами, чаще всего база данных. Добавлять серверы приложения бессмысленно, пока не расшита база: она обслуживает их всех. Это типичный потолок горизонтального масштабирования, и решается он оптимизацией запросов, кешированием и более мощным сервером под базу. Если сам балансировщик перегружен сетью или процессором, ему тоже нужен запас — здесь помогает узел с чистым IP, хорошим каналом и достаточными ресурсами. Такие VPS удобно держать в одной локации у MAATRIX с оплатой из России картой или криптой, собирая всю связку в одном месте и без трансграничных задержек между узлами.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Частые вопросы

Почему после включения балансировки пользователей разлогинивает?

Сессии хранятся в памяти отдельного бэкенда, а запросы уходят на разные узлы; вынесите сессии в общий Redis или используйте ip_hash.

Как заставить балансировщик перестать слать запросы на упавший сервер?

Задайте бэкендам max_fails и fail_timeout для пассивной проверки и держите внешний мониторинг для раннего оповещения.

Из-за чего нагрузка распределяется неравномерно?

Причина в разной стоимости запросов, весах серверов или в ip_hash за крупными NAT; часто помогает метод least_conn.

Почему приложение видит IP балансировщика вместо клиента?

Не проброшены или не приняты заголовки X-Forwarded-For; добавьте их и включите в приложении доверие к прокси.

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.