web-dev-qa-db-fra.com

Comment ajouter / supprimer dynamiquement des tâches périodiques au céleri (céleri-battement)

Si j'ai une fonction définie comme suit:

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

Existe-t-il un moyen d'ajouter dynamiquement cette fonction en tant que Celer PeriodicTask et de la lancer au moment de l'exécution? J'aimerais pouvoir faire quelque chose comme (pseudocode):

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

Je voudrais également arrêter ou supprimer cette tâche dynamiquement avec quelque chose comme (pseudocode):

celery.beat.remove_task(some_unique_task_id)

ou

celery.beat.stop(some_unique_task_id)

Pour info je n'utilise pas djcelery, qui vous permet de gérer des tâches périodiques via l'administrateur Django.

45
Jamie Forrest

Non, je suis désolé, ce n'est pas possible avec le céleri régulier.

Mais il est facilement extensible pour faire ce que vous voulez, par exemple le planificateur Django-céleri n'est qu'une sous-classe qui lit et écrit le calendrier dans la base de données (avec quelques optimisations en haut).

Vous pouvez également utiliser le planificateur Django-céleri même pour des projets non-Django.

Quelque chose comme ça:

  • Installer Django + Django-céleri:

    $ pip install -U Django django-celery

  • Ajoutez les paramètres suivants à votre celeryconfig:

    DATABASES = {
        'default': {
            'NAME': 'celerybeat.db',
            'ENGINE': 'Django.db.backends.sqlite3',
        },
    }
    INSTALLED_APPS = ('djcelery', )
    
  • Créez les tables de base de données:

    $ PYTHONPATH=. Django-admin.py syncdb --settings=celeryconfig
    
  • Démarrez celerybeat avec le planificateur de base de données:

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

Il y a aussi la commande djcelerymon qui peut être utilisée pour les projets non-Django pour démarrer celerycam et un Django Admin webserver dans le même processus, vous pouvez l'utiliser pour éditer également votre périodique tâches dans une belle interface web:

   $ djcelerymon

(Notez que pour une raison quelconque, djcelerymon ne peut pas être arrêté en utilisant Ctrl + C, vous devez utiliser Ctrl + Z + tuer% 1)

19
asksol

Cette question a reçu une réponse le google groups .

JE NE SUIS PAS L'AUTEUR, tout le mérite revient à Jean Mark

Voici une solution appropriée pour cela. Fonctionnement confirmé, dans mon scénario, j'ai sous-classé la tâche périodique et créé un modèle à partir de celui-ci car je peux ajouter d'autres champs au modèle selon mes besoins et aussi pour pouvoir ajouter la méthode "terminer". Vous devez définir la propriété activée de la tâche périodique sur False et l'enregistrer avant de la supprimer. L'ensemble du sous-classement n'est pas un must, la méthode schedule_every est celle qui fait vraiment le travail. Lorsque vous êtes prêt à terminer votre tâche (si vous ne l'avez pas sous-classée), vous pouvez simplement utiliser PeriodicTask.objects.filter (name = ...) pour rechercher votre tâche, la désactiver, puis la supprimer.

J'espère que cela t'aides!

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()
39
McP

Cela a finalement été rendu possible par n correctif inclus dans céleri v4.1.0. Maintenant, il vous suffit de modifier les entrées de planification dans le backend de la base de données, et celery-beat agira selon la nouvelle planification.

Les documents décrivent vaguement comment cela fonctionne. L'ordonnanceur par défaut pour le céleri-beat, PersistentScheduler, utilise un fichier shelve comme base de données de planification. Toute modification du beat_schedule le dictionnaire dans l'instance PersistentScheduler est synchronisé avec cette base de données (par défaut, toutes les 3 minutes), et vice-versa. Les documents décrivent comment ajouter de nouvelles entrées au beat_schedule en utilisant app.add_periodic_task. Pour modifier une entrée existante, ajoutez simplement une nouvelle entrée avec le même name. Supprimez une entrée comme vous le feriez dans un dictionnaire: del app.conf.beat_schedule['name'].

Supposons que vous souhaitiez surveiller et modifier votre programme de battements de céleri à l'aide d'une application externe. Ensuite, vous avez plusieurs options:

  1. Vous pouvez open le fichier de base de données Shelve et lire son contenu comme un dictionnaire. Réécrire dans ce fichier pour des modifications.
  2. Vous pouvez exécuter une autre instance de l'application Celery et utiliser celle-ci pour modifier le fichier Shelve comme décrit ci-dessus.
  3. Vous pouvez tiliser la classe de planificateur personnalisée de Django-celery-beat pour stocker le programme dans une base de données gérée par Django et y accéder aux entrées.
  4. Vous pouvez utiliser le planificateur de celerybeat-mongo pour stocker la planification dans un backend MongoDB et y accéder aux entrées.
6
Tristan Brown

Il existe une bibliothèque appelée Django-celery-beat qui fournit les modèles dont on a besoin. Pour lui faire charger dynamiquement de nouvelles tâches périodiques, il faut créer son propre ordonnanceur.

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
6

Vous pouvez vérifier cela flask-djcelery qui configure flask et djcelery et fournit également une api de repos consultable

2
Waqas Javed

Je cherchais la même solution pour Celery + Redis qui peut être flexible ajouter/supprimer. Découvrez celui-ci, redbeat , même gars de Heroku, même ils ont mis aussi le Redis + Sentinel.

L'espoir aide :)

1
kororo