Джанго запускает задачи (возможно) в далеком будущем

9

Предположим, у меня есть модель Event. Я хочу отправить уведомление (электронная почта, push, что угодно) всем приглашенным пользователям после того, как событие прошло. Что-то вроде:

class Event(models.Model):
    start = models.DateTimeField(...)
    end = models.DateTimeField(...)
    invited = models.ManyToManyField(model=User)

    def onEventElapsed(self):
        for user in self.invited:
           my_notification_backend.sendMessage(target=user, message="Event has elapsed")

Теперь, конечно, важная часть - это вызывать onEventElapsedвсякий раз, когда timezone.now() >= event.end. Имейте в виду, endмогут быть месяцы от текущей даты.

Я думал о двух основных способах сделать это:

  1. Используйте периодическое cronзадание (скажем, каждые пять минут или около того), которое проверяет, произошли ли какие-либо события за последние пять минут, и выполняет мой метод.

  2. Используйте celeryи запланируйте, onEventElapsedиспользуя etaпараметр для запуска в будущем (в рамках saveметода моделей ).

Учитывая вариант 1, потенциальное решение может быть django-celery-beat. Тем не менее, кажется немного странным запускать задачу с фиксированным интервалом для отправки уведомлений. Кроме того, я пришел к (потенциальной) проблеме, которая (вероятно) приведет к не очень элегантному решению:

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

Опять же, вариант 2 также имеет свои проблемы:

  • Вручную позаботьтесь о ситуации, когда время начала / окончания события перемещено. При использовании celeryнужно было бы сохранить taskID(easy, ofc) и отозвать задачу, как только даты изменились, и выпустить новую задачу. Но я читал, что у сельдерея есть (специфичные для дизайна) проблемы при работе с задачами, которые будут выполняться в будущем: Открытая проблема на github . Я понимаю, как это происходит и почему это все, но тривиально, чтобы решить.

Теперь я столкнулся с некоторыми библиотеками, которые потенциально могут решить мою проблему:

  • celery_longterm_scheduler (Но значит ли это, что я не могу использовать celery, как раньше, из-за другого класса Scheduler? Это также связано с возможным использованием django-celery-beat... При использовании любой из двух структур все еще можно ставить в очередь задания (что только немного дольше, но не через несколько месяцев?)
  • Джанго-Апшедулер , использует apscheduler. Тем не менее, я не смог найти никакой информации о том, как он будет обрабатывать задачи, которые выполняются в далеком будущем.

Есть ли какой-то фундаментальный недостаток в том, как я к этому подхожу? Я рад за любой вклад, который вы могли бы иметь.

Обратите внимание: я знаю, что, скорее всего, это будет основано на некотором мнении, но, может быть, есть одна очень простая вещь, которую я пропустил, независимо от того, что некоторые могут считать уродливыми или изящными.

Hafnernuss
источник
1
Я бы сказал, что ваш подход зависит от того, как скоро после прошедшего события конечному пользователю понадобится уведомление. У меня была похожая проблема, в которой пользователь должен был знать только на следующий день для любого назначения, пропущенного в предыдущий день. Так что в этом случае я запустил задание cron в полночь и, как вы предположили, имел булево поле, чтобы пометить, были ли отправлены уведомления. Это был очень простой и недорогой способ сделать это.
Хейден Иствуд
1
На мой взгляд, ответ о том, сколько событий нужно отправить. Если вам нужно отправлять сотни событий каждый день, не имеет значения, насколько далеко в будущем будет единичное событие: используя первое решение (адаптируя время повторения в зависимости от ваших потребностей), вы можете запустить задачу, читая обновленные данные.
Dos
@HaydenEastwood Не важно, чтобы человек получил его немедленно, но в течение 2-5 минут в течение конечной даты все должно быть в порядке. Так вы сделали что-то похожее на мой опион 1?
Hafnernuss
1
@Hafnernuss Да - я думаю, что простой вызов cron с полем в базе данных о том, было ли отправлено сообщение, подойдет для вашего случая.
Хейден Иствуд
1
Dramatiq использует другой подход, чем Celery, при планировании задач (не требует много памяти на рабочем месте) и может работать в вашем случае, см. Dramatiq.io/guide.html#scheduling-messages . Но, как говорится - посредник сообщений - это не БД, - когда вам нужно планирование долгосрочного события, ваше первое решение лучше. Таким образом, вы можете комбинировать оба: помещать события в МБ, скажем, на 1 день, и по истечении срока они отправляются в БД и отправляются через cron.
frost-nzcr4

Ответы:

2

Мы делаем что-то подобное в компании, в которой я работаю, и решение довольно простое.

Имейте ритм cron / сельдерея, который запускается каждый час, чтобы проверить, нужно ли отправлять какие-либо уведомления. Затем отправьте эти уведомления и отметьте их как выполненные. Таким образом, даже если ваше уведомление будет на несколько лет вперед, оно все равно будет отправлено. Использование ETA НЕ является подходом для очень долгого времени ожидания, ваш кеш / amqp может потерять данные.

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

Если один час слишком большой из-за разницы во времени, то вы можете запускать планировщик каждый час. Логика была бы что-то вроде

  1. запускать задачу (давайте вызовем эту задачу планировщика) ежечасно, чтобы получать все уведомления, которые должны быть отправлены в течение следующего часа (через такт сельдерея)
  2. Запланируйте эти уведомления через apply_async (eta) - это будет фактическая отправка

Использование этой методологии даст вам оба лучших мира (eta и beat)

ibaguio
источник
1
Спасибо. Это именно то, что я сделал!
Hafnernuss