Database
 sql >> Base de données >  >> RDS >> Database

Tâches asynchrones avec Django et Celery

Lorsque j'étais nouveau sur Django, l'une des choses les plus frustrantes que j'ai vécues était la nécessité d'exécuter périodiquement un peu de code. J'ai écrit une belle fonction qui effectuait une action qui devait s'exécuter quotidiennement à 12h. Facile, non ? Mauvais. Cela s'est avéré être un énorme problème pour moi car à l'époque j'étais habitué à un hébergement Web de type "Cpanel" où il y avait une belle interface graphique pratique pour configurer des tâches cron dans ce but précis.

Après de nombreuses recherches, j'ai trouvé une solution intéressante :Celery, une puissante file d'attente de tâches asynchrones utilisée pour exécuter des tâches en arrière-plan. Mais cela a entraîné des problèmes supplémentaires, car je n'ai pas trouvé d'instructions simples pour intégrer Celery dans un projet Django.

Bien sûr, j'ai finalement réussi à le comprendre - c'est ce que cet article couvrira :Comment intégrer Celery dans un projet Django et créer des tâches périodiques.

Bonus gratuit : Cliquez ici pour accéder à un guide gratuit des ressources d'apprentissage Django (PDF) qui vous montre des trucs et astuces ainsi que les pièges courants à éviter lors de la création d'applications Web Python + Django.

Ce projet utilise Python 3.4, Django 1.8.2, Celery 3.1.18 et Redis 3.0.2.


Aperçu

Pour votre commodité, étant donné qu'il s'agit d'un article si volumineux, veuillez vous reporter à ce tableau pour obtenir de brèves informations sur chaque étape et saisir le code associé.

Étape Présentation Balise Git
Passe-partout Télécharger le passe-partout v1
Configuration Intégrer Celery avec Django v2
Tâches de céleri Ajouter une tâche de céleri de base v3
Tâches périodiques Ajouter une tâche périodique v4
Exécution locale Exécutez notre application localement v5
Exécuter à distance Exécutez notre application à distance v6


Qu'est-ce que le céleri ?

«Celery est une file d'attente de tâches / file d'attente de tâches asynchrone basée sur le passage de messages distribués. Il est axé sur le fonctionnement en temps réel, mais prend également en charge la planification. Pour cet article, nous nous concentrerons sur la fonctionnalité de planification pour exécuter périodiquement un travail/une tâche.

Pourquoi est-ce utile ?

  • Pensez à toutes les fois où vous avez dû exécuter une certaine tâche dans le futur. Peut-être aviez-vous besoin d'accéder à une API toutes les heures. Ou peut-être avez-vous dû envoyer un lot d'e-mails à la fin de la journée. Grande ou petite, Celery facilite la planification de ces tâches périodiques.
  • Vous ne voulez plus que les utilisateurs finaux aient à attendre inutilement que des pages se chargent ou que des actions se terminent. Si un long processus fait partie du flux de travail de votre application, vous pouvez utiliser Celery pour exécuter ce processus en arrière-plan, à mesure que les ressources deviennent disponibles, afin que votre application puisse continuer à répondre aux demandes des clients. Cela maintient la tâche hors du contexte de l'application.


Configuration

Avant de plonger dans Celery, récupérez le projet de démarrage du repo Github. Assurez-vous d'activer un virtualenv, d'installer les exigences et d'exécuter les migrations. Lancez ensuite le serveur et accédez à http://localhost:8000/ dans votre navigateur. Vous devriez voir le texte familier "Félicitations pour votre première page alimentée par Django". Une fois terminé, tuez le serveur.

Ensuite, installons Celery en utilisant pip :

$ pip install celery==3.1.18
$ pip freeze > requirements.txt

Nous pouvons désormais intégrer Celery dans notre projet Django en seulement trois étapes simples.


Étape 1 :Ajoutez celery.py

Dans le répertoire "picha", créez un nouveau fichier appelé celery.py :

from __future__ import absolute_import
import os
from celery import Celery
from django.conf import settings

# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'picha.settings')
app = Celery('picha')

# Using a string here means the worker will not have to
# pickle the object when using Windows.
app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)


@app.task(bind=True)
def debug_task(self):
    print('Request: {0!r}'.format(self.request))

Prenez note des commentaires dans le code.



Étape 2 :Importez votre nouvelle application Celery

Pour vous assurer que l'application Celery est chargée au démarrage de Django, ajoutez le code suivant dans le __init__.py fichier qui se trouve à côté de votre settings.py fichier :

from __future__ import absolute_import

# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app

Cela fait, la mise en page de votre projet devrait maintenant ressembler à :

├── manage.py
├── picha
│   ├── __init__.py
│   ├── celery.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── requirements.txt


Étape 3 :Installez Redis en tant que "courtier" de céleri

Celery utilise des « courtiers » pour transmettre des messages entre un projet Django et les travailleurs de Celery. Dans ce didacticiel, nous utiliserons Redis comme courtier de messages.

Tout d'abord, installez Redis depuis la page de téléchargement officielle ou via brew (brew install redis ) puis tournez-vous vers votre terminal, dans une nouvelle fenêtre de terminal, lancez le serveur :

$ redis-server

Vous pouvez tester que Redis fonctionne correctement en tapant ceci dans votre terminal :

$ redis-cli ping

Redis devrait répondre avec PONG - essayez-le !

Une fois Redis activé, ajoutez le code suivant à votre fichier settings.py :

# CELERY STUFF
BROKER_URL = 'redis://localhost:6379'
CELERY_RESULT_BACKEND = 'redis://localhost:6379'
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Africa/Nairobi'

Vous devez également ajouter Redis en tant que dépendance dans le projet Django :

$ pip install redis==2.10.3
$ pip freeze > requirements.txt

C'est ça! Vous devriez maintenant pouvoir utiliser Celery avec Django. Pour plus d'informations sur la configuration de Celery avec Django, veuillez consulter la documentation officielle de Celery.

Avant de poursuivre, effectuons quelques vérifications pour nous assurer que tout va bien…

Testez que le worker Celery est prêt à recevoir des tâches :

$ celery -A picha worker -l info
...
[2015-07-07 14:07:07,398: INFO/MainProcess] Connected to redis://localhost:6379//
[2015-07-07 14:07:07,410: INFO/MainProcess] mingle: searching for neighbors
[2015-07-07 14:07:08,419: INFO/MainProcess] mingle: all alone

Tuez le processus avec CTRL-C. Maintenant, testez que le planificateur de tâches Celery est prêt à l'action :

$ celery -A picha beat -l info
...
[2015-07-07 14:08:23,054: INFO/MainProcess] beat: Starting...

Boum !

Encore une fois, tuez le processus une fois terminé.




Tâches de céleri

Celery utilise des tâches, qui peuvent être considérées comme des fonctions Python normales appelées avec Celery.

Par exemple, transformons cette fonction de base en une tâche Celery :

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

Commencez par ajouter un décorateur :

from celery.decorators import task

@task(name="sum_two_numbers")
def add(x, y):
    return x + y

Ensuite, vous pouvez exécuter cette tâche de manière asynchrone avec Celery comme suit :

add.delay(7, 8)

Simple, non ?

Ainsi, ces types de tâches sont parfaits lorsque vous souhaitez charger une page Web sans obliger l'utilisateur à attendre la fin d'un processus en arrière-plan.

Prenons un exemple…

Pour en revenir au projet Django, récupérez la version trois, qui comprend une application qui accepte les commentaires des utilisateurs, appelée à juste titre feedback :

├── feedback
│   ├── __init__.py
│   ├── admin.py
│   ├── emails.py
│   ├── forms.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── manage.py
├── picha
│   ├── __init__.py
│   ├── celery.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── requirements.txt
└── templates
    ├── base.html
    └── feedback
        ├── contact.html
        └── email
            ├── feedback_email_body.txt
            └── feedback_email_subject.txt

Installez les nouvelles exigences, lancez l'application et accédez à http://localhost:8000/feedback/. Vous devriez voir :

Câblons la tâche Céleri.


Ajouter la tâche

Fondamentalement, une fois que l'utilisateur a soumis le formulaire de commentaires, nous voulons immédiatement le laisser continuer son bonhomme de chemin pendant que nous traitons les commentaires, envoyons un e-mail, etc., le tout en arrière-plan.

Pour ce faire, ajoutez d'abord un fichier appelé tasks.py vers le répertoire "feedback" :

from celery.decorators import task
from celery.utils.log import get_task_logger

from feedback.emails import send_feedback_email

logger = get_task_logger(__name__)


@task(name="send_feedback_email_task")
def send_feedback_email_task(email, message):
    """sends an email when feedback form is filled successfully"""
    logger.info("Sent feedback email")
    return send_feedback_email(email, message)

Ensuite, mettez à jour forms.py comme ça :

from django import forms
from feedback.tasks import send_feedback_email_task


class FeedbackForm(forms.Form):
    email = forms.EmailField(label="Email Address")
    message = forms.CharField(
        label="Message", widget=forms.Textarea(attrs={'rows': 5}))
    honeypot = forms.CharField(widget=forms.HiddenInput(), required=False)

    def send_email(self):
        # try to trick spammers by checking whether the honeypot field is
        # filled in; not super complicated/effective but it works
        if self.cleaned_data['honeypot']:
            return False
        send_feedback_email_task.delay(
            self.cleaned_data['email'], self.cleaned_data['message'])

Essentiellement, le send_feedback_email_task.delay(email, message) La fonction traite et envoie l'e-mail de commentaires en arrière-plan pendant que l'utilisateur continue d'utiliser le site.

REMARQUE  :L'success_url dans views.py est configuré pour rediriger l'utilisateur vers / , qui n'existe pas encore. Nous allons configurer ce point de terminaison dans la section suivante.




Tâches périodiques

Souvent, vous devrez programmer une tâche pour qu'elle s'exécute à une heure précise de temps en temps - c'est-à-dire qu'un grattoir Web peut avoir besoin de s'exécuter quotidiennement, par exemple. Ces tâches, appelées tâches périodiques, sont faciles à configurer avec Celery.

Le céleri utilise le "battement de céleri" pour planifier des tâches périodiques. Celery beat exécute des tâches à intervalles réguliers, qui sont ensuite exécutées par les travailleurs du céleri.

Par exemple, la tâche suivante est planifiée pour s'exécuter toutes les quinze minutes :

from celery.task.schedules import crontab
from celery.decorators import periodic_task


@periodic_task(run_every=(crontab(minute='*/15')), name="some_task", ignore_result=True)
def some_task():
    # do something

Regardons un exemple plus robuste en ajoutant cette fonctionnalité dans le projet Django…

De retour au projet Django, récupérez la version quatre, qui inclut une autre nouvelle application, appelée photos , qui utilise l'API Flickr pour obtenir de nouvelles photos à afficher sur le site :

├── feedback
│   ├── __init__.py
│   ├── admin.py
│   ├── emails.py
│   ├── forms.py
│   ├── models.py
│   ├── tasks.py
│   ├── tests.py
│   └── views.py
├── manage.py
├── photos
│   ├── __init__.py
│   ├── admin.py
│   ├── models.py
│   ├── settings.py
│   ├── tests.py
│   ├── utils.py
│   └── views.py
├── picha
│   ├── __init__.py
│   ├── celery.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── requirements.txt
└── templates
    ├── base.html
    ├── feedback
    │   ├── contact.html
    │   └── email
    │       ├── feedback_email_body.txt
    │       └── feedback_email_subject.txt
    └── photos
        └── photo_list.html

Installez les nouvelles exigences, exécutez les migrations, puis lancez le serveur pour vous assurer que tout va bien. Essayez à nouveau de tester le formulaire de commentaires. Cette fois, la redirection devrait être parfaite.

Quelle est la prochaine ?

Eh bien, puisque nous aurions besoin d'appeler l'API Flickr périodiquement pour ajouter plus de photos à notre site, nous pouvons ajouter une tâche Celery.


Ajouter la tâche

Ajouter un tasks.py aux photos application :

from celery.task.schedules import crontab
from celery.decorators import periodic_task
from celery.utils.log import get_task_logger

from photos.utils import save_latest_flickr_image

logger = get_task_logger(__name__)


@periodic_task(
    run_every=(crontab(minute='*/15')),
    name="task_save_latest_flickr_image",
    ignore_result=True
)
def task_save_latest_flickr_image():
    """
    Saves latest image from Flickr
    """
    save_latest_flickr_image()
    logger.info("Saved image from Flickr")

Ici, nous exécutons le save_latest_flickr_image() fonction toutes les quinze minutes en enveloppant l'appel de la fonction dans une task . Le @periodic_task le décorateur extrait le code pour exécuter la tâche Celery, laissant le tasks.py fichier propre et facile à lire !




Exécution locale

Prêt à exécuter ce truc ?

Avec votre application Django et Redis en cours d'exécution, ouvrez deux nouvelles fenêtres/onglets de terminal. Dans chaque nouvelle fenêtre, accédez au répertoire de votre projet, activez votre virtualenv, puis exécutez les commandes suivantes (une dans chaque fenêtre) :

$ celery -A picha worker -l info
$ celery -A picha beat -l info

Lorsque vous visitez le site sur http://127.0.0.1:8000/ vous devriez maintenant voir une image. Notre application reçoit une image de Flickr toutes les 15 minutes :

Jetez un œil à photos/tasks.py pour voir le code. Cliquer sur le bouton "Commentaires" vous permet de... envoyer des commentaires :

Cela fonctionne via une tâche de céleri. Consultez feedback/tasks.py pour en savoir plus.

Ça y est, le projet Picha est lancé !

C'est bon pour tester tout en développant votre projet Django localement, mais cela ne fonctionne pas si bien lorsque vous devez déployer en production - comme sur DigitalOcean, peut-être. Pour cela, il est recommandé d'exécuter le travailleur et le planificateur Celery en arrière-plan en tant que démon avec Supervisor.



Exécuter à distance

L'installation est simple. Prenez la version cinq du référentiel (si vous ne l'avez pas déjà). Connectez-vous ensuite en SSH à votre serveur distant et exécutez :

$ sudo apt-get install supervisor

Nous devons ensuite informer le superviseur de nos travailleurs Celery en ajoutant des fichiers de configuration au répertoire "/etc/supervisor/conf.d/" sur le serveur distant. Dans notre cas, nous avons besoin de deux fichiers de configuration de ce type :un pour le travailleur Celery et un pour le planificateur Celery.

Localement, créez un dossier nommé « superviseur » à la racine du projet. Ajoutez ensuite les fichiers suivants…

Travailleur de céleri :picha_celery.conf

; ==================================
;  celery worker supervisor example
; ==================================

; the name of your supervisord program
[program:pichacelery]

; Set full path to celery program if using virtualenv
command=/home/mosh/.virtualenvs/picha/bin/celery worker -A picha --loglevel=INFO

; The directory to your Django project
directory=/home/mosh/sites/picha

; If supervisord is run as the root user, switch users to this UNIX user account
; before doing any processing.
user=mosh

; Supervisor will start as many instances of this program as named by numprocs
numprocs=1

; Put process stdout output in this file
stdout_logfile=/var/log/celery/picha_worker.log

; Put process stderr output in this file
stderr_logfile=/var/log/celery/picha_worker.log

; If true, this program will start automatically when supervisord is started
autostart=true

; May be one of false, unexpected, or true. If false, the process will never
; be autorestarted. If unexpected, the process will be restart when the program
; exits with an exit code that is not one of the exit codes associated with this
; process’ configuration (see exitcodes). If true, the process will be
; unconditionally restarted when it exits, without regard to its exit code.
autorestart=true

; The total number of seconds which the program needs to stay running after
; a startup to consider the start successful.
startsecs=10

; Need to wait for currently executing tasks to finish at shutdown.
; Increase this if you have very long running tasks.
stopwaitsecs = 600

; When resorting to send SIGKILL to the program to terminate it
; send SIGKILL to its whole process group instead,
; taking care of its children as well.
killasgroup=true

; if your broker is supervised, set its priority higher
; so it starts first
priority=998

Planificateur de céleri :picha_celerybeat.conf

; ================================
;  celery beat supervisor example
; ================================

; the name of your supervisord program
[program:pichacelerybeat]

; Set full path to celery program if using virtualenv
command=/home/mosh/.virtualenvs/picha/bin/celerybeat -A picha --loglevel=INFO

; The directory to your Django project
directory=/home/mosh/sites/picha

; If supervisord is run as the root user, switch users to this UNIX user account
; before doing any processing.
user=mosh

; Supervisor will start as many instances of this program as named by numprocs
numprocs=1

; Put process stdout output in this file
stdout_logfile=/var/log/celery/picha_beat.log

; Put process stderr output in this file
stderr_logfile=/var/log/celery/picha_beat.log

; If true, this program will start automatically when supervisord is started
autostart=true

; May be one of false, unexpected, or true. If false, the process will never
; be autorestarted. If unexpected, the process will be restart when the program
; exits with an exit code that is not one of the exit codes associated with this
; process’ configuration (see exitcodes). If true, the process will be
; unconditionally restarted when it exits, without regard to its exit code.
autorestart=true

; The total number of seconds which the program needs to stay running after
; a startup to consider the start successful.
startsecs=10

; if your broker is supervised, set its priority higher
; so it starts first
priority=999

Assurez-vous de mettre à jour les chemins dans ces fichiers pour qu'ils correspondent au système de fichiers du serveur distant.

Fondamentalement, ces fichiers de configuration de superviseur indiquent à superviseurd comment exécuter et gérer nos « programmes » (comme ils sont appelés par superviseurd).

Dans les exemples ci-dessus, nous avons créé deux programmes de supervision nommés "pichacelery" et "pichacelerybeat".

Maintenant, copiez simplement ces fichiers sur le serveur distant dans le répertoire "/etc/supervisor/conf.d/".

Nous devons également créer les fichiers journaux mentionnés dans les scripts ci-dessus sur le serveur distant :

$ touch /var/log/celery/picha_worker.log
$ touch /var/log/celery/picha_beat.log

Enfin, exécutez les commandes suivantes pour informer le superviseur des programmes - par exemple, pichacelery et pichacelerybeat :

$ sudo supervisorctl reread
$ sudo supervisorctl update

Exécutez les commandes suivantes pour arrêter, démarrer et/ou vérifier l'état de la pichacelery programme :

$ sudo supervisorctl stop pichacelery
$ sudo supervisorctl start pichacelery
$ sudo supervisorctl status pichacelery

Vous pouvez en savoir plus sur le superviseur dans la documentation officielle.



Derniers conseils

  1. Ne transmettez pas d'objets de modèle Django aux tâches Celery. Pour éviter les cas où l'objet modèle a déjà été modifié avant d'être transmis à une tâche Celery, transmettez la clé primaire de l'objet à Celery. Vous devrez alors, bien sûr, utiliser la clé primaire pour obtenir l'objet de la base de données avant de travailler dessus.
  2. Le planificateur Celery par défaut crée des fichiers pour stocker son programme localement. Ces fichiers seraient "celerybeat-schedule.db" et "celerybeat.pid". Si vous utilisez un système de contrôle de version comme Git (ce que vous devriez !), c'est une bonne idée d'ignorer ces fichiers et de ne pas les ajouter à votre référentiel car ils servent à exécuter des processus localement.


Étapes suivantes

Eh bien, c'est tout pour l'introduction de base à l'intégration de Celery dans un projet Django.

Vous en voulez plus ?

  1. Plongez dans le guide de l'utilisateur officiel de Celery pour en savoir plus.
  2. Créez un Fabfile pour configurer Supervisor et les fichiers de configuration. Assurez-vous d'ajouter les commandes à reread et update Superviseur.
  3. Dépliquez le projet à partir du référentiel et ouvrez une demande d'extraction pour ajouter une nouvelle tâche Celery.

Bonus gratuit : Cliquez ici pour accéder à un guide gratuit des ressources d'apprentissage Django (PDF) qui vous montre des trucs et astuces ainsi que les pièges courants à éviter lors de la création d'applications Web Python + Django.

Bon codage !