我正在使用一个web应用程序,该应用程序允许用户在日历上创建事件(一次性或重复),在事件启动前不久,系统会通知参与者。我在设计此类通知的流程时遇到了问题,尤其是对于重复发生的事件。
需要考虑的事项:
- web应用程序的体系结构使得有许多相同结构的数据库,每个数据库都保留自己的一组用户和事件。因此,任何针对一个数据库的查询都需要针对数千个其他数据库进行
-
重复发生的事件可能有排除日期(类似于RRULE和EXDATE组合)。
-
用户可以更新事件的时间/重复规则。
-
该应用程序是用Python编写的,并且已经使用了带有Redis broker的Celery 3.1。使用此设置的解决方案会很好,尽管任何事情都可以。根据我的发现,目前很难使用Celery动态添加定期任务。
我正在尝试的解决方案:
-
定期任务每天运行一次,扫描每个数据库,并添加任务,以便在适当的时间为当天重复发生的每个事件发出通知。
-
如上生成的每个任务的id都临时保存在Redis中。如果用户在安排通知任务后更改当天的事件时间,则该任务将被吊销并替换为新任务。
上述解决方案的示例代码:
-
在
tasks.py
中,所有要运行的任务:from celery.task import task as celery_task from celery.result import AsyncResult from datetime import datetime # ... @celery_task def create_notify_task(): for account in system.query(Account): db_session = account.get_session() # get sql alchemy session for event in db_session.query(Event): schedule_notify_event(account, partial_event) @celery_task(name='notify_event_users') def notify_event_users(account_id, event_id): # do notification for every event participant pass def schedule_notify_event(account, event): partial_event = event.get_partial_on(datetime.today()) if partial_event: result = notify_event_users.apply_async( args = (account.id, event.id), eta = partial_event.start) replace_task_id(account.id, event.id, result.id) else: replace_task_id(account.id, event.id, None) def replace_task_id(account_id, event_id, result_id): key = '{}:event'.format(account_id) client = redis.get_client() old_result_id = client.hget(key, event_id) if old_result_id: AsyncResult(old_result_id).revoke() client.hset(key, event_id, result_id)
-
在
event.py
:中# when a user change event's time def update_event(event, data): # ... # update event # ... schedule_notify_event(account, event)
-
芹菜设置文件:
from celery.schedules import crontab CELERYBEAT_SCHEDULE = { 'create-notify-every-day': { 'task': 'tasks.create_notify_task', 'schedule': crontab(minute=0, hour=0), 'args': (,) }, }
上面的一些缺点是:
-
每天的任务可能需要很长时间才能运行。上次处理的数据库中的事件必须等待,可能会错过。提前安排任务(例如,第二天前2小时)可能会缓解这种情况,但首次运行设置(或服务器重启后)有点尴尬。
-
必须小心,以免为同一事件安排两次通知任务(例如,因为create_notify_task每天运行不止一次…)。
有没有更明智的方法?
相关问题:
- 芹菜中高效的重复任务
好久没有答案了,我忘了这个问题。无论如何,当时我采用了以下解决方案。我在这里概述一下,以防有人感兴趣。
- 创建事件时,任务计划在其下一次发生前不久运行(即下一次通知时间)。计划时间是在应用所有重复规则和异常规则的情况下计算的,因此它只是一个简单的一次性计划任务
- 当任务运行时,它执行通知作业,并在下一个通知时间安排一个新任务(同样,考虑所有重复和异常规则)。如果没有发生下一个事件,则不会安排新任务
- 任务的id与事件一起保存在数据库中。如果事件的时间发生了更改,则会取消任务,并在新的下一个通知时间安排新任务。当任务运行并安排新任务时,新任务的id将保存在数据库中
我能想到的一些利弊:
- 优点:
- 不需要芹菜中复杂的重复规则,因为任务只是一次运行的时间表
- 每个任务都相当小且快速,因为它只需要关心一个事件通知
- 缺点:
- 在任何时候,都有很多芹菜定时任务等待执行,可能有几十万个。我不确定这会对芹菜的性能产生什么影响,所以这可能是一个实际的骗局,也可能不是。到目前为止,系统运行得还不错