Балансировка: round-robin, weight и least_conn

Балансировка нагрузки — это искусство равномерно раздать работу нескольким бэкендам, чтобы ни один не задохнулся, пока другой скучает.
«Round-robin честно раздаёт по очереди. least_conn умнее: отдаёт тому, кто меньше всех занят прямо сейчас.»

Когда в upstream несколько серверов, Nginx распределяет между ними запросы. Это и есть балансировка нагрузки. Метод задаётся внутри блока upstream; по умолчанию — round-robin.

Round-robin: по кругу

upstream backend {
    server 10.0.0.1;
    server 10.0.0.2;
    server 10.0.0.3;
}
# запросы идут по очереди: 1, 2, 3, 1, 2, 3, ...
   запрос 1 -> сервер A
   запрос 2 -> сервер B
   запрос 3 -> сервер C
   запрос 4 -> сервер A   (круг замкнулся)

Веса серверов

upstream backend {
    server 10.0.0.1 weight=3;   # получит втрое больше запросов
    server 10.0.0.2 weight=1;
}

Веса полезны, когда серверы разной мощности: мощному даём больший вес.

least_conn: наименее загруженному

upstream backend {
    least_conn;
    server 10.0.0.1;
    server 10.0.0.2;
}

При least_conn очередной запрос уходит серверу с наименьшим числом активных соединений. Это спасает, когда запросы разной длительности: round-robin может свалить тяжёлый отчёт на и без того занятый сервер, а least_conn выберет свободный.

Смоделируем round-robin с весами на Python

servers = [("A", 3), ("B", 1), ("C", 1)]   # (имя, вес)

# Разворачиваем веса в плоский список слотов
slots = []
for name, w in servers:
    slots.extend([name] * w)

print("Слоты round-robin:", slots)
idx = 0
for request in range(1, 11):
    chosen = slots[idx % len(slots)]
    print(f"запрос {request:2} -> сервер {chosen}")
    idx += 1

Попробуй сам ▶ Видно, что сервер A с весом 3 получает примерно втрое больше запросов, чем B и C. Реальный Nginx использует «сглаженный» взвешенный round-robin, чтобы не отдавать A три раза подряд, но пропорция та же.

Как работает под капотом

Round-robin в Nginx — это сглаженный взвешенный алгоритм: он не просто крутит список, а на каждом шаге выбирает сервер с наибольшим «текущим весом», затем уменьшает его. Так запросы к серверу с весом 3 распределяются равномерно во времени, а не пачкой. least_conn же на каждом запросе смотрит счётчики активных соединений и берёт минимальный (с поправкой на веса).

Частые ошибки

  • round-robin при сильно разной длительности запросов. Долгие запросы накапливаются на «невезучих» серверах; используй least_conn.
  • Равные веса на неравном железе. Слабый сервер захлёбывается, мощный простаивает — расставь веса.
  • Думать, что least_conn учитывает загрузку CPU. Он считает соединения, а не процессор; это приближение, а не точная метрика.

Best practices

  • Stateless-сервисы с похожими по времени запросами — round-robin.
  • Запросы разной длительности (отчёты, загрузки) — least_conn.
  • Разнородное железо — расставь weight пропорционально мощности.

Когда балансировки на Nginx достаточно, а когда нет

Nginx как балансировщик 7-го уровня (он понимает HTTP) прекрасно подходит для типичной задачи: распределить веб-трафик между несколькими копиями приложения на одной или нескольких машинах. Для подавляющего большинства проектов этого достаточно, и городить отдельный балансировщик не нужно. Но полезно понимать границы. Если нагрузка такова, что сам Nginx становится узким местом или единой точкой отказа, перед ним ставят балансировку 4-го уровня (по TCP/IP) — например, облачный L4-балансировщик распределяет трафик уже между несколькими Nginx, а каждый из них балансирует на бэкенды. Получается двухуровневая схема.

Выбор метода тоже зависит от природы трафика, и здесь нет «лучшего» варианта на все случаи. Round-robin идеален, когда запросы примерно одинаковы по стоимости — типичный REST API с короткими ответами. least_conn выигрывает, когда время обработки скачет: одни запросы отдаются мгновенно, другие генерируют тяжёлый отчёт минуту. Тогда round-robin может свалить новый тяжёлый запрос на сервер, который и так молотит долгий, а least_conn отдаст его свободному. На практике least_conn — разумный выбор по умолчанию для неоднородной нагрузки, и многие команды ставят именно его, не задумываясь. Веса же подключают, когда машины в пуле объективно разной мощности — старый сервер и новый рядом. Понимание этих компромиссов позволяет настроить балансировку под реальный профиль нагрузки, а не наугад.

Итоги

Round-robin (по умолчанию) раздаёт запросы по кругу, веса корректируют доли под мощность серверов, а least_conn отдаёт запрос наименее занятому — это лучше при разной длительности обработки. Дальше — sticky-сессии через ip_hash и проверки здоровья бэкендов.

Проверьте себя
1. Когда least_conn предпочтительнее round-robin?
AВсегда
BКогда длительность запросов сильно различается — least_conn отдаёт запрос наименее занятому серверу
CТолько для статики
DКогда сервер один
2. Что делает параметр `weight=3` у сервера в upstream?
AУскоряет его в 3 раза
BДаёт ему примерно втрое больше запросов относительно сервера с weight=1
CСтавит таймаут 3 секунды
DОткрывает 3 соединения