繁体   English   中英

如何向 Celery 动态添加/删除周期性任务(celerybeat)

[英]How to dynamically add / remove periodic tasks to Celery (celerybeat)

如果我有一个定义如下的函数:

def add(x,y):
  return x+y

有没有办法动态添加这个函数作为 celery PeriodicTask 并在运行时启动它? 我希望能够做类似(伪代码)的事情:

some_unique_task_id = celery.beat.schedule_task(add, run_every=crontab(minute="*/30"))
celery.beat.start(some_unique_task_id)

我还想用(伪代码)之类的东西动态停止或删除该任务:

celery.beat.remove_task(some_unique_task_id)

要么

celery.beat.stop(some_unique_task_id)

仅供参考,我没有使用 djcelery,它允许您通过 django 管理员管理定期任务。

这个问题是在google groups上回答的。

我不是作者,所有功劳都归功于让马克

这是一个适当的解决方案。 确认工作,在我的场景中,我对周期性任务进行了子类化并从中创建了一个模型,因为我可以根据需要向模型添加其他字段,并且还可以添加“终止”方法。 您必须将周期性任务的 enabled 属性设置为 False 并在删除之前保存它。 整个子类化不是必须的,schedule_every 方法才是真正起作用的方法。 当您准备终止您的任务时(如果您没有将其子类化),您可以使用 PeriodicTask.objects.filter(name=...) 来搜索您的任务,禁用它,然后将其删除。

希望这可以帮助!

 from djcelery.models import PeriodicTask, IntervalSchedule from datetime import datetime class TaskScheduler(models.Model): periodic_task = models.ForeignKey(PeriodicTask) @staticmethod def schedule_every(task_name, period, every, args=None, kwargs=None): """ schedules a task by name every "every" "period". So an example call would be: TaskScheduler('mycustomtask', 'seconds', 30, [1,2,3]) that would schedule your custom task to run every 30 seconds with the arguments 1,2 and 3 passed to the actual task. """ permissible_periods = ['days', 'hours', 'minutes', 'seconds'] if period not in permissible_periods: raise Exception('Invalid period specified') # create the periodic task and the interval ptask_name = "%s_%s" % (task_name, datetime.datetime.now()) # create some name for the period task interval_schedules = IntervalSchedule.objects.filter(period=period, every=every) if interval_schedules: # just check if interval schedules exist like that already and reuse em interval_schedule = interval_schedules[0] else: # create a brand new interval schedule interval_schedule = IntervalSchedule() interval_schedule.every = every # should check to make sure this is a positive int interval_schedule.period = period interval_schedule.save() ptask = PeriodicTask(name=ptask_name, task=task_name, interval=interval_schedule) if args: ptask.args = args if kwargs: ptask.kwargs = kwargs ptask.save() return TaskScheduler.objects.create(periodic_task=ptask) def stop(self): """pauses the task""" ptask = self.periodic_task ptask.enabled = False ptask.save() def start(self): """starts the task""" ptask = self.periodic_task ptask.enabled = True ptask.save() def terminate(self): self.stop() ptask = self.periodic_task self.delete() ptask.delete()

不,对不起,普通的 celerybeat 无法做到这一点。

但是它很容易扩展来做你想做的事情,例如 django-celery 调度程序只是一个子类,将调度读取和写入数据库(顶部有一些优化)。

即使对于非 Django 项目,您也可以使用 django-celery 调度程序。

像这样的东西:

  • 安装 django + django-celery:

    $ pip install -U django django-celery

  • 将以下设置添加到您的 celeryconfig 中:

     DATABASES = { 'default': { 'NAME': 'celerybeat.db', 'ENGINE': 'django.db.backends.sqlite3', }, } INSTALLED_APPS = ('djcelery', )
  • 创建数据库表:

     $ PYTHONPATH=. django-admin.py syncdb --settings=celeryconfig
  • 使用数据库调度程序启动 celerybeat:

     $ PYTHONPATH=. django-admin.py celerybeat --settings=celeryconfig \\ -S djcelery.schedulers.DatabaseScheduler

还有djcelerymon命令可用于非 Django 项目以在同一进程中启动 celerycam 和 Django Admin 网络服务器,您还可以使用它在漂亮的 Web 界面中编辑您的定期任务:

   $ djcelerymon

(注意由于某些原因无法使用 Ctrl+C 停止 djcelerymon,您必须使用 Ctrl+Z + kill %1)

这最终通过 celery v4.1.0 中包含的修复成为可能。 现在,您只需要更改数据库后端中的日程表条目,celery-beat 将根据新日程表进行操作。

文档含糊地描述了这是如何工作的。 celery-beat 的默认调度程序PersistentScheduler使用 搁置文件作为其调度数据库。 PersistentScheduler实例中的beat_schedule字典的任何更改beat_schedule与此数据库同步(默认情况下,每 3 分钟一次),反之亦然。 文档描述了如何使用app.add_periodic_taskbeat_schedule 添加新条目 要修改现有条目,只需添加一个具有相同name的新条目。 像从字典中一样删除条目: del app.conf.beat_schedule['name']

假设您想使用外部应用程序监控和修改您的 celery 节拍时间表。 那么你有几个选择:

  1. 您可以open搁置数据库文件并像阅读字典一样阅读其内容。 写回此文件进行修改。
  2. 您可以运行 Celery 应用程序的另一个实例,并使用该实例修改上述搁置文件。
  3. 您可以使用 django-celery-beat 中的自定义调度程序类将调度存储在 django 管理的数据库中,并访问那里的条目。
  4. 您可以使用celerybeat-mongo中的调度程序将调度存储在 MongoDB 后端,并访问那里的条目。

有一个名为 django-celery-beat 的库,它提供了一个需要的模型。 为了使其动态加载新的周期性任务,必须创建自己的调度程序。

from django_celery_beat.schedulers import DatabaseScheduler


class AutoUpdateScheduler(DatabaseScheduler):

    def tick(self, *args, **kwargs):
        if self.schedule_changed():
            print('resetting heap')
            self.sync()
            self._heap = None
            new_schedule = self.all_as_schedule()

            if new_schedule:
                to_add = new_schedule.keys() - self.schedule.keys()
                to_remove = self.schedule.keys() - new_schedule.keys()
                for key in to_add:
                    self.schedule[key] = new_schedule[key]
                for key in to_remove:
                    del self.schedule[key]

        super(AutoUpdateScheduler, self).tick(*args, **kwargs)

    @property
    def schedule(self):
        if not self._initial_read and not self._schedule:
            self._initial_read = True
            self._schedule = self.all_as_schedule()

        return self._schedule

你可以查看这个flask-djcelery ,它配置了flask和djcelery,还提供了可浏览的rest api

我一直在为 Celery + Redis 寻找可以灵活添加/删除的相同解决方案。 看看这个, redbeat ,来自 Heroku 的同一个人,甚至他们也放了 Redis + Sentinel。

希望有所帮助:)

Celery 可以实现与数据库和调用自身的动态周期任务。

但是APSchedule 更好。

因为动态周期任务总是意味着长时间的倒计时或 eta。 这些周期性任务太多会占用大量内存,导致重新启动和执行非延迟任务非常耗时。

任务.py

import sqlite3
from celery import Celery
from celery.utils.log import get_task_logger

logger = get_task_logger(__name__)

app = Celery(
    'tasks',
    broker='redis://localhost:6379/0',
    backend='redis://localhost:6379/1',
    imports=['tasks'],
)

conn = sqlite3.connect('database.db', check_same_thread=False)
c = conn.cursor()
sql = '''
CREATE TABLE IF NOT EXISTS `tasks` 
(
   `id` INTEGER UNIQUE PRIMARY KEY AUTOINCREMENT,
   `name` TEXT,
   `countdown` INTEGER
);
'''
c.execute(sql)


def create(name='job', countdown=5):
    sql = 'INSERT INTO `tasks` (`name`, `countdown`) VALUES (?, ?)'
    c.execute(sql, (name, countdown))
    conn.commit()
    return c.lastrowid


def read(id=None, verbose=False):
    sql = 'SELECT * FROM `tasks` '
    if id:
        sql = 'SELECT * FROM `tasks` WHERE `id`={}'.format(id)
    all_rows = c.execute(sql).fetchall()
    if verbose:
        print(all_rows)
    return all_rows


def update(id, countdown):
    sql = 'UPDATE `tasks` SET `countdown`=? WHERE `id`=?'
    c.execute(sql, (countdown, id))
    conn.commit()


def delete(id, verbose=False):
    sql = 'DELETE FROM `tasks` WHERE `id`=?'
    affected_rows = c.execute(sql, (id,)).rowcount
    if verbose:
        print('deleted {} rows'.format(affected_rows))
    conn.commit()


@app.task
def job(id):
    id = read(id)
    if id:
        id, name, countdown = id[0]
    else:
        logger.info('stop')
        return

    logger.warning('id={}'.format(id))
    logger.warning('name={}'.format(name))
    logger.warning('countdown={}'.format(countdown))

    job.apply_async(args=(id,), countdown=countdown)

主文件

from tasks import *

id = create(name='job', countdown=5)
job(id)
# job.apply_async((id,), countdown=5)  # wait 5s

print(read())

input('enter to update')
update(id, countdown=1)

input('enter to delete')
delete(id, verbose=True)

暂无
暂无

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

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