Наверх ▲

Принципы балансировки

Алексей Бажин Алексей Бажин Директор по эксплуатации в Mail.Ru Group.
View more presentations from rit2010.

Алексей Бажин: Меня зовут Алексей Бажин, я главный системный администратор Mail.Ru. В своем докладе я хочу рассказать о принципах балансировки для внешних сервисов с неспециализированными клиентами. Простейший пример такого сервиса и клиента — это веб-сервер и браузер. Мы рассмотрим непосредственно принципы балансировки, их плюсы и минусы, немного поговорим об их реализациях и применимости к различным сервисам.

Допустим, у нас есть некоторый интернет-проект, веб-сервер которого пока что работает на одном сервере. Постепенно мы понимаем, что нас ограничивает производительность. Самое очевидное решение в данном случае — это обновление оборудования. Но если мы пойдем по этому пути, то через некоторое время обновлять оборудование будет просто-напросто не на что.

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

Теперь остро встает вопрос распределения нагрузки между серверами. На этом этапе мы и будем использовать балансировку. Кроме непосредственного распределения нагрузки мы хотим решить еще ряд задач.

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

Самый простой метод балансировки — это использование алгоритма DNS Round Robin. Суть его в том, что мы создаем несколько DNS-записей типа А для записи нашего домена на DNS-сервере. DNS-сервер выдает наши записи типа А в чередующемся циклическом порядке.

Обычно на клиентах решающее устройство (англ. resolver) запрограммировано таким образом, чтобы кэширующий DNS-сервер клиента не влиял на балансировку. К тому же сам клиент из полученных записей выбирает случайную. Соответственно, происходит соединение с нужным сервером.

Для реализации данного метода подойдет совершенно любой DNS-сервер. Допустим, это будет сервер Named - сервер доменных имен пакета BIND.

Плюсы метода
  • Он абсолютно не зависит от протокола высокого уровня. То есть для этого метода может использоваться любой протокол, где обращение к серверу идет по имени.
  • Метод не зависит от нагрузки на сервер. Благодаря тому, что есть кэширующие DNS-сервера, нам все равно, сколько у нас будет клиентов — хоть единицы, хоть миллионы.
  • Метод не требует связи между серверами. Поэтому он может использоваться как для локальной балансировки (это балансировка серверов внутри одного дата-центра, скажем), так и для глобальной балансировки, когда у нас есть несколько дата-центров, где сервера между собой почти никак не связаны.
  • Самый главный плюс метода — это низкая стоимость решения. Если у нас есть проект, домен, DNS-сервер, то нам нужно всего лишь добавить еще записей в DNS, чтобы перейти к этому методу балансировки.

Минусы метода

  • Сложно отключать серверы, которые не отвечают или вышли из строя. В DNS существует кэширование. Запись убрали, а клиенты перестанут пользоваться ей только спустя время, которое задается параметром TTL (Time To Live) в DNS-зоне.
    К тому же у некоторых провайдеров есть DNS-серверы, которые принудительно кэшируют записи на гораздо более долгое время. Мы сталкивались даже с ситуацией, когда запись была убрана из DNS, а клиенты по ней еще год продолжали заходить. 
  • Очень сложно распределять нагрузку между серверами в нужной пропорции. Единственный способ это сделать — предусмотреть для каждого сервера по несколько IP-адресов так, чтобы их количество было пропорционально той части нагрузки, которая должна на них идти. Это минус, так как IP-адресов у нас обычно не очень много.
  • Следующий минус — возможно неравномерное распределение нагрузки при использовании продуктов Microsoft. Дело в том, что в Windows 2008 и Windows Vista без пакетов обновления решающее устройство по умолчанию настроено таким образом, что из полученных от сервера записей выбирается не случайную запись, а та, которая кажется ближе по номерам сети к его собственному IP-адресу решающего устройства.

Если IP-адрес клиента 1.1.1.1, и есть серверы с IP-адресами 2.2.2.2 и 3.3.3.3, то будет выбран IP-адрес 2.2.2.2, так как он ближе. В Рунете сети обычно сконцентрированы в определенных местах. Наш проект предназначен для аудитории Рунета. Получается, что если мы используем DNS Round Robin, то все такие клиенты идут на один и тот же сервер. Это может давать значительную неравномерность при распределении нагрузки между серверами.

Это, конечно, было наиболее актуально около года назад. Сейчас Microsoft уже выпустила исправления. Но до сих пор встречается такой эффект.

Как мы это обнаружили? В одном из проектов, где использовалась  балансировка с помощью DNS Round Robin, была выявлена проблема на одном из балансируемых серверов. Как оказалось, все компьютеры с Vista из нашего офиса "выбирали" как раз этот сервер. Так мы постепенно поняли, что происходит и почему.

Как я уже говорил, на практике максимальное число IP-адресов, которые мы можем балансировать таким методом, оказывается ограниченным. В протоколе DNS в случае, если ответ превышает 512 байт, по протоколу IDP выдается отбивка о том, что нужен перезапрос по протоколу TCP.

Как оказалось, не на всех DNS-серверах открыты обращения по протоколу TCP от клиентов. Например, полтора года назад у «Стрима»  это было закрыто. Проблему со «Стримом» мы довольно оперативно решили, но она возникла не только у них. Более чем при 20 с небольшим записях DNS применять DNS Round Robin мы не смогли.

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

Самое очевидное решение, которое пришло в голову: используя короткое время хранения записи TTL, выдавать на каждый запрос только по одной записи, чередуя их необходимым образом. Буквально за вечер был написан модуль для DNS-сервера под названием PowerDNS, который и реализовал этот метод.

Плюсы метода

  • Мы решили проблему с неравномерным распределением нагрузки.
  • Мы можем использовать алгоритм Round Robin с весами в этом методе, выдавая нужный IP-адрес большее число раз.
  • Можем распределять нагрузку с нужными нам пропорциями.

Минус

Малое время хранения записей. Соответственно, большая нагрузка приходится на DNS-серверы. Мы установили для параметра TTL значение 1 минута. 

Существуют DNS-серверы, которые обслуживают большое число клиентов. В течение минуты, установленной для Time To Live они отдают один и тот же сервер всем клиентам. Из-за этого может быть неравномерность. Но, как показала практика, даже при 30 балансируемых серверах такой неравномерности не наблюдается.

Следующий метод балансировки — это балансировка на втором уровне стека протоколов. Здесь можно выделить два варианта: балансировка с использованием отдельного выделенного балансировщика и без.

В обоих случаях мы берем некоторый IP-адрес нашего сервиса. Его мы устанавливаем для всех наших серверов либо на OBEC, либо на другой специализированный интерфейс. Делается это для того, чтобы данные серверы могли принимать соединения на этот IP-адрес и отвечать с него, но не отвечали бы на ARP-запросы, относящиеся к этому адресу.

Как работает такая балансировка? На балансировщик, имеющий этот IP-адрес и отвечающий на ARP, приходит, допустим, первый пакет соединения. Мы определяем, что он первый. Нужным алгоритмом отправляем его на интересующий нас сервер, меняя MAC-адрес на место назначения (англ. destination). Записываем его в некоторую таблицу соединений.

Если у нас это не первый пакет, то мы просто смотрим по таблице соединений, каким сервером обрабатывается это соединение, и отправляем его туда.

Учитывая, что мы не модифицируем заголовки третьего уровня и выше, то ответы от серверов мы можем отправлять мимо балансировщика напрямую через Интернет клиенту (до нашего шлюза).

Самое распространенное сейчас решение из программных реализаций данного метода называется Linux Virtual Server. В URL-терминологии данный метод балансировки называется прямой маршрутизацией (англ. Direct Routing).

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

На серверах мы вычисляем некоторый HASH, скажем, от IP-адреса клиента. По его значению сервер определяет, должен ли он отвечать на эти запросы. Если HASH=0, то должен отвечать первый сервер. Он и отвечает. Остальные серверы "знают", что они не должны отвечать.

Плюсы метода
  • Независимость от протокола высокого уровня. Можно балансировать HTTP, FTP или SMTP - разницы не будет.
  • Есть метод балансировки без выделенного балансировщика. При небольшом количестве серверов это может быть актуально.
  • Есть возможность посылать ответы мимо балансировщика. Учитывая, что, например, в протоколе HTTP размер ответа обычно на порядок больше, чем размер запроса, то мы довольно сильно экономим на ресурсах.
  • Относительно малое потребление ресурсов.

Минус метода

Очевидный минус этого метода — то, что все серверы должны находиться в одном и том же сегменте сети. Необходима специфическая настройка серверов и сетевого оборудования. Поэтому этот метод не всегда удобен и применим.

Существует метод, очень похожий на предыдущий — это метод балансировки на третьем уровне стека протоколов, то есть на уровне протокола IP. В нем мы назначаем балансировщику тот же IP-адрес сервиса. Когда идет обращение на него, мы применяем так называемый Destination NAT, то есть подменяем IP-адрес назначения в пакете: IP-адреса текущего сервера меняется на выбранный по нужному алгоритму IP-адрес сервера, который будет обрабатывать запрос.

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

Реализаций этого метода тоже достаточно много. Самые простые — это все тот же Linux Virtual Server; можно использовать Iptables. Также существует множество аппаратных реализаций.

Это наиболее часто используемый метод балансировки у нас в Mail.Ru. Он не требует никакой дополнительной настройки сетевого оборудования или серверов. Для них этот метод абсолютно прозрачен. Для серверов все выглядит так, будто к ним обращается сам клиент.

Забыл упомянуть, что в качестве реализации этой схемы можно использовать кластер IP в файерволле Iptables для Linux.

Плюсы балансировки на третьем уровне
  • Независимость от протокола высокого уровня.
  • Абсолютная прозрачность для серверов.

Минус

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

Метод проксирования

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

Этот метод для сервера уже не прозрачен, поскольку он видит, что к нему обращается наш прокси. Для этого внутри протокола высокого уровня мы должны каким-то образом передать сведения о том, какой же клиент к нам обращается. Для протокола HTTP мы можем добавлять заголовок X-Real-IP с IP-адресом клиента.

Плюсы проксирования
  • Так как проксирование работает на уровне протокола, прокси-сервер может анализировать и изменять наши запросы и ответы. Это позволяет делать привязку клиента к серверу. Допустим, по значению определенной настройки cookie. Это может быть полезно, если мы используем локальное хранение сессии данного клиента на сервере.
  • Проксирование позволяет распределять разные типы запросов по разным серверам. Мы можем выделить отдельные серверы, которые будут "отдавать" статически генерируемую главную страницу, и делать это с гораздо меньшим временем отклика, чем серверы, которые обрабатывают "тяжелые" запросы.
  • Есть возможность модификации запроса-ответа. Там может выполняться добавление заголовка, например, перекодировка. Есть возможность кэширования ответов на нашем прокси-сервере.

Минусы

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

Балансировка с помощью редиректа

Еще один метод балансировки — балансировка редиректом. Редирект применим для довольно малого числа протоколов. К счастью, для HTTP он применим.

Есть некоторый балансировщик, который при обращении к нашему сервису (например, http://site.com) дает клиенту редирект на конкретный сервер (например, http://server2.site.com). В случае HTTP это будет выглядеть как "HTTP redirect 302", по-моему, код редиректа будет выглядеть как "временно перемещено" (англ. moved temporary).

Плюсы редиректа

  • Если запросы достаточно "тяжелые", то иногда имеет смысл использовать редирект даже для глобальной балансировки. У нас есть балансировщик, который с помощью редиректов отправляет запросы на обработку в разные дата-центры.
  • Метод также позволяет распределять разные типы запросов по разным серверам. Запросы вполне могут быть проанализированы.

Минусы редиректа

  • Он, как я уже сказал, применим к очень малому числу протоколов высокого уровня.
  • Для клиента на каждый запрос мы, получается, делаем два запроса. Один — к нашему редиректору, один — к серверу, который обрабатывает соединение. Это увеличивает время, через которое клиент получит окончательный ответ на свой запрос.

Из реализаций этого метода и предыдущего (проксирования) для HTTP можно применять всем известный веб-сервер nginx.

Алгоритмы распределения нагрузки между серверами

Расскажу еще о том, какие существуют алгоритмы распределения нагрузки между серверами.

Самый простой метод - высчитать хеш от некоторых параметров нашего соединения. Допустим, от IP-адреса клиента, его порта. По значению этого хеша пакеты отправляются  на нужный сервер. Минус этого алгоритма в том, что он должен быть довольно сложным, чтобы мы могли в случае исключения или включения серверов распределять нагрузку по другим.

Следующий метод — алгоритм Round Robin с весами (простой Round Robin не так интересен). Это некоторое чередование наших серверов, когда частота их появления пропорциональна весу, который задан для данного сервера.

Самый интересный, наверное, метод балансировки — когда к нам приходит соединение, мы его отправляем на сервер, который в данный момент обрабатывает наименьшее число соединений. Соответственно, есть вероятность, что он быстрее всего обработает наше текущее соединение.

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

В таком случае мы можем поставить два балансировщика и сделать между ними некоторые отдельные резервирования, - например, с помощью VRRP или CARP. Из реализаций под Linux: UCARP, Keeper AFD, HardBit. Нагрузку между ними мы можем балансировать с помощью DNS, а сами балансировщики будут распределять уже нагрузку между серверами.

Это схема, которая используется на абсолютном большинстве наших проектов. Она применима для малого числа серверов. Допустим, у нас есть 2 сервера. Мы можем в качестве балансировщиков использовать прокси, которые будут "висеть" прямо на тех же серверах и резервировать их CARP или VRRP. Также эта схема применима и для большого числа серверов, когда имеются аппаратные балансировщики, с которых запросы распределяются на большое количество серверов.

Есть еще одна интересная схема. Мы можем выделить отдачу статики на отдельные сервера, чтобы не пропускать ее через наш балансировщик. Обработка каждого соединения для него достаточно ресурсоемка. Статика обычно отдается достаточно легко, малым числом серверов.

С этими серверами обычно ничего не случается, кроме отказов в работе. Достаточно зарезервировать их каким-нибудь образом. Нам необязательно следить за их временем отклика. Так как это статика, скорее всего, все будет в порядке.

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

Несколько слов об аппаратных балансировщиках

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

Упомяну те, которые применялись или тестировались нами — это довольно старые CISCO CSS11503. Это некоторый аппаратный балансировщик с гигабитными портами, который позволяет делать балансировку на третьем уровне и глобальную балансировку с помощью DNS (в простом случае). У нас эти балансировщики до сих пор используются на не очень больших проектах.

Какие у них есть плюсы? Легко прогнозировать нагрузку, которую они могут "потянуть" по тому, что создаст проблемы раньше: пропускная способность сети либо загрузка ЦП. 

Более современное решение от CISCO — это модуль ACE для Catalyst 6500. Он рассчитан практически на все вышеперечисленные методы балансировки и имеет теоретическую пропускную способность 16 гигабит.

Но с ним появляется ряд проблем, которые мы с аппаратом CISCO не всегда решаем успешно. Довольно сложно спрогнозировать, например, какую нагрузку он сможет "потянуть" дополнительно.

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

Сейчас мы тестируем решение BigIP от F5. Оно очень гибкое. Имеет свой встроенный скриптовый язык, так что мы можем балансировать, как нам захочется. О нем уже говорили на прошлых конференциях.

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

На этом все. Я готов выслушать вопросы.

Вопросы и ответы

Вопрос:

— Здравствуйте. У вас было написано, что при отделении статики вы используете несколько серверов, резервируя их методом CARP.

Алексей Бажин:

— Да. И балансируя с помощью DNS.

Вопрос:

— Каким образом у вас контент... Дублируется, получается? Или как?

Алексей Бажин:

— Фактически - да. Всю скачку мы просто складываем на отдельные серверы и отдаем по отдельному доменному имени. Причем довольно полезно использовать имя из другого домена второго уровня. Это нужно, чтобы в запросы, которые идут туда, у нас не включались те же cookie, что идут на основной сервер. Чтобы размер запросов был меньше.

Вопрос:

— Все-таки хочется узнать, что с контентом. Каждый контент лежит на одном сервере? "Упадет" сервер — и Бог с ним. Или есть дублирование?

Алексей Бажин:

— Да, он дублируется. Разумеется.

Вопрос:

— Хранилища не пробовали использовать? Дороже получается?

Алексей Бажин:

— Естественно. Если сравнить, что мы поставили два супермикровских сервера, которые могут обслуживать, скажем, третью часть Mail.Ru. Их стоимость посравнению со стоимостью хранилища значительно меньше.

Вопрос:

— На Западе популярно использовать балансировщики для нагрузочного тестирования вживую. Вы не пробовали так?

Алексей Бажин:

— Я не совсем понял вопрос.

Вопрос:

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

Алексей Бажин:

— Да, мы используем такой метод иногда. Когда, допустим, приходит новое «железо», которое более производительно чем старое, мы таким образом можем посмотреть, сколько же оно вытянет.

Вопрос:

— Допустим, мы Round Robin’ом раскидываем на два IP: один-второй, один-второй, один-второй... Один сервер упал. Клиент попадает на сервер, заходит на сайт — получает ответ. Нажимает F5 — попадает на неправильный сервер, не получает ответ. F5 — на правильный, F5 — неправильный. Как с этим бороться?

Алексей Бажин:

— Как я уже рассказывал, можно использовать некоторые методы резервирования. Такие, как по протоколам CARP или VRRP. Для Linux — UCARP, Keeper AFD, HardBit.

Вопрос:

— По NAT-балансировке вы говорили про использование Iptables. Вы пробовали этот вариант?

Алексей Бажин:

— Да, пробовали. Но на практике мы его не используем, так как там очень сложно отключать "упавшие" серверы. Нужно что-то отдельно скриптить. Только поэтому.

Вопрос:

— Там проблема, например, с контракт-таблицей...

Алексей Бажин:

— Размер контракт-таблицы можно задать. Это раз. Во-вторых, NAT-программы для очень больших нагрузок использовать не рекомендуется.

Вопрос:

— Если выбирать аппаратный балансировщик, то что порекомендуете?

Алексей Бажин:

— Смотря какие нагрузки. Если до гигабита, то нас устраивает CISCO CSS. Если нужны большие нагрузки, то мы пока еще сами в поисках. В принципе, тот же CISCO CSS работает. Но есть нюансы.

Вопрос:

— Второй вопрос по поводу DNS-балансировки. У вас есть какие-то данные по поводу количества тех провайдеров, которые принудительно кэшируют записи? Это 1%, 2%, 5%?

Алексей Бажин:

— Скажем так. По истечении времени хранения записи (TTL) 90% нагрузки уже идет куда нужно. Спустя сутки это уже, наверное, 98%. Оставшиеся 2% могут приходить довольно долгое время, как я уже говорил. Какая-то нагрузка видна даже спустя год бывает.

Вопрос:

— Какой метод посоветуете для глобальной балансировки с условием необходимости наличия привязки к сессии?

Алексей Бажин:

— Для глобальной балансировки очень сложно, точнее, не очень правильно делать привязку к сессии. Объясню, почему... В глобальной балансировке можно использовать HTTP-редирект. Тогда мы видим клиента, который к нам обращается, и можем его перекидывать, куда нужно. Либо мы можем считать, что у нас каждый клиент приходит только с одного DNS-сервера. Соответственно, привязываться к этому. Но я бы не рекомендовал так делать.

Вопрос:

—Вам не кажется, что с точки зрения большой компании реализовывать все вручную через CARP и тому подобное достаточно накладно? Проще использовать какие-то решения от вендоров типа F5, CISCO. С точки зрения менеджмента, как вы считаете?

Что имею в виду? Есть одна большая «железка». На ней, грубо говоря, один человек все раскидывает. Я правильно понимаю, что в случае, если мы пользуемся какими-то другими решениями, о которых вы рассказали в первой части, это будет достаточно большое количество узлов, зависимостей, человеческих ресурсов и рисков. Здесь можете пояснить?

Алексей Бажин:

— На самом деле, это так. Но учтем, что для нас в одну «железку» уже все не влезает. То есть у нас этих «железок» тоже получается немало. Это раз.

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

Комментарии

Нет ни одного комментария

Только пользователи могут оставлять комментарии

Возможно, вам будет интересно:

Хейки Линнакангас привез из Финляндии сокровенную информацию о том, что творится в самом сердце ядра PostgreSQL.

Антон Немцев

Антон Немцев

Независимый frontend-разработчик c 14-летним стажем, создатель Frontender Magazine, докладчик на WSD, представитель ВСТ в Украине.

Доклад с примерами использования 3D/2D/анимации с помощью CSS для создания эмоционального дизайна.

Алексей Юрченко

Алексей Юрченко

Работает в области репликации баз данных с 2003 года.

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