В этом посте пойдет речь о библиотеке, которая регистрирует в себе узлы и перенаправляет запросы из вне на конкретный узел.
После того, как появилась необходимость в парсинге сайтов в больших количествах я попытался реализовать такую штуку с помощью selenium grid, потом взял selenoid. selenoid подошел, но там было много того, что мне было не нужно, например версии и варианты браузеров, а также, самое главное, это отсутствие auto scaling (но selenoid не для этого). 90% времени кластер простаивает, а потом появляется большая нагрузка, с которой сервер не справляется. Получается большие расходы на железо, которое почти все время не работает, да еще и не справляется. Я подумал, что было бы здорово, если бы по мере поступления нагрузки — количество исполняемых браузеров бы увеличивалось, а как нагрузка пропадает и браузеры удаляются. К счастью такое можно реализовать, например, через AWS EC2.
Узел делает запрос к хабу, с установленным заголовком token — который является token из переменной окружения. Хаб проверяет token из запроса и если они совпадают — он запоминает его. Хаб начинает пинговать этот узел, раз в 4 секунды. Если 5 попыток пинга провалились — узел удаляется с пометкой потери соединения. Узел в свою очередь инициализирует ответный пинг, раз в 10 секунд, на случай, если было потеряно соединение с хабом. Это сделано для того, чтобы после обрыва связи кластер сам востановил свое состояние.
Пользователь делает запрос к хабу с установленными заголовками token и number. Токен нужен для того, чтобы только доверенные узлы могли эксплуатировать кластер, а number для того, чтобы мы могли создать разные сессии в рамках одного клиентского ip. На каждую сессию свое уникальное число.
Для каждого запроса хаб проверяет, есть ли уже созданный маршрут или нет, если есть — запрос просто перенаправляется на нужный узел, если такого маршрута нет — запрос от пользователя встает в очередь на освобождение узла. Как только один из узлов освобождается — хаб составляет маршрут для пользовательской сессии и освободившимся маршрутом. Теперь все запросы для этот сессии пойдут на конкретный узел.
Спустя минуту. как пользователь закрыл соединение — узел освобождается и передается другому пользовательскому запросу.
Ссылка на репозиторий проекта
Пост получился больше похож на инструкцию к применению, но тем не менее, я считаю, что этот проект может стать полезным.
Это первый проект, который я начал писать на GOLANG, в связи с чем, если у кого-то есть предложения или замечания — пишите, пожалуйста, в комментарии (на PR я даже не рассчитываю, но было бы супер круто!)
Как появилась идея написать этот проект?
После того, как появилась необходимость в парсинге сайтов в больших количествах я попытался реализовать такую штуку с помощью 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 я даже не рассчитываю, но было бы супер круто!)
polearnik
почему не nginx — какойто rabbitMQ — и тот же phantomjs?
ArkadyBagdasarov Автор
Как это будет работать вместе?
polearnik
Есть мастер который генерирует задания и складывает в очередь есть куча воркеров которые обрабатывают задания и складывают результат в базу. Мастер в данном случае будет довольно простой скрипт который генерирует начальную ссылку для парсинга и получает от воркеров ссылки полученные в результате парсинга которые проверяет на уникальность и добавляет в очередь. nginx выступает в роли балансировщика.
ArkadyBagdasarov Автор
Как при таком раскладе сохранить сессии? Например, я через один скрипт работаю с сайтом1, во втором скрипте работаю с сайтом2.
Все, что делает первый скрипт — должно выполняться на одном браузере постоянно(например после авторизации). Тоже и для второго скрипта и так далее.
Если бы мне нужно было собрать просто главные страницы от тысячи сайтов — то ваша реализация была бы очень удобна, но в моем случае мне нужно хранить маршрут конкретного пользователя до конкретного исполняемого узла, с сохраненными куками и тд.