Балансировка нагрузки в Nginx: upstream-блоки, алгоритмы и проверка доступности бэкендов

Балансировка нагрузки в Nginx реализуется через директиву upstream, которая описывает группу бэкенд-серверов. Запросы распределяются между ними согласно выбранному алгоритму, а при отказе одного из серверов трафик автоматически перенаправляется на оставшиеся.

Базовая структура upstream-блока​


Группа серверов объявляется директивой upstream в контексте http. Внутри перечисляются бэкенды с помощью директивы server. Адрес можно задать доменным именем, IP-адресом с необязательным портом или путём к UNIX-сокету с префиксом unix:. Если порт не указан, используется порт 80.

NGINX:
upstream backend {
    server backend1.example.com weight=5;
    server backend2.example.com:8080;
    server unix:/tmp/backend3;
    server backup1.example.com:8080 backup;
    server backup2.example.com:8080 backup;
}

server {
    location / {
        proxy_pass http://backend;
    }
}

Серверы с параметром backup получают трафик только тогда, когда все основные серверы недоступны. Доменное имя, которому соответствует несколько IP-адресов, автоматически раскладывается на несколько серверов.

Алгоритмы балансировки​


Round-robin (по умолчанию)​


Если ни одна директива балансировки не указана, Nginx распределяет запросы циклически с учётом весов. В примере выше каждые 7 запросов распределятся так: 5 на backend1.example.com, по одному на backend2.example.com:8080 и unix:/tmp/backend3.

При ошибке обращения к серверу запрос передаётся следующему, и так до тех пор, пока не будут опробованы все работающие серверы. Если ни один не ответил успешно, клиенту возвращается результат последнего.

least_conn​


Запрос передаётся серверу с наименьшим числом активных соединений, с учётом весов. Если таких серверов несколько — между ними применяется round-robin.

NGINX:
upstream backend {
    least_conn;
    server backend1.example.com;
    server backend2.example.com;
    server backend3.example.com;
}

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

ip_hash​


Ключом хэширования служат первые три октета IPv4-адреса клиента или весь IPv6-адрес. Запросы одного клиента всегда попадают на один и тот же сервер. Если сервер становится недоступным, запросы перенаправляются на другой, но с высокой вероятностью это будет один и тот же сервер.

NGINX:
upstream backend {
    ip_hash;
    server backend1.example.com;
    server backend2.example.com;
    server backend3.example.com down;
    server backend4.example.com;
}

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

hash​


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

NGINX:
upstream backend {
    hash $request_uri consistent;
    server backend1.example.com;
    server backend2.example.com;
}

Параметр consistent включает консистентное хэширование (алгоритм ketama). При добавлении или удалении сервера перераспределяется минимальное число ключей, что критично для кэширующих бэкендов: процент попаданий в кэш остаётся высоким.

Без consistent любое изменение состава группы приводит к перераспределению большинства ключей.

least_time (коммерческая подписка)​


Запрос передаётся серверу с наименьшим средним временем ответа и числом активных соединений. Параметр header учитывает время получения заголовка ответа, last_byte — время получения всего ответа, inflight (с версии 1.11.6) — также незавершённые запросы.

random (с версии 1.15.1)​


Запрос передаётся случайно выбранному серверу с учётом весов. С параметром two Nginx выбирает два случайных сервера, а затем применяет к ним указанный метод (по умолчанию least_conn).

NGINX:
upstream backend {
    random two least_time=last_byte;
    server backend1.example.com;
    server backend2.example.com;
    server backend3.example.com;
}

Параметры доступности: max_fails и fail_timeout​


Директива server принимает два ключевых параметра, управляющих пассивной проверкой здоровья:

ПараметрНазначениеЗначение по умолчанию
max_failsЧисло неудачных попыток за период fail_timeout, после которых сервер считается недоступным1
fail_timeoutОкно времени для подсчёта неудач и длительность, на которую сервер помечается недоступным10s

NGINX:
upstream backend {
    server backend1.example.com max_fails=3 fail_timeout=30s;
    server backend2.example.com max_fails=3 fail_timeout=30s;
}

Это пассивная проверка: Nginx не отправляет специальных health-check-запросов, а реагирует на реальные ошибки при обработке клиентских запросов. Активные периодические проверки (health_check) доступны только в коммерческой версии (Nginx Plus).

Keepalive-соединения к бэкендам​


По умолчанию Nginx открывает новое соединение к бэкенду на каждый запрос. Директива keepalive внутри upstream включает пул постоянных соединений:

NGINX:
upstream http_backend {
    server 127.0.0.1:8080;
    keepalive 16;
}

server {
    location /http/ {
        proxy_pass http://http_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

Начиная с версии 1.29.7 кэш соединений задействован по умолчанию, proxy_http_version уже равен 1.1 и заголовок Connection очищается автоматически. Для более ранних версий обе строки в location обязательны.

Для FastCGI-бэкендов дополнительно нужна директива fastcgi_keep_conn on:

NGINX:
upstream fastcgi_backend {
    server 127.0.0.1:9000;
    keepalive 8;
}

server {
    location /fastcgi/ {
        fastcgi_pass fastcgi_backend;
        fastcgi_keep_conn on;
    }
}

Дополнительные параметры пула (с версии 1.15.3):

  • keepalive_requests — максимальное число запросов по одному соединению до его закрытия. Слишком большое значение ведёт к росту потребления памяти.
  • keepalive_time (с 1.19.10) — максимальное время жизни соединения.
  • keepalive_timeout — таймаут неактивности, после которого соединение закрывается.

Sticky-сессии (коммерческая подписка)​


Для привязки клиента к конкретному серверу на уровне HTTP-куки используется директива sticky:

NGINX:
upstream backend {
    server backend1.example.com;
    server backend2.example.com;
    sticky cookie srv_id expires=1h domain=.example.com path=/;
}

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

Динамическая переконфигурация (коммерческая подписка)​


Директива zone создаёт область разделяемой памяти для хранения конфигурации и состояния группы:

NGINX:
upstream dynamic {
    zone upstream_dynamic 64k;
    server backend1.example.com weight=5;
    server backend2.example.com:8080 fail_timeout=5s slow_start=30s;
    server 192.0.2.1 max_fails=3;
    server backend3.example.com resolve;
    server backup1.example.com:8080 backup;
}

С zone состав группы можно менять через API без перезагрузки Nginx. Параметр resolve требует директиву resolver в блоке http или upstream и позволяет автоматически обновлять адреса по DNS.

Директива state сохраняет текущий состав группы в файл, чтобы изменения пережили перезагрузку:

NGINX:
state /var/lib/nginx/state/servers.conf;

Файл читается при парсинге конфигурации и обновляется при каждом изменении группы. Редактировать его вручную не рекомендуется.

Очередь запросов (коммерческая подписка)​


Если в момент обработки запроса подходящий сервер не найден, запрос помещается в очередь. Директива queue задаёт максимальный размер очереди и таймаут:

NGINX:
upstream backend {
    queue 100 timeout=30s;
    server backend1.example.com;
    server backend2.example.com;
}

При переполнении очереди или истечении таймаута клиент получает ошибку 502.

Типичные ошибки при настройке​


Отсутствие proxy_http_version 1.1 и очистки Connection при использовании keepalive (версии до 1.29.7). Без этих директив пул соединений не работает: каждый запрос открывает новое TCP-соединение.

Удаление сервера из ip_hash-группы вместо пометки down. Удаление меняет хэш-кольцо и перераспределяет клиентов. Параметр down сохраняет текущее распределение.

Слишком большой keepalive_requests. Соединения накапливают память; периодическое закрытие необходимо. Значение по умолчанию обычно достаточно.

Ожидание активной проверки здоровья в open-source версии. max_fails/fail_timeout — пассивная проверка. Если на бэкенд нет трафика, его недоступность не будет обнаружена. Для активных проверок нужен Nginx Plus или внешний health-checker.

hash без consistent при частых изменениях состава группы. Каждое добавление или удаление сервера перераспределяет большинство ключей, что приводит к массовому промаху кэша.

Проверка конфигурации и диагностика​


Перед применением изменений проверяйте синтаксис:

Bash:
nginx -t

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

Bash:
nginx -s reload

Убедиться, что балансировка работает, можно по логам доступа. Добавьте переменную $upstream_addr в формат лога:

NGINX:
log_format upstream_log '$remote_addr -> $upstream_addr [$time_local] '
                        '"$request" $status';

access_log /var/log/nginx/upstream.log upstream_log;

После нескольких запросов в логе будет видно, на какие бэкенды уходят запросы и как они распределяются.

Для диагностики на уровне соединений полезен stub_status (open-source) или API-модуль (коммерческая версия), которые показывают текущие активные соединения и запросы.

Источники​


 
Назад
Верх Низ