Распределение нагрузки при парсинге сайтов и подключение дополнительных облачных ресурсов +15


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

Как появилась идея написать этот проект?


После того, как появилась необходимость в парсинге сайтов в больших количествах я попытался реализовать такую штуку с помощью selenium grid, потом взял selenoid. selenoid подошел, но там было много того, что мне было не нужно, например версии и варианты браузеров, а также, самое главное, это отсутствие auto scaling (но selenoid не для этого). 90% времени кластер простаивает, а потом появляется большая нагрузка, с которой сервер не справляется. Получается большие расходы на железо, которое почти все время не работает, да еще и не справляется. Я подумал, что было бы здорово, если бы по мере поступления нагрузки — количество исполняемых браузеров бы увеличивалось, а как нагрузка пропадает и браузеры удаляются. К счастью такое можно реализовать, например, через AWS EC2.


Немного о структуре


  • Хаб.

    Хаб запускается там, где вам удобно, он нужен в одном экземпляре.
    При создании docker контейнера с хабом ему нужно передать переменную окружения token.

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

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

    При создании docker контейнера с узлом ему нужно передать переменную окружения token и server. Server — это ip нашего хаба.


Вариант № 1. Запрос от узла


Узел делает запрос к хабу, с установленным заголовком token — который является token из переменной окружения. Хаб проверяет token из запроса и если они совпадают — он запоминает его. Хаб начинает пинговать этот узел, раз в 4 секунды. Если 5 попыток пинга провалились — узел удаляется с пометкой потери соединения. Узел в свою очередь инициализирует ответный пинг, раз в 10 секунд, на случай, если было потеряно соединение с хабом. Это сделано для того, чтобы после обрыва связи кластер сам востановил свое состояние.

Вариант № 2. Запрос от пользователя


Пользователь делает запрос к хабу с установленными заголовками token и number. Токен нужен для того, чтобы только доверенные узлы могли эксплуатировать кластер, а number для того, чтобы мы могли создать разные сессии в рамках одного клиентского ip. На каждую сессию свое уникальное число.

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

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

Ссылка на репозиторий проекта

Итог


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

P.S. Некоторые уточнения


Это первый проект, который я начал писать на GOLANG, в связи с чем, если у кого-то есть предложения или замечания — пишите, пожалуйста, в комментарии (на PR я даже не рассчитываю, но было бы супер круто!)




К сожалению, не доступен сервер mySQL