У меня есть несколько вопросов о реализации взаимодействия между двумя сервисами (A и B). Сервис A управляет расписанием генерации отчетов (формат: еженедельно в пятницу в 18:22, секунды не учитываются), а сервис B отвечает за генерацию и сохранение соответствующих отчетов на основе этого расписания. Мы ожидаем, что в промежутке одного часа (с 20:00 до 21:00 в субботу) может потребоваться создать около 1000 отчетов, что является довольно сложной и времязатратной задачей (порядка 20 секунд на каждую). Параллельно планируется использовать 8 воркеров для обработки этих задач. Также стоит учесть, что время генерации отчетов может изменяться на протяжении дня (например, с 18:00 на 20:00). Я вижу следующее решение: сервис B будет опрашивать сервис A по HTTP каждую минуту (это возможно, так как оба сервиса находятся в одном контуре). Он будет запрашивать идентификаторы сущностей, для которых необходимо сгенерировать отчет в ближайшие 5 минут. После этого сервис B создает отложенные задачи в очереди, избегая дубликатов за счет уникальной маркировки задач по паре (идентификатор сущности + временная метка). Мы можем не учитывать случай, когда время уже запланированного задания было изменено, предполагая, что это достаточно редкая ситуация, либо позже добавить обработку событий, которая будет реагировать на изменения в сервисе A и удалять соответствующие задания из очереди. Однако у меня возникли сомнения по поводу возможных недостатков этого подхода, так как я ранее не сталкивался с подобными задачами. Могли бы вы подсказать, в верном ли направлении я двигаюсь? Стек технологий: Redis (очереди, кэш), Laravel (логика генерации отчета, ежеминутный вызов команды).
Ваше решение имеет несколько положительных аспектов и, в то же время, нуждается в доработке, чтобы справиться с возможными проблемами. Давайте рассмотрим ваш подход и предложим улучшения. ### 1. Опрашивание сервиса A Постоянный опрос сервиса A каждую минуту — вполне рабочий подход, но у него есть свои недостатки: - **Нагрузка на сервис A**: Если сервис A обрабатывает много запросов от различных клиентов, это может вызвать нагрузку на его ресурс. Есть вероятность, что при высоком трафике его производительность может упасть. - **Дублирования запросов**: Вам потребуется предусмотренная система для предотвращения дубликатов в очереди. Это может привести к ситуации, когда два процесса одновременно попытаются создать одно и то же задание. ### 2. Решения для улучшения Вот несколько предложений, как можно улучшить вашу архитектуру: #### a. Вебхуки Если возможно, настройте вебхуки в сервисе A. Это позволит сервису B получать уведомления о изменениях в расписании мгновенно, без необходимости опрашивать сервис A. Когда планирование меняется, сервис A отправляет уведомление в сервис B, который затем обновляет свои задачи. #### b. Менеджер заданий Вместо опрашивания каждую минуту, сервис B мог бы иметь специальный менеджер заданий, который активируют `crontab` или другой механизм для выполнения с заданной периодичностью. Менеджер может проверять наличие новых задач с определенной периодичностью, например, каждые 5 или 10 минут, что снизит нагрузку на сервис A. #### c. Кэширование Используйте Redis для кэширования результатов последнего запроса к сервису A, так, чтобы сервис B мог быстро проверить, есть ли уже созданные задания для конкретного временного окна, тем самым сократив шансы на дублирование. #### d. Обработка очередей Используйте отложенные задания и очереди для того, чтобы управлять нагрузкой на сервис B. Можно легко преобразовать задачи в Redis, чтобы они были обработаны асинхронно. Распределите 1000 отчетов по времени — например, генерируйте 100 отчетов в час, чтобы избежать перегрузки. #### e. Обработка изменений в расписании Если предполагается, что время генерации отчетов может изменяться, то ваш сервис B должен уметь убирать старые задания из очереди, если они больше не актуальны (например, по помощи уникального идентификатора). ### 3. Система мониторинга Будьте готовы к тому, что возникнут ошибки в процессе генерации отчетов. Убедитесь, что на вашем сервере есть система логирования и мониторинга, позволяющая отслеживать состояние задания генерации отчетов, и настройте уведомления в случае неудачи. ### Заключение Ваше решение основано на вразумительных принципах, но требует доработок для обеспечения надежности и снижения нагрузки на сервис A. Безусловно, использование правильных инструментов и подходов позволит вам создать эффективный сервис для генерации отчетов.
В качестве обходного пути - эта 1000 отчетов выглядит для телепата как один и тот же отчет для 1000 пользователей с незначительными отличиями в фильтрах. Почему бы не провести тестирование на разбиение этого на два этапа: 1. получение всех данных из БД и сервисов для всех пользователей; 2. генерация из этих данных 1000 отчетов путем фильтрации в памяти.
Выглядит как задача для OLAP или его заменителя на самом деле. Типа подготавливаем в отдельном кубе (ну или таблице) данные для отчета в фоне, а в нужный момент просто выплевываем по простейшему фильтру и агрегатам. Т.е. первый этап из предыдущего абзаца делать заранее в фоне по расписанию (например за пару часов до часа Х или вообще каждый час только по обновленным данным), а на втором просто простейшее получение данных, которое не занимает много времени.
Фраза про хранение расписания на сервисе А звучит немного неудобно, ведь сервис В обязан знать оперативно обо всех изменениях в расписании, поэтому - расписание должно синхронизироваться между сервисами в момент его изменения. Для синхронизации нужно продумать варианты с проблемами на сервисе В, а так же продумать первоначальную синхронизацию при первом запуске, когда В только что запущен или, например пересоздан.
Нужно продумать о возможности гибкой модификации количества воркеров. Реализация в лоб (изменить конфиг и перезапустить) не всегда корректна, ведь текущие работающие воркеры, исключенные из конфига, могут зависнуть, а сервис их даже не проверит. Обычно для воркера можно реализовать состояние - остановлен, когда он работает но не принимает новые задачи, и процедура исключения ноды из конфига это ожидание окончания его работы (состояние остановлен+свободен).
Постоянные опросы, это просто некрасиво и да, тут не создаст проблем, ведь делает это только один участник (некрасиво это когда запрашивающих состояние много, тогда нагрузка на сервер взлетает экспоненциально от их количества). Правильно и логично, наладить двустороннюю связь по http rest (сервис В сообщает об изменении в состоянии сервису А вызвав у него соответствующий http запрос) или используя socket (websocket, благо решений готовых тьма, т.е. сервис А держит открытое подключение к В и по нему же отправляет и получает всю необходимую информацию, бонусом максимальная оперативность и информация проблемах на сервисе или со связью, что будет возможно с задержкой при http rest подходе).
Реализация не требует чего то особенного и тяжелого типа RabbitMQ или Kafka (о чем тут все наверняка сразу подумали/погуглили, всего тысячи отчетов всего 8 воркеров)... это задача того же уровня проверки на профпригодность.
Реализовать примитивный воркер несколько десятков строк кода.. .в базе хранится список задач, которые здесь и сейчас нужно выполнить, воркеры, после выполнения задания или по сигналу если они не заняты (модуль что заведует этой базой или сам sql сервер, все уже давно поддерживают ивенты, которые можно дергать хоть триггером) берут самую старую не выполненную задачу из списка, отмечают ей статус - на выполнении, выполняют ее, и либо меняют статус на ошибка либо удаляют (или, если требует бизнеслогика, оставляют до какого то времени со статусом исполнено) - внимание, операция выбора задачи - атомарная смена статуса - т.е. один запрос должен изменить статус на 'выполняется воркером номер такой то' (для совсем непонятливых это несколько полей в таблице типа worker_id, status) и уже после начинает его выполнение.
Механизм, с помощью которого свободный воркер определяет, когда ему нужно запрашивать следующую задачу, определит способ баллансировки нагрузки. Можно допустить вариант, когда центральный сервис сам принимает решение, какой воркер какую задачу будет выполнять (а тут на выбор можно собирать статистику нагрузки на процессор, время выполнения и т.п. что бы к примеру равномерно распределять нагрузку, или тупо выбирать случайную ноду).
Нужно помнить, что работа сервиса по управлению воркерами да и их самих лучше делать stateless, т.е. что бы его остановка в любое время никак не повлияла на перезапуск и продолжение работы (само собой нужно отработать, что делать с 'опоздавшими' отчетами, причем вариант когда воркеры не успели тоже)
Параллельно должен крутиться механизм, выявляющий сервисы в статусе 'на исполнении' дольше определенного времени, а еще лучше, проверяющий ноды с воркерами на работоспособоность (воркеры должны уметь отвечать - да я делают такую то работу с таким то прогрессом, таким образом что бы если они повисли или сломались, было бы однозначно ясно, например прогресс не менялся долгое время) и помечающий их в статус - ошибка (естественно причина ошибки должна быть подробной), если речь об интерфейсе администратора, можно тут же доступ к логам воркера дать (с фильтрацией по общей тематики + конкретная задача).
Так как сервис stateless то резервное копирование достаточно делать на уровне хранилища (т.е. достаточно резервировать базу данных и логи). Ну и про авторизацию не забыть, все api могут либо требовать авторизацию на уровне веб сервера (самое простое) либо вручную реализовывать любым алгоритмом (хеширование с секретной солью или цифровая подпись)
Итого на сервисе В должны быть:
* веб сервер с приложением обслуживающий запросы, синхронизацию расписания, заполнение очереди задач на выполнение, контроль за нодами с воркерами
* ноды с воркерами со своим веб сервером (независим от процесса, выполняющего задачу)
* хранилище логов воркеров (независимое от воркеров) + база данных сервиса для расписания и очереди