Pull to refresh

Балансировка в Angie

Level of difficultyMedium
Reading time20 min
Views3.5K

Сергей Каличев, старший разработчик, Angie Software

Бычок всерьёз занимается балансировкой
Бычок всерьёз занимается балансировкой

Статья посвящена обзору возможностей балансировки в трёх продуктах: Angie, Angie PRO, Angie Load Balancer в составе Angie ADC. Angie - бесплатный продукт с открытым исходным кодом, остальные два - коммерческие продукты. Некогда Angie был создан как форк NGINX.

Это первая статья из цикла статей, не знаю пока насколько длинного, о балансировке в Angie. В ней я постарался сделать обзор балансировки в целом, не касаясь конкретных балансировщиков и подробностей их конфигурирования. Вместо этого рассказываю, что между ними есть общего, принципы работы. Во второй статье планирую перечислить все балансировщики продуктов с описанием того, что они могут, для чего предназначены и некоторых их неявных особенностей. Подробностей конфигурирования по-прежнему не будет, это и так описано в документации. А тут хотелось бы скорее целостного обзора, нежели описания всех опций.

Зачем нужна балансировка

Место балансировщика в сети
Место балансировщика в сети

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

Увеличение совокупной производительность системы требуется тогда, когда один сервер не справляется с обслуживанием потока клиентских запросов. Имеются в виду сетевые запросы. Что именно это за запросы - не так важно. Это могут быть HTTP-запросы, запросы к базе данных, запросы к нестандартному сервису по специализированному протоколу. Главное тут то, что по какой-то причине, сервер не справляется с обработкой запросов. Это может быть вызвано разными причинами. Не справляется процессор, недостаточно памяти, низкая скорость диска или сетевых интерфейсов. При увеличении масштабов системы и количества клиентских запросов, в один прекрасный (или ужасный) момент сервер не сможет ответить всем, каким бы мощным он ни был. И тут возникает идея - а пусть на запросы отвечает не один сервер, а несколько. Их суммарная мощность кратно больше. Хорошо, но кто-то должен распределять клиентские запросы по серверам, сами себя они не распределят. Этим и занимаются балансировщики. Строго говоря, неким распределением могли бы заниматься клиентские хосты, например одна группа клиентов работает с сервером №1, вторая группа с сервером №2 и т.д., но дело в том, что во-первых оконечные хосты не обладают никакой статистикой по загруженности серверов, не могут "с умом" динамически перераспределить нагрузку, во-вторых нет единой точки входа для доступа к сервису, вместо этого множество адресов, отсюда сложности настройки, плохая масштабируемость, в третьих усложнение самого клиентского ПО.

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

Если один из серверов вышел из строя, то балансировщик больше не будет на него ничего направлять. Это к вопросу о второй важной функции балансировки - отказоустойчивости. Запросы перераспределятся по остальным серверам и клиент не заметит "подмены". Способ определения "жив" ли сервер - это отдельная тема, но если сервер вновь станет работоспособным, то балансировщик, узнав об этом, начнёт его учитывать, как возможного кандидата для обработки клиентских запросов. На картинке показана ситуация когда сервер "backend #3" стал недоступен. Клиентские запросы перераспределились на остальные сервера: "backend #1", "backend #2".

Вообще говоря, не все клиентские запросы одинаковы и не все обслуживающие сервера одинаковы. Например, в случае работы с базами данных, один сервер может быть доступен на запись, а остальные только на чтение. И, соответственно, специализированный балансировщик должен различать типы запросов (запись или чтение) и направлять нужному серверу. Если говорить про балансировку в Angie, то в данный момент поддерживается только такой режим работы, когда все сервера считаются равнозначными, с точки зрения функциональности. Клиенту всё равно, на какой сервер попадёт его запрос.

Уровни балансировки

А что мы вообще балансируем, т.е. что является единицей балансировки? Теоретически можно ведь балансировать побайтно - байт туда, байт сюда. А можно на уровне стран. Весь трафик из ЮАР на этот сервер, а из Бразилии - на другой. Существуют очень разные уровни балансировки. Angie имеет дело с уровнем "запросов" и выше.

Под запросом, в данном случае, подразумевается HTTP запрос, к примеру "дай мне содержимое страницы по такому-то адресу". В рамках одного клиентского соединения (соединение клиента и балансировщика) таких запросов может быть много. HTTP - протокол уровня L7 и балансировщик, работающий на этом уровне, разбирает поток данных на отдельные запросы. И запросы из одного соединения могут отправиться на обработку на разные серверы. А что умеет Angie на уровнях выше, чем запросы?

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

Балансировка на уровне соединений подразумевает, что выбор сервера происходит во время установки клиентского соединения. После того, как сервер выбран, балансировщик устанавливает серверное соединение, т.е. соединение с выбранным сервером. Клиентское и серверное соединение жёстко привязываются друг к другу. Считайте, что серверное соединение становится, как бы, продолжением клиентского. Существуют разные случаи когда требуется балансировка на уровне соединений и некоторые из них будут упомянуты в статье позже, но самый очевидный случай - это балансировка L4, т.е. когда мы не знаем ничего об используемом прикладном L7 протоколе и, соответственно, нет возможности разделить поток данных на запросы или ещё какие-то дробные составляющие.

Для балансировки может использоваться какая-либо информация о клиенте. Самое простое - это IP-адрес. Назовём такой уровень балансировки клиентским. Сюда, в том числе, входит пример про клиентов из Бразилии, т.е. балансировка по географическому признаку.

Модули-балансировщики

В исходном коде Angie балансировщики представлены в виде отдельных модулей. Модуль можно собрать, а можно и не собирать. Обязательным является только модуль "round_robin". Это базовый модуль для балансировки и он используется по-умолчанию, если не указано иное. В нем объявлены структуры и некоторые функции, общие для всех модулей-балансировщиков. Т.е. другие модули-балансировщики при работе используют эти структуры и могут вызывать функции, определенные в "round_robin". Вообще говоря, я немного преувеличил, написав, что модуль "round_robin" является обязательным к сборке. Теоретически, собраться можно и без него, но тогда придется писать свой собственный альтернативный "полный" балансировщик, который делает кучу "лишней" работы. Для чего это может понадобиться в реальной жизни - мне трудно представить. Все встроенные балансировщики Angie завязаны на "round_robin" и не будут работать без него.

В документации балансировщики выглядят не как отдельные модули, а как настройки более крупного модуля "upstream". Возможно так и удобнее для администраторов, т.к. именно секция конфига "upstream" содержит список обслуживающих серверов и прочие полезные настройки. Обслуживающие сервера будем называть upstream-серверами или backend-серверами. Множество upstream-серверов, заданных в пределах одной секции "upstream" образуют upstream-группу. В примере указывается группа из четырёх upstream-серверов и алгоритм балансировки "ip_hash".

upstream backend {
    server backend1.example.com weight=5;
    server 127.0.0.1:8080       max_fails=3 fail_timeout=30s;
    server unix:/tmp/backend3;
    server backup1.example.com  backup;

    ip_hash;
}

Вообще, если более строго и формально подходить к описанию, то можно сказать, что нет никаких балансировщиков, во множественном числе, есть один балансировщик и несколько алгоритмов балансировки. Собственно структура документации на это намекает. Но в этой статье будет удобнее говорить о "балансировщиках" имея в виду модули, реализующие тот или иной алгоритм балансировки. Я иду со стороны исходного кода, а тут это вполне себе полноценные модули, а не локальные и внутренние настройки модуля "upstream".

Для upstream-группы можно задать только один балансировщик. Вернее, действовать будет только один. Задать можно и больше, но использоваться будет тот, что определен ниже по тексту в конфиге. При этом в лог будет записано предупреждение "load balancing method redefined". Это, как бы, считается и не ошибка, но будьте осторожны, во избежание путаницы не стоит задавать больше одного балансировщика. Ну, и как я уже упоминал, если не указать никакой балансировщик, то будет использоваться "round_robin".

Angie содержит балансировщики для работы на двух уровнях - L7 и L4. Здесь на уровне L7 поддерживается протокол HTTP, настройка которого производится в секции "http". На уровне L4 поддерживаются TCP и UDP, работа с которыми контролируется в секции "stream". Модули-балансировщики для http и stream существуют отдельно, это не одни и те-же модули, хотя их набор, во многом, совпадает. Также есть некий вариант балансировки в подсистеме "mail", где сервер аутентификации, работающий по HTTP-протоколу, отвечает адресом, куда направить клиента, но это довольно вырожденный случай, потому что балансировщиком становится скорее сервер аутентификации, нежели Angie.

Детерминированный и вероятностный выбор сервера

Балансировщики делятся на два типа, по способу выбора upstream-сервера для обработки клиентского запроса - детерминированные и вероятностные.

Детерминированный или вероятностный?
Детерминированный или вероятностный выбор у ослика?

Детерминированный балансировщик, при одинаковых условиях, всегда выберет один и тот же upstream-сервер. Пример такого балансировщика - "ip_hash". Он хэширует три первых октета IP-адреса клиента и на этом основании выбирает upstream-сервер. Сколько бы раз ни пришел к нему клиентский запрос с определенного IP-адреса, столько раз будет выбран один и тот же upstream-сервер. Другой пример детерминированного балансировщика - "least_conn". Балансировщик выбирает тот сервер, который имеет наименьшее количество установленных соединений. Если у первого сервера соединений на одно меньше, чем у второго, то гарантированно выберется первый сервер (для особо внимательных уточню, что веса обоих серверов предполагаются равными).

С вероятностными балансировщиками - по-другому. Даже при одинаковых условиях нельзя точно сказать, какой upstream-сервер будет выбран в данный момент. Почему? Потому что решение принимается случайным образом на основании вероятностей. Тут примером может служить балансировщик "least_bandwidth", который собирает статистику по количеству траффика в байтах, прокачанных в единицу времени по направлению к upstream-серверу или от него. По сути, чем больше поток в байтах обрабатывает сервер, тем меньше мы его хотим нагружать. Тут для каждого сервера рассчитывается некое взвешенное среднее значение. Как именно оно рассчитывается, обсудим позже, сейчас главное то, что каждому серверу сопоставлено числовое значение и чем оно меньше, тем больше вероятность выбора upstream-сервера. А если единицу поделить на это взвешенное среднее, то, чем больше будет получившееся число, тем больше вероятность выбора. Бывают балансировщики, в которых изначально, чем число больше, тем и вероятность выбора больше. В общем, каждый сервер участвует в лотерее с вероятностью выигрыша пропорциональной его вкладу в виде взвешенного среднего или величине обратной взвешенному среднему, как в случае с "least_bandwidth".

А почему бы не сделать "least_bandwidth" детерминированным? Представим себе, что существует два upstream-сервера и настроен балансировщик "least_bandwidth" с параметром "downstream". Это означает, что считаться будет только трафик в направлении от upstream-сервера к клиенту. Приходит первый запрос и направляется, предположим, на сервер №1. Приходит ответ, считается трафик. В итоге у сервера №1 "большой" трафик, а у сервера №2 никакого трафика. А дальше нам прилетают три запроса подряд. При детерминированном распределении все три уходят на сервер №2, в то время, как сервер №1 не делает ничего. И так будет происходить, пока сервер №2 не ответит, чем породит трафик. После этого балансировщик увидит, что у сервера №2 большой трафик, а у сервера №1 трафика не было в последнее время. И тогда он с полной уверенностью в своей правоте отправит все новые запросы серверу №1. Получаются качели. То густо, то пусто. Вероятностный выбор позволяет этого избежать. Нагрузка серверов становится более сглаженная. А когда трафика очень много, ведь, собственно, для таких случаев и нужна балансировка, теория вероятности берёт своё и статистика выбора будет такая, как и предполагалось настройками.

А могли бы детерминированные балансировщики стать вероятностными? Некоторые - да, другие - нет. Балансировщик "ip_hash" не смог бы стать вероятностным, его суть в детерминированности. А вот "least_conn" мог бы. А нужно это ему? Не особо. Ведь он считает соединения, которые сам и устанавливает. Его подсчеты количества соединений актуальны в любой момент времени. При этом первая версия балансировщика "least_bandwidth" была детерминированной, а потом балансировщик стал вероятностным.

Знать о том, является ли ваш текущий балансировщик детерминированным или вероятностным, иногда полезно. Например можно долго смотреть в лог и удивляться, почему запрос ушел на этот сервер, хотя по всем параметрам должен был уйти на другой? А вот так фишка легла, балансировщик вероятностный. Он со временем исправится.

Математика вероятностного балансировщика

"Математика" - это в данном случае сильно сказано, но тем не менее...

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

Предположим у нас есть какое-либо измеренное мгновенное значение. И мы хотим его использовать для выбора upstream-сервера. Если просто сравнивать мгновенные значения, соответствующие upstream-серверам, то никак не будет учитываться история вопроса. Предположим наше значение - это количество байт, отправленных на сервер за последние 100 мс и это количество довольно небольшое. Мы посчитаем, что сервер не нагружен. А что если в предыдущие 100 мс на этот же сервер ушли огромные объемы данных и он до сих пор занят их обработкой? Как это учесть? Вот так:

average = average * factor + value * (1 - factor)

Где "average" - это взвешенное средние, то число, которое будет использовано для выбора upstream-сервера, "value" - текущее мгновенное значение. Параметр "factor" - это коэффициент сглаживания. Формула написана с расчетом на то, что "factor" меньше единицы и представляет собой долю от целого. Этот коэффициент можно задать в конфиге для любого вероятностного балансировщика "factor=процент", и задается он в процентах. Т.е. для подстановки в формулу надо его значение поделить на 100. Что он означает по смыслу? Он означает какой вклад в новое рассчитанное значение "average" дадут нам "исторические" данные. Если фактор очень большой, то "average" меняется медленно, так как будет преобладать "история", и наоборот, если фактор очень маленький, то "average" по большей части будет определяться мгновенным значением, а историю мы быстро забываем. Ещё можно сказать, что "factor" определяет то, как быстро надо реагировать на изменения условий. По умолчанию используется "factor=90". Не стоит ставить слишком маленькие значения, при этом пропадает сглаживание и возможны резкие скачки.

Ну и ещё раз про то, откуда берётся значение "value" для формулы. Если мы считаем, что большее число в измеренном значении должно давать большую вероятность выбора upstream-сервера, к которому оно относиться, то подставляем измеренное значение в формулу "как есть". Ну например, если измеренное значение - это количество свободных слотов в некой очереди на обработку на upstream-сервере. И наоборот, если меньшее значение должно давать большую вероятность выбора upstream-сервера, то подставляем в формулу обратную от измеренного значения величину, т.е. "1/m", где "m" - измеренная величина. Например, мы можем измерять время ответа upstream-сервера на запрос. Тут, как раз, чем время будет больше, тем загруженнее сервер, и тем меньше новых запросов надо на него направлять. Балансировщики, использующие обратные значения для подстановки в формулу, обычно имеют в своем названии префикс "least_", например балансировщик "least_time".

В вероятностных балансировщиках, использующих обратное значение измеренной величины, существует особое значение - "0". Например, балансировщик "feedback" ждет обратной связи от upstream-сервера с некой характеристикой. Например upstream-сервер меряет загруженность своих процессоров в процентах и отсылает значение балансировщику. Если сервер ничем не занят, то он честно пошлет "0". Действительно, что будет, если поделить единицу на ноль? Я раньше думал, что будет исключение, программа упадёт и прочие катастрофические последствия. Ан - нет. Большинство современных систем при делении на ноль возвращают "очень очень очень" большое число. Вот и балансировщики считают, что если надо делить на ноль, то получим очень большое число. Они выбирают это очень большое число так, чтобы оно было несоизмеримо больше любых реально измеренных значений, но при этом не вызвало бы переполнения при дальнейших расчетах. Какое это именно число - задумываться не надо. Главное, что при выборе upstream-сервера оно даст заведомо большую вероятность.

А что будет, если "feedback" балансировщик получил от одного сервера "0", а от второго "1" в качестве процентного показателя загруженности процессора. Казалось бы второй сервер не будет выбран никогда, так как полученное при делении на ноль "очень" большое число навсегда выдавит единицу из списков претендентов на выбор. Хотя, на самом деле, показатели загруженности по смыслу довольно близки. В этом случае балансировщики поступают следующим образом. Если вероятность выбора сервера составляет меньше одного процента, то шансы сервера искусственным образом дотягиваются до этого 1%. Точнее говоря, вероятность его выбора будет чуть меньше 1%, но не сильно. Важно, что, хоть и с небольшими шансами, но "отстающие" серверы продолжают участвовать в "розыгрыше".

Представим еще одну ситуацию. У нас есть десять upstream-серверов, все они работают уже неделю, каждый имеет ненулевое взвешенное среднее ("average") и тут в их группу добавляется новый сервер, не имеющий "истории". А у нас "factor" выставлен в 99%. Балансировщик рассчитывает новые значения "average" и видит, что появился "бездельник". Что он делает? Весь поток запросов перенаправляет на несчастный новый сервер. От чего тот приходит в полный ступор. Плохая ситуация. Чтобы такого не происходило, вероятностные балансировщики искусственно приписывают вновь прибывшим серверам значение "average" равное среднему "average" по всем другим upstream-серверам. Среднее от взвешенных средних значений... запутано. В общем, другими словами, приходя в группу, новый сервер становиться "середнячком" и никто его запросами выше меры не заваливает.

Производительность

Воркеры и разделяемая память
Воркеры и разделяемая память

Типичная конфигурация Angie - несколько, работающих параллельно, воркеров. Воркер - это рабочий процесс (worker process), который делает всю фактическую работу. Если совсем кратко, то есть мастер-процесс, он загружает конфигурацию и запускает воркеры, а сам ждет сигналов от пользователя и следит, чтобы воркеры не умирали. Подробнее об архитектуре существуют отдельные статьи, например "NGINX изнутри: рожден для производительности и масштабирования". А тут я упомянул об этом, в связи с тем, что воркеров может быть много. Как только их становится много, то появляется проблема взаимодействия друг с другом. Зачем им вообще взаимодействовать, применительно к задачам балансировки? Для обмена данными о состоянии upstream-серверов, для обмена статистикой, которая может использоваться в процессе выбора upstream-сервера. Ведь один запрос может обрабатывать первый воркер, а следующий запрос - второй воркер.

Воркеры обмениваются данными через область разделяемой памяти. При таком обмене нужны блокировки. В данном случае используются rw-блокировки. Ну т.е. на чтение сразу много процессов могут одновременно захватить блокировку, а вот на запись блокировка эксклюзивная - если кто-то пишет, то остальные не могут не читать, не писать. Некоторым балансировщикам для выбора upstream-сервера достаточно блокировки на чтение, другим недостаточно, и нужна write-блокировка. Грубо можно сказать так, балансировщики, которые основывают свой выбор upstream-сервера на данных клиента, например IP-адресе клиента, обходятся read-блокировкой, а балансировщики, использующие статистику или какую-либо обратную связь от upstream-серверов, зажимают write-блокировку.

Балансировщики, которым не нужна write-блокировка, в среднем работают быстрее. Потому что не мешают и не тормозят друг друга. Имеется в виду, что один и тот же алгоритм может работать одновременно в разных воркерах, на разных процессорных ядрах. Захватывая read-блокировку, они, по-сути, не догадываются, что кто-то другой её уже захватил. А те, кому нужна write-блокировка, должны иногда ждать друг друга. Отсюда дополнительные задержки.

На всякий случай обращаю внимание, что когда я пишу, что какие-то балансировщики работают быстрее, то я имею в виду непосредственно, и только, код самого балансировщика. Это абсолютно не означает, что вся система балансировки в целом будет работать быстрее или клиент получит ответ быстрее. Вполне возможна ситуация, что наилучшие результаты будут получены с использованием самого "медленного" балансировщика. Зачастую качество балансировки, т.е. правильного выбора upstream-сервера, гораздо важнее скорости кода балансировки.

Отказоустойчивость

В случае с Angie можно выделить два аспекта при обеспечении отказоустойчивости, или как это ещё называется, высокой доступности (High Availability). Во-первых, проверка работоспособности upstream-серверов. Во-вторых, backup-группы серверов.

Проверки работоспособности бывают двух типов - пассивные и активные. Пассивная проверка происходит "сама собой", когда клиентский запрос направляется на обработку upstream-серверу. Если с сервером не удается связаться, то балансировщик помечает его, как нерабочий и не отправляет на него новые запросы. Текущий запрос направляется на другой upstream-сервер. Через определенный тайм-аут, при поступлении нового клиентского запроса, балансировщик опять попробует связаться с нерабочим сервером.

Если настроены активные проверки, то балансировщик периодически сам соединяется с upstream-серверами и выясняет живы они или нет, независимо от того, есть ли сейчас клиентские запросы. Модуль активных проверок называется "upstream_probe" и доступен в составе коммерческих продуктов Angie.

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

В конфигурационном файле Angie задаются upstream-сервера на которые будут балансироваться клиентские запросы. Это основная группа upstream-серверов. Кроме этого, рядом с основной группой серверов может быть задана дополнительная группа серверов. Назовем её backup-группой серверов. Чтобы upstream-сервер попал в backup-группу вместо основной, директиве "server" нужно добавить опцию "backup". Итак, у нас есть две группы серверов, для чего они используются? Балансировщик пытается направить все клиентские запросы на сервера основной группы. Если хоть один сервер основной группы жив, то сервера backup-группы никак не используются и находятся в резерве. Если в основной группе не останется ни одного работоспособного сервера, то балансировщик начинает использовать сервера backup-группы. Однако, когда придет новый клиентский запрос, балансировщик опять ищет подходящий upstream-сервер, начиная с основной группы. Т.е. как только в основной группе снова появится хотя бы один живой сервер, backup-группа становится резервной, поиск среди её серверов не производится.

Для некоторых больших и сложных систем двух групп доступности, основной и backup, может быть недостаточно. Для этого в продукте Angie ADC в скором времени будет добавлена поддержка множественных backup-групп. Если не осталось серверов в основной группе, балансировщик ищет сервера в backup-группе уровня 1, если и в ней никого не осталось, то в backup-группе уровня 2, и так далее. Основная группа условно считается backup-группой уровня 0. Backup-уровень сервера задается через опцию "backup=уровень" директивы "server". Кроме этого поддерживается возможность получения серверов с разным backup-уровнем из SRV записи сервера доменных имен. У адресов в SRV записи есть поле приоритета (priority), оно используется для определения, в какую группу следует поместить сервер. Значения поля priority могут рассматриваться как для относительного определения backup-уровня, так и для абсолютного. Ещё из новшеств появится возможность переходить на backup-группу "навсегда" или на какое-то определённое время. Если указана директива "backup_switch permanent", то если балансировщик перешёл на резервную группу, то в ней он и останется. Т.е. не будет при каждом новом клиентском запросе проверять, а нет ли живых среди серверов с более низким backup-уровнем, начнёт искать с того уровня, где в последний раз нашёл подходящего кандидата. При этом через REST API можно будет узнать, какая backup-группа активна в данный момент, а в последствии будет добавлена возможность не только узнать, но и установить текущую активную группу.

Стандартизованные возможности балансировщиков

В Angie существует набор стандартизованных дополнительных возможностей, которые могут поддерживаться балансировщиками. Каждый балансировщик объявляет поддерживаемые возможности с помощью выставления соответствующих флагов. Расскажу о некоторых из них.

Возможность задавать "вес" upstream-сервера с помощью опции "weight" директивы "server". По умолчанию вес равен единице, но если он установлен в большее значение, то шансы сервера быть выбранным балансировщиком для обработки клиентского запроса возрастают кратно весу. Для вероятностных балансировщиков вес увеличивает вероятность выбора. Для детерминированных - кратно увеличивает, используемые для выбора, значения. Например, для детерминированного балансировщика "least_conn" гарантировано будет выбран сервер, у которого в полтора раза больше активных (т.е. используемых в данный момент) соединений, чем у соседнего, но при этом имеющий вес "2". Т.е. 1/c < (1/(c * 3/2) * 2), где "c" - количество активных соединений с соседним сервером. Балансировщик "least_conn" использует обратную количеству активных соединений величину, потому что, чем меньше соединений, тем сервер считается менее загруженным.

Возможность использовать backup-группы серверов, т.е. опцию "backup" директивы "server". Не все балансировщики это умеют в настоящее время. Считалось, что не для всех алгоритмов это нужно. Вероятно, некоторые неумеющие балансировщики в будущем "научатся" использовать backup-группы.

Возможность "медленного старта", опция "slow_start" директивы "server". Когда, после сбоя, сервер вновь вводится в строй, "slow_start" позволяет сделать это плавно. Шансы сервера быть выбранным для обработки клиентского запроса искусственным образом понижаются, а затем, в течении определённого времени, постепенно повышаются и доводятся до обычной величины.

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

Смежные модули

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

Модуль "queue" создаёт очередь запросов. Если балансировщик не смог выбрать upstream-сервер для обработки, то запрос попадает в очередь ожидания. Без этого модуля клиенту была бы сразу возвращена ошибка. Смысл в том, что если подходящий сервер не найден это не обязательно означает, что все сервера неработоспособны. Например может быть превышен предел на количество соединений с сервером и произойти это может при кратковременном всплеске нагрузки. В таком случае, после обработки одного из уже принятых в работу запросов, сервер "освободится", уменьшится количество соединений, и он сможет начать обработку запроса из очереди. Таким образом модуль "queue" может сглаживать всплески нагрузки. Естественно, если все сервера действительно стали неработоспособны или всплеск нагрузки оказался долговременным, то очередь переполнится и новые запросы будут отклонены, а клиенту будет возвращена ошибка. Так же ошибка будет возвращена, если запрос слишком долго находится в очереди ожидания. Модуль существует для HTTP подсистемы и доступен в составе коммерческих продуктов Angie.

Модуль "keepalive" позволяет держать открытые неактивные соединения с upstream-серверами, т.е. не закрывать соединения при завершении обработки запроса. Это делается для того, чтобы каждый раз, для каждого запроса, не устанавливать соединение заново. Новые запросы можно отправлять на upstream-сервер, используя уже установленное соединение. Максимальное количество одновременно открытых неактивных соединений можно указать опцией. Переиспользование уже установленных соединений позволяет сократить общее время обработки запросов. В одно серверное соединение могут попадать как разные запросы одного клиента, так и запросы другого клиента. В F5 BIG-IP аналогичная возможность называется OneConnect, если кому-то будет так понятнее о чём речь. Модуль существует для HTTP подсистемы и доступен во всех продуктах Angie.

Директива "bind_conn", реализованная в модуле "keepalive", позволяет "навсегда" привязать клиентское соединение к серверному соединению. В обычном режиме HTTP-запросы, приходящие по одному клиентскому соединению, могут направляться балансировщиком для обработки на разные upstream-сервера. Это, в принципе, хорошо, но бывают случаи, когда так делать нельзя. Например при NTLM-аутентификации аутентифицированным считается не запрос, а соединение в целом. Поэтому, если после NTLM-аутентификации сменится upstream-сервер или даже просто соединение с тем же сервером, то обращение будет считаться не аутентифицированным. Поэтому надо привязать клиентское соединение к одному серверному соединению и не менять его на протяжении всей жизни клиентского соединения. Должна быть зафиксирована однозначная связь. Т.е. запрос из данного клиентского соединения не должен попасть в какое либо другое серверное соединение - соединение будет неаутентифицированным, и наоборот, никакой запрос из другого клиентского соединения не должен попасть в аутентифицированное клиентом серверное соединение - нарушение безопасности. Директива "bind_conn" позволяет это сделать. Функциональность актуальна только для HTTP, так как при работе подсистемы Stream на уровне TCP соединений, мы не можем выделить из потока отдельные запросы и, соответственно, клиентское соединение и так закреплено за одним сервером и одним серверным соединением. Кроме NTLM-аутентификации, функциональность может быть полезна в случаях, когда на соединение завязан какой-то контекст, некая оптимизация на backend-е. Например backend работает в режиме форка (fork()), т.е. порождает отдельный процесс на каждое соединение и поэтому ему удобно обрабатывать запросы клиента в одном и том же соединении. Директива "bind_conn" доступна в составе коммерческих продуктов Angie.

Модуль "sticky" также позволяет привязывать клиентское соединение к upstream-серверу, но на основе информации, содержащейся в самом запросе. Например для направления запроса на определенный сервер может использоваться значение cookie с указанным именем. Тут есть привязка к upstream-серверу, но нет привязки к соединению. Модуль актуален и для HTTP и для Stream подсистемы. У него есть три режима работы. Два из них доступны в составе всех продуктов Angie, режим "learn" доступен только в составе коммерческих продуктов Angie.

Модуль "zone" обеспечивает доступ с использованием REST API. API позволяет получать набор метрик, относящихся, в том числе, и к балансировке. В составе коммерческих продуктов Angie REST API позволяет ещё и управлять некоторыми аспектами балансировки, добавлять и удалять upstream-сервера. Это видимая часть работы модуля. А невидимая и даже более важная - это обмен данными между воркерами через область разделяемой памяти и синхронизация. Воркеры обмениваются информацией о доступности upstream-серверов, их загруженности, статистикой. Без модуля "zone", каждый воркер бы работал независимо от других. Предположим, один выяснил, что сервер "упал", а остальные не в курсе и продолжают попытки соединиться с неработоспособным сервером. Оценка загруженности сервера или статистика - вообще не соответствуют действительности, т.к. каждый воркер видит только свою часть данных. Как только воркеров становится больше одного - для адекватной балансировки сразу требуется модуль "zone".

Модуль "upstream_probe" позволяет обеспечить динамические проверки upstream-серверов, но о нём уже было написано ранее.

На этот раз - всё. Спасибо за внимание!

Tags:
Hubs:
Total votes 27: ↑26 and ↓1+30
Comments18

Articles