Балансировка нагрузки#

Angie распределяет клиентские запросы по группе проксируемых серверов, описанной блоком upstream. Какой именно сервер обработает конкретный запрос, определяет метод балансировки, заданный для группы.

Это руководство описывает балансировку HTTP, которая настраивается в модуле HTTP Upstream. Балансировка TCP- и UDP-сессий устроена так же и настраивается в модуле Stream Upstream; различия отмечены там, где они существенны.

Для группы действует ровно один метод балансировки. Если в блоке задано несколько методов, применяется последний из них, а в лог записывается предупреждение load balancing method redefined; если не задано ни одного, используется Взвешенный round-robin.

Выбор метода#

Метод определяет принцип, по которому Angie выбирает сервер для запроса, и каждый метод принимает свой набор параметров директивы server. Неподдерживаемый параметр не игнорируется: Angie отказывается запускаться и сообщает balancing method does not support parameter. Сверьтесь с таблицей, прежде чем сочетать метод с параметрами weight, slow_start или backup.

Метод

Распределяет запросы по

Уровень

weight

slow_start

backup

Взвешенный round-robin

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

L4, L7

hash

Хэшу произвольного ключа, например URI или заголовка.

L4, L7

ip_hash

Хэшу адреса клиента.

L7

least_conn

Наименьшему числу активных соединений на единицу веса.

L4, L7

random

Случайному выбору с учетом веса.

L4, L7

least_time

Вероятности, обратно пропорциональной среднему времени ответа.

L4, L7

feedback

Вероятности, пропорциональной значению, которое сообщают сами серверы.

L4, L7

Все перечисленные методы доступны в Angie ADC. У ip_hash нет аналога в Stream; используйте вместо него hash с переменной $remote_addr.

В Angie ADC дополнительно доступны методы, распределяющие нагрузку по объему передаваемого трафика:

Метод

Распределяет запросы по

Уровень

weight

slow_start

backup

least_bandwidth

Вероятности, обратно пропорциональной используемой полосе пропускания.

L4, L7

least_packets

Вероятности, обратно пропорциональной числу пакетов в единицу времени. Только для TCP и UDP.

L4

Методы балансировки#

Взвешенный round-robin#

Round-robin — метод по умолчанию: Angie применяет его, если для группы не задан другой метод, и отдельной директивы для него нет. Серверы группы обходятся по кругу, и каждому достается доля цикла пропорционально его весу weight:

upstream backend {
    zone backend 1m;
    server backend1.example.com weight=5;
    server backend2.example.com;
    server backend3.example.com;
}

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

Round-robin служит также запасным вариантом для других методов. Если hash, ip_hash или random не могут выбрать работоспособный сервер, выбор передается round-robin.

hash#

Метод hash направляет все запросы с одинаковым хэшем ключа на один и тот же сервер. Это удобно, когда серверы группы сами кэшируют ответы, например, если это кэширующие прокси или узлы memcached. Пока состав группы не меняется, запросы к одному URI приходят на один и тот же сервер, и ответ кэшируется только на нем, а не на каждом сервере группы.

upstream backend {
    zone backend 1m;
    hash $request_uri consistent;
    server backend1.example.com;
    server backend2.example.com;
}

Ключом может быть любая строка с переменными. Вес сервера увеличивает долю пространства ключей, которая ему достается.

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

Примечание

Позиции сервера на кольце вычисляются из имени, записанного в директиве server, а не из адресов, в которые это имя разрешается. Все адреса одного имени делят эти позиции, поэтому такое имя занимает на кольце столько же места, сколько один сервер того же веса. Когда ключ попадает на позицию имени, Angie выбирает один из его адресов по принципу round-robin.

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

ip_hash#

Метод ip_hash использует в качестве ключа адрес клиента: первые три октета IPv4-адреса или IPv6-адрес целиком. Запросы одного клиента попадают на один и тот же сервер, пока тот доступен.

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

upstream backend {
    zone backend 1m;
    ip_hash;
    server backend1.example.com;
    server backend2.example.com down;
    server backend3.example.com;
}

least_conn#

Метод least_conn выбирает сервер с наименьшим числом активных соединений на единицу веса — он минимизирует отношение числа соединений к весу, а не само число. Поэтому сервер с weight=2 и 15 соединениями предпочтительнее сервера с weight=1 и 10 соединениями. Если несколько серверов равны по этому показателю, они перебираются взвешенным round-robin.

Учитываются только соединения, по которым сейчас передается запрос. Простаивающие keepalive-соединения не учитываются.

Метод уместен, когда запросы сильно различаются тем, как долго они занимают сервер: скачивание больших файлов, long polling, WebSocket. Round-robin отправляет серверам одинаковое с учетом веса число запросов, но оно не отражает нагрузку, если одни запросы занимают сервер на миллисекунды, а другие на минуты; least_conn учитывает запросы, которые сервер обрабатывает в данный момент. Если же серверы различаются скоростью, подойдет least_time.

random#

Метод random выбирает сервер случайно, с вероятностью, пропорциональной весу. На большом числе запросов распределение соответствует весам.

Параметр two выбирает два случайных сервера, а затем из них — тот, у которого меньше активных соединений на единицу веса:

upstream backend {
    zone backend 1m;
    random two;
    server backend1.example.com;
    server backend2.example.com;
    server backend3.example.com;
}

Это важно, когда перед одними и теми же серверами работает несколько балансировщиков, не разделяющих состояние. Если каждый из них выбирает единственный наименее загруженный сервер, все они нагружают одну и ту же цель; выбор лучшего из двух случайных кандидатов распределяет нагрузку. Для одного балансировщика проще использовать least_conn.

Примечание

Запись random two least_conn принимается для совместимости и работает точно так же, как random two: сравнение активных соединений — единственный режим параметра two.

least_time#

Метод least_time отдает предпочтение серверам, которые отвечают быстрее. Что именно измеряется, задает режим: header — время до получения заголовка ответа, last_byte — время до получения ответа целиком.

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

upstream backend {
    zone backend 1m;
    least_time header;
    server backend1.example.com;
    server backend2.example.com;
}

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

Перед выбором этого метода стоит учесть два его свойства.

Он не выбирает просто самый быстрый сервер. Обратно пропорциональной среднему времени ответа является вероятность выбора сервера. Даже стабильно медленный сервер сохраняет небольшую долю — примерно один процент от того, что получает средний сервер, то есть около одного запроса из 100 x N для группы из N серверов. Это сделано намеренно: сервер, не получающий запросов, никогда не дал бы нового измерения, и его восстановление невозможно было бы обнаружить.

Он отвергает параметр weight. Группа, в которой least_time сочетается с weight= хотя бы у одного сервера, не запустится. Время ответа уже отражает то, сколько нагрузки принимает сервер, так что весу нечего добавить.

В Stream метод least_time принимает другой набор режимов: connect (время установки соединения с сервером), first_byte (время до получения первого байта данных от сервера) и last_byte (время до завершения сессии); режима header там нет.

feedback#

Метод feedback позволяет самим серверам сообщать, сколько работы они готовы принять. Angie извлекает значение из каждого ответа — как правило, из заголовка — и выделяет серверу долю запросов пропорционально этому значению, усредняя его так же, как least_time усредняет время ответа.

upstream backend {
    zone backend 1m;
    feedback $upstream_http_x_capacity;
    server backend1.example.com;
    server backend2.example.com;
}

Предупреждение

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

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

least_bandwidth#

Метод least_bandwidth распределяет запросы по объему трафика, реально переданного за единицу времени. Режим обязателен, значения по умолчанию нет: upstream учитывает трафик от клиента к серверу, downstream — от сервера к клиенту, both — их сумму.

upstream backend {
    zone backend 1m;
    least_bandwidth both;
    server backend1.example.com;
    server backend2.example.com;
}

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

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

Веса серверов и доли трафика#

Параметр weight означает разное для разных методов, и лишь у части из них доля трафика сервера равна его весу, деленному на сумму весов:

Метод

Что делает weight

round-robin, random

Задает долю напрямую: она равна весу, деленному на суммарный вес группы.

hash, ip_hash

Задает долю пространства ключей. Трафик соответствует весам, только если сами ключи распределены равномерно; реальные адреса клиентов и URI, как правило, распределены иначе.

least_conn

Делит сравниваемое число соединений. Сервер с большим весом доводится до пропорционально большего числа одновременных соединений, а не до пропорционально большего числа запросов.

least_time

Ничего — параметр отвергается при запуске.

feedback

Умножает значение, сообщенное сервером. Вес — лишь один из двух сомножителей, поэтому долю нельзя предсказать по одному только весу.

Как только в игру вступают max_conns, slow_start, max_fails, down или резервные серверы, ни один метод не распределяет трафик строго по весам.

Отказы и восстановление#

Пассивные проверки#

Angie судит о сервере по результатам отправленных на него запросов. Если результат попытки допускает повтор и лимит попыток не исчерпан, Angie может передать запрос другому серверу. Условия повторной попытки задают директива proxy_next_upstream и ее аналоги, а число и продолжительность попыток ограничивают proxy_next_upstream_tries и proxy_next_upstream_timeout.

max_fails неудачных попыток в течение fail_timeout помечают сервер недоступным на тот же период, после чего он проверяется снова. По умолчанию это одна попытка и десять секунд. Что считается неудачной попыткой, определяют директива proxy_next_upstream и ее аналоги.

Значение max_fails=0 отключает проверку для этого сервера: он никогда не будет помечен недоступным.

Примечание

Отдельные неудачные попытки, не достигающие порога max_fails, все же стоят серверу части его доли запросов, которую он возвращает себе не сразу. Без slow_start у сервера снижается эффективный вес, который учитывают round-robin и least_conn; сервер отыгрывает его обратно на последующих запросах. Со slow_start каждая такая неудача отбрасывает сервер назад по кривой разгона, которую учитывают все методы, принимающие этот параметр; здесь возврат к полному весу идет по времени, а не по числу запросов. В обоих случаях нужно, чтобы max_fails был больше единицы: при значении по умолчанию, равном единице, первая же неудачная попытка сразу помечает сервер недоступным, и промежуточного состояния не возникает.

Активные проверки#

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

Активные проверки обращаются к серверам по расписанию, независимо от клиентского трафика, и замечают как отказ, так и восстановление, не расходуя на это запросы клиентов.

Проверку задает директива upstream_probe в том же блоке location, где запросы передаются в группу; самой группе нужна зона разделяемой памяти:

upstream backend {
    zone backend 1m;
    server backend1.example.com;
    server backend2.example.com;
}

map $upstream_status $good {
    200     "1";
}

server {
    listen 80;

    location / {
        proxy_pass http://backend;
        upstream_probe backend_probe uri=/health interval=5s
                       test=$good fails=3 passes=2;
    }
}

Каждые пять секунд Angie запрашивает /health у каждого сервера группы. Три неудачные проверки подряд помечают сервер неработающим, две успешные подряд возвращают его в работу. Без параметра test проверка считается успешной по тем же правилам, что и клиентский запрос в этом location: по умолчанию proxy_next_upstream считает неудачей только ошибку и таймаут, поэтому ответ 500 или 503 проверку не проваливает. Здесь test=$good засчитывает проверку, только если сервер ответил кодом 200: для любого другого кода map оставляет переменную пустой.

Ответы проверок могут также служить данными для метода feedback через его параметр account.

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

Резервные серверы#

Серверы, помеченные параметром backup, находятся в резерве:

upstream backend {
    zone backend 1m;
    server backend1.example.com;
    server backend2.example.com;

    server backup1.example.com backup;
}

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

Директива backup_switch с параметром permanent меняет это поведение: она продолжает направлять запросы в группу, обслужившую их последней. Без указания времени переключение не отменяется само; с параметром permanent=время Angie по истечении этого интервала отправляет один запрос в основную группу, чтобы проверить, восстановилась ли она.

Параметр backup отвергается методами hash, ip_hash и random.

В Angie ADC резервных групп может быть несколько: параметр backup=уровень задает номер уровня, и группы перебираются по возрастанию уровня.

Кроме того, внутри одной группы часть серверов можно объявить запасными, указав параметр spare:

upstream backend {
    zone backend 1m;
    server backend1.example.com weight=2;
    server backend2.example.com;
    server spare1.example.com spare;
}

Запасной сервер простаивает, пока суммарный вес доступных основных серверов группы равен весу, заданному для них в конфигурации (здесь — трем). Когда этот вес падает, Angie ADC вводит в работу столько запасных серверов, сколько нужно, чтобы его восполнить, начиная с сервера с наибольшим весом. После восстановления основных серверов лишние запасные возвращаются в режим ожидания, начиная с сервера с наименьшим весом. Здесь отказ backend2.example.com вводит в работу spare1.example.com, и группа сохраняет прежний суммарный вес.

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

Возврат сервера в работу#

Параметр slow_start возвращает восстановившийся сервер к его полному весу постепенно, а не сразу: это защищает от полной нагрузки сервер с холодным кэшем или только что запущенный процесс:

server backend1.example.com slow_start=30s;

Чаще всего параметр срабатывает после того, как сервер был помечен недоступным и восстановился; кроме того, по той же кривой сервер откатывается назад после отдельных неудачных попыток (см. Отказы и восстановление). Действует он только с методами, отмеченными в колонке slow_start в разделе Выбор метода. В отличие от некоторых других реализаций, зона разделяемой памяти (zone) для него не требуется.

Чтобы вывести сервер из балансировки намеренно, используйте параметр down; чтобы прекратить направлять на него новый трафик, дав завершиться уже привязанным сессиям, — параметр drain.

Общее состояние рабочих процессов#

Балансировка согласована в пределах экземпляра Angie только тогда, когда рабочие процессы разделяют общее представление о группе. Его дает директива zone:

upstream backend {
    zone backend 1m;
    server backend1.example.com;
    server backend2.example.com;
}

Без нее каждый рабочий процесс хранит собственную копию состояния группы — счетчики отказов, число активных соединений, позицию в цикле round-robin, средние времена ответа — и балансирует независимо от остальных. При N рабочих процессах вы фактически получаете N балансировщиков над одними и теми же серверами: один процесс может считать сервер недоступным, пока остальные продолжают слать на него запросы, а ограничение max_conns действует в пределах процесса, допуская в N раз больше соединений, чем задано.

Зона также делает группу видимой для метрик upstream-групп и необходима для параметров resolve и service директивы server.

Примечание

Перезагрузка конфигурации создает зону заново независимо от того, изменилось ли значение size: счетчики, состояние пассивных проверок и накопленная история балансировки начинаются с нуля. Переносятся адреса, в которые разрешились серверы с параметром resolve, а также признак работоспособности, на который опираются активные проверки с параметром persistent (см. Активные проверки).

Привязка сессий и смежные механизмы#

Балансировка определяет, куда уйдет запрос. Две директивы ограничивают это решение:

sticky

Привязывает клиентскую сессию к серверу, который обслужил ее первым; сессия опознается по cookie, по значению маршрута или — в режиме learn — по значению, которое Angie видит в трафике. Пока сессия жива, привязка имеет приоритет над методом балансировки.

queue

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

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

Предупреждение

Директивы sticky, keepalive и queue должны следовать после директивы, задающей метод балансировки, иначе они не работают.