Задать вопрос
@OrbiJ

Как правильно построить сервис, создающий отложенные задачи на основе расписания из другого сервиса?

Есть 2 сервиса (A и B). Сервис A хранит в себе расписание для генерации отчётов (Формат: еженедельно в пятницу в 18:22, секунды не учитываются), сервис B должен на основе этого расписания генерировать и сохранять к себе соответствующий отчёт. Ожидаемый кейс, что в промежутке одного часа (в 20:00-21:00 в субботу) может одновременно потребоваться генерация ~1000 отчётов (операция относительно тяжёлая и затратная по времени: ~20 секунд на отчёт, ожидаемое кол-во воркеров - 8). Задача усложняется тем, что в процессе дня время генерации может измениться (например, переехать с 18:00 на 20:00).

Пока вижу такое решение:
Сервис B по HTTP (они внутри одного контура, так что можно позволить себе) каждую минуту опрашивает сервис A и запрашивает у него все id-шники сущностей, отчёт для которых должен быть сгенерирован в ближайшие 5 минут, после чего ставит отложенные задачи в очередь, каждая задача маркируется уникальной по паре(id-сущности + временная метка), чтобы при следующих опросах в течение 5 минут не залетели дубликаты и были подхвачены задания, установленные в соответствующий промежуток. Кейсом, при котором время уже запланированного задания было изменено можно пренебречь (сказать, что не успели поменять, лол, либо в перспективе дописать соответствующее событие, которое будет реагировать на изменение в исходном сервисе и убивать уже существующее задание в очереди).

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

Стек: Redis (очереди, кэш), Laravel (логика генерации отчета, ежеминутный вызов команды).
  • Вопрос задан
  • 273 просмотра
Подписаться 2 Простой 5 комментариев
Пригласить эксперта
Ответы на вопрос 2
Fragster
@Fragster
помогло? отметь решением!
В качестве обходного пути - эта 1000 отчетов выглядит для телепата как один и тот же отчет для 1000 пользователей с незначительными отличиями в фильтрах. Почему бы не провести тестирование на разбиение этого на два этапа: 1. получение всех данных из БД и сервисов для всех пользователей; 2. генерация из этих данных 1000 отчетов путем фильтрации в памяти.

Выглядит как задача для OLAP или его заменителя на самом деле. Типа подготавливаем в отдельном кубе (ну или таблице) данные для отчета в фоне, а в нужный момент просто выплевываем по простейшему фильтру и агрегатам. Т.е. первый этап из предыдущего абзаца делать заранее в фоне по расписанию (например за пару часов до часа Х или вообще каждый час только по обновленным данным), а на втором просто простейшее получение данных, которое не занимает много времени.
Ответ написан
@rPman
Фраза про хранение расписания на сервисе А звучит немного неудобно, ведь сервис В обязан знать оперативно обо всех изменениях в расписании, поэтому - расписание должно синхронизироваться между сервисами в момент его изменения. Для синхронизации нужно продумать варианты с проблемами на сервисе В, а так же продумать первоначальную синхронизацию при первом запуске, когда В только что запущен или, например пересоздан.

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

Постоянные опросы, это просто некрасиво и да, тут не создаст проблем, ведь делает это только один участник (некрасиво это когда запрашивающих состояние много, тогда нагрузка на сервер взлетает экспоненциально от их количества). Правильно и логично, наладить двустороннюю связь по http rest (сервис В сообщает об изменении в состоянии сервису А вызвав у него соответствующий http запрос) или используя socket (websocket, благо решений готовых тьма, т.е. сервис А держит открытое подключение к В и по нему же отправляет и получает всю необходимую информацию, бонусом максимальная оперативность и информация проблемах на сервисе или со связью, что будет возможно с задержкой при http rest подходе).

Реализация не требует чего то особенного и тяжелого типа RabbitMQ или Kafka (о чем тут все наверняка сразу подумали/погуглили, всего тысячи отчетов всего 8 воркеров)... это задача того же уровня проверки на профпригодность.

Реализовать примитивный воркер несколько десятков строк кода.. .в базе хранится список задач, которые здесь и сейчас нужно выполнить, воркеры, после выполнения задания или по сигналу если они не заняты (модуль что заведует этой базой или сам sql сервер, все уже давно поддерживают ивенты, которые можно дергать хоть триггером) берут самую старую не выполненную задачу из списка, отмечают ей статус - на выполнении, выполняют ее, и либо меняют статус на ошибка либо удаляют (или, если требует бизнеслогика, оставляют до какого то времени со статусом исполнено) - внимание, операция выбора задачи - атомарная смена статуса - т.е. один запрос должен изменить статус на 'выполняется воркером номер такой то' (для совсем непонятливых это несколько полей в таблице типа worker_id, status) и уже после начинает его выполнение.

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

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

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

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

Итого на сервисе В должны быть:
* веб сервер с приложением обслуживающий запросы, синхронизацию расписания, заполнение очереди задач на выполнение, контроль за нодами с воркерами
* ноды с воркерами со своим веб сервером (независим от процесса, выполняющего задачу)
* хранилище логов воркеров (независимое от воркеров) + база данных сервиса для расписания и очереди
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы