Джанго виконує завдання (можливо) в далекому майбутньому


9

Припустимо, у мене є модель Event. Я хочу надіслати сповіщення (електронною поштою, надіслати, що завгодно) всім запрошеним користувачам, коли подія закінчиться. Щось у напрямку:

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) і скасувати завдання, як тільки дати змінилися, і видати нове завдання. Але я читав, що у селери є проблеми (специфічні для дизайну) при вирішенні завдань, які виконуються в майбутньому: Open Issue на github . Я усвідомлюю, як це відбувається і чому вирішувати все, окрім як банального.

Тепер я зіткнувся з деякими бібліотеками, які потенційно могли б вирішити мою проблему:

  • celery_longterm_scheduler (Але чи це означає, що я не можу використовувати селеру так, як це було б раніше, через різний клас Планувальник? Це також пов'язане з можливим використанням django-celery-beat... Використовуючи будь-яку з двох фреймворків, чи все-таки можна встановити чергу із завданнями (що трішки триваліший біг, а не місяці?)
  • джанго-апшедулер , використовує apscheduler. Однак мені не вдалося знайти будь-якої інформації про те, як вона буде справлятися із завданнями, які виконуються в далекому майбутньому.

Чи є фундаментальний недолік у тому, як я підходжу до цього? Я радий за будь-які матеріали, які ви можете мати.

Зауважте: я знаю, що це, мабуть, базується на якійсь думці, але, можливо, є дуже елементарна річ, яку я пропустив, незалежно від того, що хтось може вважати потворним чи елегантним.


1
Я б сказав, що ваш підхід залежить від того, як швидко після закінчення події кінцевому користувачеві потрібно повідомити. У мене була подібна проблема, в якій користувачеві потрібно було знати лише наступного дня про будь-яку зустріч, пропущену попередній день. Тож у цьому випадку я запустив роботу з крон опівночі і мав, як ви запропонували, булеве поле, щоб позначити, чи надсилалися повідомлення. Це був дуже простий і обчислювально недорогий спосіб зробити це.
Hayden Eastwood

1
На мою думку, відповідь полягає в тому, скільки подій вам потрібно надіслати. Якщо у вас щодня надсилаються сотні подій, не має значення, наскільки в майбутньому відбувається одна подія: використовуючи перше рішення (адаптуючи час повторення відповідно до ваших потреб), ви можете запустити завдання з читання оновлених даних.
Dos

@HaydenEastwood Не важливо, щоб людина негайно отримувала це, але протягом 2-5 хвилин протягом дати закінчення має бути добре. Ви зробили щось подібне до мого опиту 1?
Хафнернус

1
@Hafnernuss Так - я вважаю, що простий дзвінок із кроном із полем у базі даних щодо того, чи було надіслане повідомлення, було б добре підійде для вашого випадку.
Hayden Eastwood

1
Dramatiq використовує інший підхід, ніж Селера, коли планує завдання (не пам'ять голодує працівника) і може працювати у вашому випадку, див. Dramatiq.io/guide.html#scheduling-messages . Але як кажуть - брокер повідомлень не є БД - коли вам потрібно планувати довгострокові події, ваше перше рішення краще. Таким чином, ви можете комбінувати обидва: покладіть події в МБ, скажімо, до 1 дня, а після закінчення терміну дії вони перейдуть до БД і будуть надсилатися через cron.
frost-nzcr4

Відповіді:


2

Ми щось подібне робимо в компанії, в якій працюю, і рішення досить просте.

Запропонуйте бій крона / селери, який працює щогодини, щоб перевірити, чи потрібно надсилати якесь повідомлення. Потім надішліть ці сповіщення та позначте їх як виконані. Таким чином, навіть якщо ваш час сповіщення на роки вперед, воно все одно буде надіслане. Використання ETA НЕ є способом пройти дуже довгий час очікування, ваш кеш / amqp може втратити дані.

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

Якщо одна година занадто велика різниця у часі, тоді ви можете запускати планувальник щогодини. Логіка була б чимось на кшталт

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

Використовуючи цю методологію, ви отримаєте обидва найкращих світу (ета та бит)


1
Дякую. Саме це я і зробив!
Hafnernuss
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.