Как мы сделали ровную балансировку нагрузки на фронтенд-кластере

    Юрий Насретдинов ( youROCK, Badoo)


    Юрий Насретдинов

    Расшифровка доклада Юрия Насретдинова на конференции HighLoad++ 2015. Юрий расскажет про то, как Badoo (крупнейшая социальная сеть для знакомств с новыми людьми) сделали практически идеально ровную балансировку нагрузки на нашем кластере. Передаём ему слово...

    Чтобы обслуживать то количество пользователей, то количество реквестов в секунду, которые мы получаем, у нас около трех тысяч серверов, и на PHP-FPM конкретно приходится 70 тысяч в пике.

    О том, как мы эти запросы распределяем по нашему кластеру, я расскажу.



    Кратко — о чем я буду рассказывать. Во-первых, я расскажу, как «с высоты птичьего полета» устроен роутинг запросов на нашем сайте. Потом расскажу, какие, вообще, существуют алгоритмы балансировки; про то, как мы делали балансировку до того, как мы сделали автоматическую систему. Расскажу про эту автоматическую систему, ну и будут кое-какие выводы. Также хотел сказать, что эта система будет выложена в open-source после этого доклада.


    Давайте начнем с архитектуры.



    На этом слайде изображен путь, который проходит запрос, когда вы набираете badoo.com и, соответственно, попадаете на наш сайт. Для начала, поскольку у нас больше, чем один дата-центр, есть система от компании «F5», которая называется «Global Traffic Manager». Она умеет для разных континентов резолвить DNS-запросы на соответствующий дата-центр. Потом, когда IP-адрес уже получен, трафик приходит на систему «Local Traffic Manager» той же самой компании. Это просто «железка», которая умеет принимать на себя весь трафик и распределять по тем правилам, которые вы задаете, распределяете его по разным кластерам. И у нас есть два вида трафика — это мобильный трафик и трафик на веб-сайтах. Для мобильного трафика у нас есть еще отдельный прокси, который принимает соединения от мобильных клиентов и держит их. Он не показан на слайде, но именно он общается с Nginx, который в свою очередь раздает запросы на PHP-FPM. Для веб-сайта LTM умеет балансировать такие запросы напрямую.



    Таким образом, все это обслуживается либо PHP-FPM, либо в случае веб — это Nginx + PHP-FPM.

    Зачем, вообще, нужна балансировка и что это такое?



    Если количество запросов у вас на сайт больше, чем может выдержать один сервер, то, в принципе, вам нужна балансировка нагрузки для того, чтобы распределить трафик по нескольким серверам. И также, в принципе, достаточно очевидно, но это все-таки стоит отметить, что чем равномернее распределение нагрузки по тем серверам, которые стоят в бэкенде, тем меньше вам серверов нужно, чтобы обслуживать пики трафика, и тем лучше User Experience, потому что, как правило, время ответа зависит от загрузки сервера, и она тем выше, чем меньше сервера загружены.

    Какие, вообще, существуют алгоритмы?



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

    Первый вид балансировки я отнес к категории «тупых».



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

    Далее, весьма простой алгоритм — это Sticky sessions, когда запросы с одного и того же IP-адреса или же от одного и того же клиента попадают на один и тот же сервер. Это простой в реализации алгоритм и, в принципе, у него есть большое количество плюсов, особенно если ваше приложение не на PHP. Но у его также есть много подводных камней, о которых я сейчас рассказывать не хотел бы, это тема отдельного доклада.

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



    И, что более распространенно, это алгоритм Round-robin. Этот алгоритм более сложный, чем случайная балансировка, но обладает самым главным достоинством по сравнению с ней — это равномерность нагрузки, т.е. если у вас одинаковые сервера и у вас одинаковые запросы, то вы с помощью этого алгоритма получите идеально ровную балансировку. Т.е. этот алгоритм заключается в том, что вы посылаете каждый следующий запрос на следующий сервер, и таким образом достаточно легко показать, что загрузка будет равномерной, не будет никаких всплесков.

    Но жизнь немного сложнее и на практике такой алгоритм используют, но с некоторой модификацией.



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



    Группа алгоритмов, которые, грубо говоря, я отнес к категории «умных», которые каким-то образом должны следить за состоянием системы и балансировать нагрузку, исходя из этого. Например, очень распространенный алгоритм, который много где используется — Least Connections, когда ваш балансировщик отдает следующие запросы на сервера, с которыми установлено меньше всего соединений. Это тоже позволяет достичь равномерной нагрузки, потому что предполагается, что пока соединение открыто, то реквест обрабатывается.

    Поскольку сервера неодинаковые, есть модификация этого алгоритма, которая учитывает веса — Weighted Least Connections.

    Взвешенный Round-robin предполагает статические веса для серверов, которые задаются один раз и не меняются после этого. Но, строго говоря, вы можете их менять со временем. И это то, о чем я расскажу дальше.

    Есть еще некоторые другие алгоритмы, которые тоже можно упомянуть.



    Как мы распределяли нагрузку до того, как мы написали автоматическую систему? Это делалось руками администраторов — грубо говоря, приходит системный администратор, добавляет 10, 20, сколько-то серверов на наш кластер и «на глаз» смотрит, какой вес ему присвоить на основании частоты процессора, количества ядер, таймингов памяти и т.д. Он редактирует конфиг, который выглядит в Nginx примерно таким образом:



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



    Для разных серверов загрузка процессора показана разным цветом. Можно видеть, что линии разного цвета находятся строго друг над другом. Т.е. загрузка стабильная, но у разных серверов она отличается довольно существенно. Отличается где-то на 20-30%.



    Вы, наверное, спросите: а почему админы не могли затюнить веса руками? На самом деле тут причин очень много. Основная причина — это то, что очень большое количество времени проходит между внесением изменений и получением какого-то результата. Т.е. вы можете выровнять каждую линию отдельно, но вам нужно будет сделать 200 итераций и обновление не стоит делать слишком часто, например, мы делаем не чаще, чем раз в 15 минут. В общем, это очень большое усилие и ручная работа, которую не очень хотелось бы делать. Поэтому мы решили сделать по-другому.



    Когда на нашем сайте начал расти трафик, особенно на мобильном кластере, мы решили сделать автоматическую балансировку, которая позволила, помимо выравнивания нагрузки, иметь меньшее количество серверов для того, чтобы обслуживать трафик в пике, и чтобы не упираться в 100% процессора, а то и желательно держать загрузку меньше, чем 75% (условно).



    Я, опять же, выделил два основных подхода к тому, чтобы делать подбор весов для балансировщика:

    1. Это высчитывание какого-то индекса производительности и задание статического веса серверам;
    2. И противоположность первому — на основании состояния системы оценивать, какие веса нужно дать, и соответственно выдавать веса в процессе работы, наблюдая за тем, как она себя ведет.



    На этих двух графиках я попытался изобразить проблемы с первым подходом. На левом графике изображена дневная производительность машины, т.е. мы написали скрипт, который прогоняет некоторое количество циклов нашего приложения и измеряет, сколько их было за единицу времени (за одну секунду), и это гоняется на одном ядре. Можно видеть, что производительность как таковая, по крайней мере, для одного ядра, зависит от загрузки на системе, и это очень странно. Например, на процессорах AMD вы такого не увидите, но мы используем процессоры Intel, в которые включен hyper-threading. На слайдах не очень хорошо видно, но проседание производительности начинается где-то после 50 %, при этом сервер продолжает обслуживать все увеличивающуюся нагрузку и делает это вполне успешно, и hyper-threading в нашем случае очень хорошо работает, мы его оставляем включенным. Т.е. я хотел сказать, что выдать какой-то один вес на самом деле довольно проблематично, потому что производительность машины меняется со временем.



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

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



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

    Напомню, как выглядел график (правда, 3-хмесячный, у нас, к сожалению, нет более точных данных, чтобы дать дневные графики) до того, как мы внедрили эту систему:



    И вот как стал выглядеть график после:



    Это дневной график.

    Можно видеть, что, за исключением пары серверов, загрузка на которых больше похожа на синус, чем на что-то другое, почти все сервера (а там их около 200 штук) слились в одну линию. В принципе, это наблюдалось постепенно — как только мы включили эту систему, оно начало сходиться в очень тоненький промежуток, и разницу между самым нагруженным и самым ненагруженным сервером очень сложно увидеть на графике.



    У этого подхода, на самом деле, есть очень много проблем, т.е. несмотря на то, что он дает хорошие результаты, было бы странно закончить доклад на этом и не рассказать о том, что с этой системой «не так».

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

    Потом, есть интересная проблема — то, что этот алгоритм довольно проблематично встроить в LTM и сам Nginx, т.е. наверное, в Nginx можно, а LTM — это просто закрытая «железка», которой можно сообщать новые веса каким-то внешним образом.

    И есть такая проблема — эти веса могут, в принципе, не применяться, т.е. вы даете системе какие-то новые веса, но она продолжает работать со старыми, вы продолжаете применять свою формулу, а ничего не получается.

    Потом отдельная проблема — как удалять машины? Она, в принципе, не связана конкретно с ровной балансировкой, просто это проблема, которая здесь не решается никак.

    Еще — у LTM есть ограничения на максимальный вес в 100 и, возможно, с этим тоже есть какие-то проблемы.



    Во-первых, как сделать из такой очень простой формулы что-нибудь более стабильное, что не подвержено перегрузкам, т.е. если ваш сервер 15 минут назад имел загрузку 50%, вы на него дали загрузку в два раза больше, то это, скорее всего, значит, что у него загрузка 100%, но не факт, что он будет обслуживать все запросы корректно при этом.

    Идея очень простая: не стоит менять веса слишком сильно за единицу времени. В нашем случае мы получаем более-менее ровную линию загрузки серверов, если строить одну точку за 15 минут. Т.е. обновление весов должно происходить не чаще, чем в 15 минут в наших условиях, иначе там очень большие временные погрешности из-за неравномерности пользовательской нагрузки получаются. Соответственно, мы накладываем ограничения на коэффициент, на который мы умножаем вес. Это, опять же, подобрано эмпирическим путем — мы добавляем не больше 5% к весу каждый раз, и поскольку это целое число, то значит, что минимальный вес должен быть 20, т.е. 5% от него — единица. А также, чтобы не было ситуаций, когда кластер не нагружен, например, серверы временно ошибаются в запросах или еще какие-нибудь проблемы, не скидывает нагрузку с тех серверов, которые в состоянии обслуживать то, что слишком быстро…



    Что делать, если у вас таки нарушилась обратная связь, т.е. веса не применяются? Довольно легко понять, что приведенная мной формула будет тем сильнее увеличивать разницу между загрузкой процессора, чем больше времени прошло. Смысл в том, что если веса не применяются, вы будете видеть разницу, которой нет, и будете все дальше и дальше увеличивать нагрузку на слабые сервера и наоборот недодавать нагрузку на сильные. Это совсем не то, что нужно.

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

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



    Еще один вопрос: что делать, если нужно удалить машину из кластера, т.е. машина больше не отвечает на запросы, но такое возможно в двух случаях — если машина перегружена, т.е. на нее дается слишком много запросов, и вторая проблема — машина действительно «умерла». Дело в том, что когда вы достигнете пика трафика, и если у вас вдруг в этот момент возникнут какие-то проблемы на кластере, то скорее всего, они будут на всех серверах. И просто, потому что серверы не отвечают по heartbeat’у, не стоит удалять сервера, т.к. вы можете таким ообразом каскадно отключить весь кластер.

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



    И напоследок кратко о том, что я хотел сказать.

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

    Опять же, в нашем случае, чтобы подобрать веса статически, нужно приложить довольно много усилий, и не всегда этот подход хорошо работает. Я не привел цифры, сколько на самом деле разброс составил после того, как мы все сделали. Это 2.5%. Для двухсот серверов, мне кажется, это весьма неплохая цифра, т.е. разница между максимальной и минимальной загрузкой очень маленькая.

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



    Этот балансировщик будет выложен по адресу на слайде.

    Также у нас есть очень большое количество других проектов, о которых, возможно, вы слышали. Это PHP-FPM, Pinba и Blitz. Есть и много мелких, о которых, возможно, вы не слышали — это, например, наш форматер для кода, инструменты для аналитики на клиенте под названием Jinba. Инструмент для того, чтобы запускать много параллельно через SSH под названием GoSSHa (написан на языке Go, соответственно). И разные другие вещи, например, библиотека для Android, которая позволяет избежать утечек памяти.

    Контакты


    y.nasretdinov@corp.badoo.com
    youROCK
    Блог компании Badoo

    Этот доклад — расшифровка одного из лучших выступлений на конференции разработчиков высоконагруженных систем HighLoad++. Сейчас мы активно готовим конференцию 2016 года — в этом году HighLoad++ пройдёт в Сколково, 7 и 8 ноября.

    В этом году Юрий также будет выступать на HighLoad++ с докладом "5 способов деплоя PHP-кода в условиях хайлоада". В этом докладе он расскажет о том, как Badoo деплоились в течение 10 лет, о том, какую новую систему для деплоя PHP-кода разработали и внедрили в production, а также проведёт обзор решений для масштабного деплоя кода на PHP и анализ их производительности.

    Также некоторые из этих материалов используются нами в обучающем онлайн-курсе по разработке высоконагруженных систем HighLoad.Guide — это цепочка специально подобранных писем, статей, материалов, видео. Уже сейчас в нашем учебнике более 30 уникальных материалов. Подключайтесь!
    Конференции Олега Бунина (Онтико)
    730,00
    Конференции Олега Бунина
    Поделиться публикацией

    Комментарии 15

      +1
      Имея образование в области автоматизации и управления, забавно было вдруг наткнуться в докладе на своего рода П-регулятор. Вообще много теоретических работ по поводу распределения нагрузки в многоагентной системе. Тем кто занимается такими проблемами стоит на них обратить внимание.
        +1
        Соглашусь. Многие недоученные (уж простите, но сам такой был в институте, мат. аппарат «гулял», но усиленно изучал компьютеры, в итоге всё равно пришлось всё выучить) ITшники любят изобретать велосипед.
        В данном случае прекрасно бы подошёл PID регулятор с обратной связью для настройки весов + нейросеть для хранения старых коэффициентов.
        Идея с нейросетью конечно, «на вскидку», но по-другому учитывать множество факторов влияющих на сиюминутную загрузку будет просто неудобно и трудоёмко.
          +3
          Я конечно не закончил институт, но это не играет особой роли. Здесь не нужны никакие нейросети и прочее, это пустая трата времени. Вся система подгонки весов была написана за пару часов и не требует хранения предыдущего состояния, поэтому очень проста в эксплуатации. В этом случае ваше образование вам бы скорее помешали достигнуть цели, чем помогли.
            0
            Я прошу прощения, вы меня сейчас троллите называя снобом?
            Вы же выложили материал претендующий на серьёзный подход (слова типа production и enterprise, нужное подчеркнуть), но сами говорите, что написали за пару часов систему для внятной балансировки кластера из нескольких тысяч серверов?
            На мой взгляд у вас уже на уровне GTM — bottleneck и высокий риск потери availability.
            Вы меня конечно извините, но сейчас ваша статья больше похожа на рекламу сервиса знакомств, чем на руководство к действию для серьёзных интернет площадок с высокой нагрузкой.
              +2
              Да, в докладе речь шла про распределение нагрузки по серверам внутри ДЦ. На уровне датацентров полной отказоустойчивости в данный момент нет, но и датацентров не так уж много.
              Алгоритм действительно был запущен и протестирован за пару часов и я не вижу здесь ничего плохого. После запуска он был немного доработан путем выставления лимитов на максимальный/минисальный вес на ядро процессора, чтобы можно было задать адекватный начальный вес и чтобы система не слишком сильно разбалансировалась, когда нарушается обратная связь. Я понимаю, что вам обидно, что в основе лежит простейший п-регулятор, который мы сами изобрели за пару часов и все прекрасно работает в продакшене под нагрузкой и решает все поставленные задачи. Это не повод писать снобисткие комментарии про «неучей», а скорее наоборот. Нужно понимать, что наличие профильного образования для решения этой задачи не требуется и решить ее можно своими силами. Не забывайте, что проблем в крупных системах великое множество, и это лишь одна из них. Нужно уметь думать своей головой и тогда вы сможете быстро и адекватно решать задачи, которые перед вами встают. Безусловно, статья этому не учит, она лишь показывает ход мысли, который нас привел к построению такой системы. Конкретно часть про АСУ на самом деле занимает лишь половину доклада, а вторая часть уже специфична для хайлоада. К ней почему-то ни у кого претензий нет, хотя существует множество способов даже просто реализовать weighted round-robin и они дают сильно различные результаты, которые влияют на распределение нагрузки сильнее, чем «колебания», вызванные п-регулятором.
                0
                А можно с этого места
                «На мой взгляд у вас уже на уровне GTM — bottleneck и высокий риск потери availability.»
                поподробнее?

                  0
                  Извините, был не прав. Их система идеальна.
                  Роботы наши друзья!
                    0
                    И все же…
                    Идеально, не идеально — сугубо субъективно.
                    А вот узкое место влияющие на доступность — это узкое место…
                    Вот и хотелось бы именно в этом, сугубо частном сегмента системы, какие есть/вы видите проблемы…

          +2
          Такую же задачу много раз решал на том же оборудовании. В каждом случае использовались разные методы.
          Когда нужно было управлять весами членов пула, я реализовал всю логику на самом LTM с помощью iCall.
          В другом случае, со стороны сервера управления через iControl REST (rest api) подавались команды на смену весов. И да, в тот раз я наткнулся на проблему с тем, что веса не могут быть выше сотни)
          Еще была история, когда веса выставлялись исключительно штатными средствами с помощью WMI или SNMP монитора, а серверы были распределены по разным Priority Group. Поэтому в работе всегда было лишь необходимое количество серверов.
          Часто, даже встроенные в LTM методы балансировки Predictive и Observed показывают хорошие результаты.

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

          В зависимости от контекста, типа трафика и прочей специфики, разные методы показывают разную эффективность. В статье, и в самом выступлении, хорошо отображены навыки и умение использовать привычные инструменты для решения бизнес задач. Кому-то нейронные сети и pid-регуляторы — у всех разные инструментывелосипеды.
            0
            Спасибо за доклад. Как раз решаю похожую проблему, только у меня ситуация на порядок сложнее :). А можно узнать какая нагрузка балансируется? Ну какой лоад держит например 1 инстанс (количество реквестов, сеть, диск). Так же не увидел ничего про хранение состояния (данные сессии) между серверами. Можно немножко больше подробностей тут?
              0
              Нагрузка в среднем измеряется десятками тысяч RPS. Один сервер держит пару сотен RPS и тратит порядка 100 мбит/сек.
              Сессии хранятся в отдельных демонах, постепенно эти демона заменяются на тарантул.
                0
                Сессии хранятся в отдельных демонах, постепенно эти демона заменяются на тарантул.

                А чем обусловлен выбор тарантула, почему, например, не redis? Или там больше чем только хранение?
                  0
                  Да, там много кастомной логики, в основном нужной для того, чтобы разрешать конфликты при параллельной записи в сессию из нескольких веб-запросов
              +1
              gtm да, проблема в этой схеме, как и межДЦ резервирование. Но то до первого инцидента. Мы избавились от gtm полностью. Странная железка, за деньги.

              Все остальное, соглашусь с докладчиком, тривиально и не требует знания PID-регуляторов + нейросетей. На изучение api участвующих в балансировке компонент уйдет полдня, + пара часов на написание скрипта с подробным логированием. + несколько дней на наблюдение, дебаг и правки. И институт для таких вещей и даром не нужен)
                +1
                Картинка «И вот как стал выглядеть график после». Долго не мог понять, почему вы радуетесь тому, что сделали только хуже, пока не понял, что это сравнение месячного графика с дневным.

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

                Самое читаемое