@galliard
Стандартный способ решить задачу: обработчик пользовательского запроса создает в sql-базе таск на отправку письма со статусом «new» и возвращает пользователю ответ. Далее некий воркер читает новые таски из базы, обрабатывает их (в нашем случае формирует и отсылает письмо) выставляет таску статус «done». Перед обработкой можно еще статус «in_progress» поставить, если хендлеров несколько или это, допустим, запускаемые по крону скрипты, но это уже детали реализации, не будем о них.
Так же давайте будем считать, что нагрузки не планируется, маштабирование не потребуется, все таски будут молотиться одним единственным хендлером.
И вроде как напрашивается идея использовать брокер очередей, например тот же RabbitMQ. Тогда схема становится такой: обработчик пользовательского запроса все еще создает в sql-базе таск на отправку письма со статусом «new», так как эта важная для бизнеса операция и нужно контролировать отправку писем, а брокеры очередей в случае падения теряют данные. Хендлер так же каждые 5 секунд опрашивает, но уже не базу, а брокер очередей, на предмет появления новых тасков. Выполняет таск и ставит в базе данных статус «done».
Получаем те же яица, только в профиль. Из плюсов только то, что нам больше не нужен статус «in_progress», так как за тем, чтоб таск не выполнялся дважды будет следить сам брокер. Но преимущество сомнительное.
Можно немного изменить модель: хендлер не будет писать в базу статус, а будет складывать отработанные таски в отдельную очередь, из которой их будет забирать второй хендлер, вся суть которого будет состоять в проставлении выполненyым таскам статуса «done». Из преимуществ — первому хендлеру не нужно подключение к базе, все данные он будет получать из брокера очередей, но для этого их туда еще положить надо. В общем так же выглядит оверхедом.
Возникает вопрос: может быть брокеры очередей в принципе не подходят для подобного типа задач и созданы для другого? Или я как-то не правильно его готовлю?
Решения вопроса 0
Ответы на вопрос 5
@sergiks
В вопросе описана так-себе реализация с поллингом очереди. Различие без-очереди и с-очередью — в направлении инициативы:
- без очередей «рабочий» опрашивает базу снова и снова: «есть чё?». Это как из браузера ajax’ом пинговать сервер раз в секунду в поиске сообщений. Работает, но такое себе..
- с очередью инициатор процесса – брокер. Это брокер передаёт очередную задачу на исполнение первому освободившемуся «рабочему».
id
записи в БД, которую надо обработать – чтобы рабочий, взявший задачу, заново собирал данные. Это не кошерно и калорийно.Лучше класть в очередь самодостаточную задачу. Например, сериализовать модель. Чтобы рабочий, получивший задачу, уже не обращался к базе – а только наполнял темплейт письма данными из полученной модели и отправлял послание. И в БД проставлял статус «отправлено»
@zoonman
В вашем приведенном примере приведен способ уведомления клиента о каком-то событии. Например о том, что его заказ обработан, отправлен, доставлен и т.д. Событий может быть много и их источники могут быть различными.
Например у вас может быть интеграция со сторонней системой логистики. А еще с 10 разными системами оплаты.
Еще есть маркетинг, который тоже хочет отправлять массовые рассылки.
Получается, что под каждый случай вам прийдется хранить информацию о письмах и статусах. Но даже проблема не в этом, проблема в том, что каждое событие имеет разный приоритет и разную ценность.
В случае работы с сайтом у вас будет 1 воркер, который будет просто сканировать таблицы на поиск неуведомленных пользователей. Если у вас будет 2 воркера, надо делать систему блокировок и т.д. Т.е. мы упираемся в проблему масштабирования. Для решения задачи разной скорости уведомлений, вы построите систему приоритетов или будете делать разные таблицы с разными воркерами. Это все будет работать, но нагрузка на базу будет серьезной и она будет увеличиваться с ростом пользовательской базы. Опять вы упретесь в тормоза.
Опять же есть письма, которые должны быть доставлены немедленно, вроде подтверждений аккаунта, смены пароля и т.д. Еще одна таблица? Дополнительный приоритет? Может так произойти, что вы некоторые пользователи вообще никогда не получат писем и уведомлений. Прийдется придумывать способ контроля.
Я уж не говорю о куче кастомных воркеров под каждую ситуацию. И их будет с десяток, не меньше.
Очереди решают, т.к. можно сделать их несколько и на для каждой настроить определенное количество абсолютно одинаковых воркеров. Пример с поллингом отвратителен и сейчас никто так не делает, а с очередями делают так.
У вас может быть цепочка из нескольких воркеров, когда результат работы одного помещается в очередь.
Например, когда надо сначала достать данные из нескольких разных медленных систем, отрендерить в шаблон, а потом отправить письмо. Сборка, рендеринг и отправка — три разные компонента, последние два из которых, можно активно переиспользовать для других целей изменяя лишь конфигурацию времени исполнения.
При таком подходе проще развивать кодовую базу, исправлять ошибки, т.к. они изолированы в одном месте, а не разбросаны по всей системе. И да, косяк в «горячем» модуле вы заметите немедленно. А самое прикольное то, что ваш косяк, пользователи и не заметят, для них это будет выглядеть как простая задержка, а у вас будет время на то, чтобы исправить ошибку. Сообщения посидят в очереди и все.
Но это еще не все, ваши воркеры могут быть написаны на разных языках программирования. Например пользователь может загружать фото на сайт на PHP, объекты распознаваться на Python, видео рендериться на Rust, а отправка писем может быть на Go. И такой подход может подойти для сложных систем с распределенными командами и различным уровнем компетенция в применяемых технологиях. Специалистов превосходно владеющих всеми приведенными технологиями просто единицы, и поверьте, они решают задачи совершенно другого уровня.
@glaphire
По ответам из разных источников получается, что реббит становится полезен тогда, когда в системе появляется много продюсеров и консюмеров, и реббит, как самостоятельная прослойка, инкапсулирует логику рассылки, попыток переотправки и т.д. То, что сообщеня могут теряться это да, проблема, но можно снизить ее вероятность, если углубиться в настройки и покрутить экстеншены реббита
@qbudha
… Хендлер так же каждые 5 секунд опрашивает, но уже не базу, а брокер очередей, на предмет появления новых тасков. Выполняет таск и ставит в базе данных статус «done».
Из преимуществ — первому хендлеру не нужно подключение к базе, все данные он будет получать из брокера очередей, но для этого их туда еще положить надо
В принципе Вы сами описали преимущества — разгрести очередь быстрее, чем сходить в БД. Профит будет тогда, когда появится нагрузка.
Ну и опять же, всё зависит от ЯП, например, на PHP скидывать сообщение в очередь это более-менее устоявшееся production level решение проблемы отсутствия асинхронности и корутин.
А если Ваш ЯП поддерживает асинхронные вызовы и может не блокировать основной поток выполнения, то может быть Вам пока и не нужны очереди.
Еще в очереди скидывают большие задачи, которые точно займут много времени — это всякий рендеринг видео и тому подобные расчёты.
Upd
Корутины в PHP, конечно же есть. Но я их не юзал и сказать про них ничего не могу:(
@saboteur_kiev
а брокеры очередей в случае падения теряют данные.
Это неверно.
Брокеры бывают разные и настроены по разному.
В общем случае, брокер — вещь гораздо более легковесная, чем база данных. Гораздо проще масштабируется и кластеризируется, чем база данных. Да и работает в основном гораздо быстрее.
Но в вашем случае — отправлять уведомление пользователю, это не совсем задача для брокеров. Брокеры в основном нужны для общения микросервисов/программ друг с другом.