Концепт простого Load-balancer'a / Failover'a IP сервисов

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

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

    Итак, в кратце:
    1. В nsswitch.conf для hosts прописываем свою библиотеку ДО files и dns. Так чтобы в /etc/hosts и в DNS система смотрела только если наша библиотека вернула NSS_STATUS_UNAVAIL.
    2. Делаем свою библиотеку всего с одной функцией int gethostbyname_r (...) (подробности как реализовать смотрим тут: www.gnu.org/s/hello/manual/libc/Name-Service-Switch.html#Name-Service-Switch). Функция смотрит нет ли запрошенного имени в shared memory, если есть — возвращает оттуда же IP адрес и увеличивает в shared memory счётчик использования этого имени на 1. Если же в shared memory такого имени нет — возвращает NSS_STATUS_UNAVAIL.
    3. Если библиотека вернула NSS_STATUS_UNAVAIL — система продолжает resolve имени в IP далее штатным образом (/etc/hosts, DNS).
    4. На любом языке, способном открыть multicast socket и работать с shared memory и named socket пишется небольшой сервис, который:
    4.1 читает свой конфиг (начальная конфигурация) с информацией о сервисах, передаёт его в мультикаст группу с пометкой «initial» и кладёт в shared memory.
    4.2 Всё что приходит от других членов мультикаст группы — догружает в shared memory (или удаляет, есть пришло удаление). Если пришло с пометкой «initial» — послать в ответ известные изменения относительно начальной конфигурации.
    4.3 Слушает некие команды через named socket, как следствие добавляя / удаляя / переставляя что-то из shared memory и дублируя это в мультикаст группу.
    4.4 Опционально: по достижении счётчиками в shared memory неких значений удаляет оттуда хост, дублирует команду в мультикаст группу.
    5. Все, кому надо пользоваться неким сервисом, подключаются к нему по имени — библиотека отдаст его из shared memory моментально.

    Так как мы по сути дела перехватываем на себя стандартную функцию системы gethostbyname(), а её по идее использует всё, что работает по IP протоколу и согласно использовать имя хоста (а не только IP напрямую) — то без каких либо дополнительных изменений с этой схемой будут работать большинство клиентских приложений. Некие проблемы могут возникнуть с клиентами которые кешируют проресолвленный IP(например web-browser-ы), впрочем, иногда это даже плюс — не порвётся сессия.

    Таким образом получаем некий скелет, который можно подстроить под всё что угодно и легко управлять нагрузкой и доступностью сервисов (и/или раскидывать нагрузку между ними) просто кидая команды в named socket. Это можно делать умными скриптами / системой мониторинга / web-интерфейсом, тупо 'cat' некого файла с командами в named socket по cron'у и так далее.

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

    Что думаете?
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 3
    • +5
      Мне кажется, что вы переизобрели en.wikipedia.org/wiki/Round-robin_DNS
      • 0
        Ну если быть точнее — я взял его за основу и убрал основные недостатки:
        1. Время ответа: вместо обращения к библиотеке nss_files, чтения /etc/hosts, потом обращения к nss_dns, чтения /etc/resolv.conf и потом обращения к DNS serveram по списку (и обработки ответов, таймаутов, если кто-то из DNS упал) делается обращение к библиотеке nss_xxx которая отдаёт уже готовый ответ из shared memory, что, по идее, значительно быстрее.
        2. При внесении изменений в DNS они вступят в силу на slave-dns серверах самое раннее через секунду (из-за TTL). Репликация зоны между DNS серверами — тоже не самое быстрое действие.
        3. Нет необходимости в настройке Failover-DNS'a.
        4. Если первый в списке resolv.conf DNS помер — второй будет использован только после таймаута (мин. 1 сек), и так далее по списку. При списке в 3 сервера это всё может залипнуть аж на 3 секунды.
        5. Если одна из нод хочет сказать «притормозите, мне и так плохо» — через DNS это весьма нетривиальная задачка.

        Всё это вместе делает использование обычного round-robin DNS'a для, например, обращения к mysql порядка 5 000 раз в секунду мягко говоря пороховой бочкой. Не дай бог первый в списке DNS тормознёт на несколько секунд…

        Далее:
        5. Общаться с УДАЛЁННЫМ bind'ом через rndc, менять зону скриптами и перезапускать dns-server с каждой ноды, так чтоб все это сразу подхватили — имхо куда сложнее чем тупо писать в файл (named-socket) команду типа 'delete host somename' или 'add host somename ip priority'
        7. Настраивать всю это конструкцие со slave-dns на каждой ноде и возможностью быстро менять состояние зоны с каждой ноды — то ещё удовольствие.

        Ну а в остальном — да, согласен, довольно близко.
        • 0
          Хотя, возможно, существуют Round-robin DNS решения и лишённые этих недостатков. Если кто-то встречал такие — дайте ссылочку, пожалуйста — очень хочется пощупать.

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