繁体   English   中英

Django Celery:根据用户输入在运行时按计划创建周期性任务

[英]Django Celery: create periodic task at runtime with schedule depending on user input

我有一个简单的 Django (v3.1) 应用程序,我从表单接收数据,用视图处理它,然后将它传递给 Celery(v4.4.7,RabbitMQ 作为代理)。 根据表单中提交的数据,它可以是一次性任务或周期性任务。

周期性任务应该执行与一次性任务相同的任务,但是,嗯,有一个周期性的计划。 我想将该计划传递给任务,包括开始日期、结束日期和间隔(例如:每 2 天下午 4 点执行一次,从现在开始直到 4 周)。

我的观点(当然,为了说明目的而缩短和重命名):

# views.py

if request.method == 'POST':
    form = BackupForm(request.POST)
        
    if form.is_valid():
        data = ...

        if not form.cleaned_data['periodic']: 
            # execute one-time task 
            celery_task = single_task.delay(data)

        else:
            schedule = {
                'first_backup': form.cleaned_data['first_backup'],
                'last_backup': form.cleaned_data['last_backup'],
                'intervall_every': form.cleaned_data['intervall_every'],
                'intervall_unit': form.cleaned_data['intervall_unit'],
                'intervall_time': form.cleaned_data['intervall_time'],
            }

            # execute periodic task, depending on the schedule submitted in the form
            celery_task = single_task.delay(data, schedule=schedule)

        return HttpResponseRedirect(reverse('app:index'))

单个任务如下所示:

# tasks.py

@shared_task
def single_task(data: dict, **kwargs) -> None:
    asyncio.run(bulk_screen(data=data))
    # TODO: receive schedule if periodic and create a periodic task with it

这适用于单个任务。 但是,我不知道如何调整它以创建动态周期性任务。 我的日程安排数据因用户的表单输入而异。 我必须在运行时创建周期性任务。

根据有关定期任务官方文档crontab 计划是我需要的:

from celery.schedules import crontab

app.conf.beat_schedule = {
    # Executes every Monday morning at 7:30 a.m.
    'add-every-monday-morning': {
        'task': 'tasks.add',
        'schedule': crontab(hour=7, minute=30, day_of_week=1),
        'args': (16, 16),
    },
}

虽然这看起来不错,但它位于带有硬编码时间表的 celery 配置中。

我还阅读了on_after_finalize.connect装饰器,我可以在其中执行以下操作:

@celery_app.on_after_finalize.connect
def setup_periodic_tasks(sender, **kwargs):
    sender.add_periodic_task(10.0, task123.s('hello'))

但我不知道如何将时间表传递给这个函数。 另外,发件人是什么? 我可以从我的角度通过它吗?

然后我阅读了有关在 celery beat 中填充相关模型的信息 但我想必须有一种更优雅的方式,使用没有过时装饰器的稳定版本。

谢谢你。

您绝对应该尝试填充django_celery_beat包提供的PeriodicTaskCrontabSchedule 链接到文档

Celery beat 是一个定期运行的调度器,它会简单地根据一个调度执行所有任务(在django_celery_beat情况下,一个数据库支持一个)。 参考文献 1 , 参考文献 2

Celery beat 无疑是处理具有不同计划的周期性任务的最干净的方式,而不是创建自己的计划程序或处理不同的计划。

我相信在 celery 4.x 中你不能设置你需要的动态时间表(我记得看到它出现在 5 中,但不确定它是否有)。 但是,您的代码看起来很有希望——您只需要检查single_task计划并使用countdowneta计划新任务(如果需要)。 任务可以为将来的时间安排它自己的任务

例如

@shared_task
def single_task(data: dict, **kwargs) -> None:
    asyncio.run(bulk_screen(data=data))
    schedule = kwargs.get("schedule")
    if schedule:  # do we need to run this again?
        next_run_at = get_next_run_at(schedule)
        if next_run_at:
            # yes!
            single_task.apply_async(args=[data], kwargs=kwargs, eta=next_run_at)
        # else the task will complete without rescheduling itself

def next_run_at_schedule(schedule: dict) -> Optional[datetime]:
    """ return None if the schedule has expired, or is invalid
        else return the date and time to run the next task
    """
    pass 

有关apply_asynceta详细信息,请参阅Celery 文档 我还没有测试过这个,但原理是合理的。 你应该

  • single_task添加错误处理,以便在主代码中出现错误时安排后续运行
  • 可能在执行主要任务之前添加额外的检查以确保您没有运行僵尸任务(例如用户已取消订阅等)
  • 绝对确保驱动时间表的用户输入已经过彻底验证——不仅仅是日期是日期等。“永远不要相信用户输入”

如果您愿意,您可以在 apply_async 中使用countdown而不是eta

当然,将计划加载到数据库中然后每分钟运行一个简单的 celery 任务来检查需要发生的备份可能更容易(也更可靠,因为正确执行我提到的错误处理并非微不足道)然后将通过simple_task.delay(data)并行触发它们。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM