Database
 sql >> Database >  >> RDS >> Database

Attività asincrone con Django e Celery

Quando ero nuovo di Django, una delle cose più frustranti che ho riscontrato è stata la necessità di eseguire periodicamente un po' di codice. Ho scritto una bella funzione che eseguiva un'azione che doveva essere eseguita ogni giorno alle 00:00. Facile, vero? Sbagliato. Questo si è rivelato un grosso problema per me poiché all'epoca ero abituato all'hosting web di tipo "Cpanel" dove c'era una bella GUI pratica per impostare i lavori cron proprio per questo scopo.

Dopo molte ricerche, ho trovato una buona soluzione:Sedano, una potente coda di lavoro asincrona utilizzata per eseguire attività in background. Ma questo ha portato a ulteriori problemi, dal momento che non riuscivo a trovare un semplice set di istruzioni per integrare Celery in un progetto Django.

Ovviamente alla fine sono riuscito a capirlo, che è ciò che tratterà questo articolo:Come integrare il sedano in un progetto Django e creare attività periodiche.

Bonus gratuito: Fai clic qui per accedere a una guida gratuita alle risorse di apprendimento di Django (PDF) che mostra suggerimenti e trucchi, nonché le insidie ​​​​comuni da evitare durante la creazione di applicazioni Web Python + Django.

Questo progetto utilizza Python 3.4, Django 1.8.2, Celery 3.1.18 e Redis 3.0.2.


Panoramica

Per tua comodità, dato che si tratta di un post così grande, fai riferimento a questa tabella per brevi informazioni su ogni passaggio e per prendere il codice associato.

Passo Panoramica Git Tag
Boilerplate Scarica boilerplate v1
Impostazione Integra sedano con Django v2
Attività del sedano Aggiungi attività di base sul sedano v3
Compiti periodici Aggiungi attività periodica v4
Esecuzione in locale Esegui la nostra app in locale v5
Esecuzione in remoto Esegui la nostra app da remoto v6


Cos'è il sedano?

“Sedano è una coda di attività/coda di lavoro asincrona basata sul passaggio di messaggi distribuito. È incentrato sul funzionamento in tempo reale, ma supporta anche la pianificazione". Per questo post, ci concentreremo sulla funzione di pianificazione per eseguire periodicamente un lavoro/attività.

Perché è utile?

  • Pensa a tutte le volte in cui hai dovuto eseguire una determinata attività in futuro. Forse dovevi accedere a un'API ogni ora. O forse avevi bisogno di inviare una serie di e-mail alla fine della giornata. Grande o piccolo, Celery semplifica la pianificazione di tali attività periodiche.
  • Non vuoi mai che gli utenti finali debbano aspettare inutilmente il caricamento delle pagine o il completamento delle azioni. Se un processo lungo fa parte del flusso di lavoro della tua applicazione, puoi utilizzare Celery per eseguire tale processo in background, man mano che le risorse diventano disponibili, in modo che la tua applicazione possa continuare a rispondere alle richieste dei client. Ciò mantiene l'attività fuori dal contesto dell'applicazione.


Configurazione

Prima di tuffarti in Celery, prendi il progetto iniziale dal repository Github. Assicurati di attivare un virtualenv, installare i requisiti ed eseguire le migrazioni. Quindi avvia il server e vai a http://localhost:8000/ nel tuo browser. Dovresti vedere il familiare testo "Congratulazioni per la tua prima pagina basata su Django". Al termine, uccidi il server.

Quindi, installiamo Celery usando pip:

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

Ora possiamo integrare Celery nel nostro progetto Django in soli tre semplici passaggi.


Passaggio 1:aggiungi celery.py

All'interno della directory "picha", crea un nuovo file chiamato 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))

Prendi nota dei commenti nel codice.



Passaggio 2:importa la tua nuova app Celery

Per assicurarti che l'app Celery venga caricata all'avvio di Django, aggiungi il codice seguente in __init__.py file che si trova accanto al tuo settings.py file:

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

Fatto ciò, il layout del tuo progetto dovrebbe ora essere simile a:

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


Fase 3:installa Redis come "Broker" di sedano

Celery utilizza "broker" per passare messaggi tra un progetto Django e i lavoratori del sedano. In questo tutorial utilizzeremo Redis come broker di messaggi.

Innanzitutto, installa Redis dalla pagina di download ufficiale o tramite brew (brew install redis ) e poi vai al tuo terminale, in una nuova finestra di terminale, accendi il server:

$ redis-server

Puoi verificare che Redis funzioni correttamente digitando questo nel tuo terminale:

$ redis-cli ping

Redis dovrebbe rispondere con PONG - provalo!

Una volta che Redis è attivo, aggiungi il seguente codice al tuo file 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'

Devi anche aggiungere Redis come dipendenza nel progetto Django:

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

Questo è tutto! Ora dovresti essere in grado di usare Celery con Django. Per ulteriori informazioni sulla configurazione di Celery con Django, consulta la documentazione ufficiale di Celery.

Prima di andare avanti, eseguiamo alcuni controlli di integrità per assicurarci che tutto vada bene...

Verifica che il lavoratore Sedano sia pronto a ricevere compiti:

$ 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

Termina il processo con CTRL-C. Ora verifica che l'utilità di pianificazione di Celery sia pronta per l'azione:

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

Boom!

Ancora una volta, termina il processo al termine.




Attività del sedano

Celery utilizza attività, che possono essere considerate come normali funzioni Python che vengono chiamate con Celery.

Ad esempio, trasformiamo questa funzione di base in un'attività Sedano:

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

Innanzitutto, aggiungi un decoratore:

from celery.decorators import task

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

Quindi puoi eseguire questa attività in modo asincrono con Celery in questo modo:

add.delay(7, 8)

Semplice, vero?

Quindi, questi tipi di attività sono perfetti per quando vuoi caricare una pagina web senza che l'utente attenda il completamento di un processo in background.

Diamo un'occhiata ad un esempio...

Tornando al progetto Django, prendi la versione tre, che include un'app che accetta feedback dagli utenti, giustamente chiamata 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

Installa i nuovi requisiti, avvia l'app e vai a http://localhost:8000/feedback/. Dovresti vedere:

Connettiamo il compito Sedano.


Aggiungi l'attività

Fondamentalmente, dopo che l'utente ha inviato il modulo di feedback, vogliamo lasciarlo immediatamente continuare per la sua buona strada mentre elaboriamo il feedback, inviamo un'e-mail, ecc., Il tutto in background.

Per fare ciò, prima aggiungi un file chiamato tasks.py alla directory "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)

Quindi aggiorna forms.py così:

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'])

In sostanza, il send_feedback_email_task.delay(email, message) la funzione elabora e invia l'e-mail di feedback in background mentre l'utente continua a utilizzare il sito.

NOTA :Il success_url in views.py è impostato per reindirizzare l'utente a / , che ancora non esiste. Imposteremo questo endpoint nella prossima sezione.




Compiti periodici

Spesso dovrai pianificare un'attività da eseguire a un'ora specifica ogni tanto, ad esempio, un web scraper potrebbe dover essere eseguito ogni giorno, ad esempio. Tali attività, chiamate attività periodiche, sono facili da configurare con Celery.

Il sedano usa il "battito di sedano" per programmare attività periodiche. Celery Beat esegue attività a intervalli regolari, che vengono quindi eseguite dai lavoratori del sedano.

Ad esempio, l'attività seguente è pianificata per l'esecuzione ogni quindici minuti:

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

Diamo un'occhiata a un esempio più robusto aggiungendo questa funzionalità nel progetto Django...

Tornando al progetto Django, prendi la versione quattro, che include un'altra nuova app, chiamata photos , che utilizza l'API di Flickr per ottenere nuove foto da visualizzare sul sito:

├── 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

Installa i nuovi requisiti, esegui le migrazioni e quindi avvia il server per assicurarti che tutto vada bene. Prova a testare di nuovo il modulo di feedback. Questa volta dovrebbe reindirizzare bene.

Qual è il prossimo passo?

Bene, dal momento che dovremmo chiamare periodicamente l'API di Flickr per aggiungere più foto al nostro sito, possiamo aggiungere un'attività Sedano.


Aggiungi l'attività

Aggiungi un tasks.py alle photos app:

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")

Qui eseguiamo save_latest_flickr_image() funzione ogni quindici minuti avvolgendo la chiamata di funzione in un task . Il @periodic_task decorator estrae il codice per eseguire l'attività Celery, lasciando tasks.py file pulito e facile da leggere!




Esecuzione in locale

Pronto per eseguire questa cosa?

Con l'app Django e Redis in esecuzione, apri due nuove finestre/schede del terminale. In ogni nuova finestra, vai alla directory del tuo progetto, attiva il tuo virtualenv, quindi esegui i seguenti comandi (uno in ogni finestra):

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

Quando visiti il ​​sito su http://127.0.0.1:8000/ ora dovresti vedere un'immagine. La nostra app riceve un'immagine da Flickr ogni 15 minuti:

Dai un'occhiata a photos/tasks.py per vedere il codice Facendo clic sul pulsante "Feedback" puoi... inviare un feedback:

Questo funziona tramite un compito di sedano. Dai un'occhiata a feedback/tasks.py per di più.

Ecco fatto, hai il progetto Picha attivo e funzionante!

Questo è utile per i test durante lo sviluppo del tuo progetto Django in locale, ma non funziona così bene quando devi eseguire il deployment in produzione, come forse su DigitalOcean. Per questo, si consiglia di eseguire il lavoratore Celery e lo scheduler in background come demone con Supervisor.



Esecuzione in remoto

L'installazione è semplice. Prendi la versione cinque dal repository (se non ce l'hai già). Quindi SSH nel tuo server remoto ed esegui:

$ sudo apt-get install supervisor

Dobbiamo quindi informare Supervisor dei nostri lavoratori Celery aggiungendo i file di configurazione alla directory "/etc/supervisor/conf.d/" sul server remoto. Nel nostro caso, abbiamo bisogno di due di questi file di configurazione:uno per l'operatore Celery e uno per lo scheduler Celery.

In locale, crea una cartella chiamata "supervisor" nella radice del progetto. Quindi aggiungi i seguenti file...

Sedano lavoratore: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

Programmazione Sedano: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

Assicurati di aggiornare i percorsi in questi file in modo che corrispondano al filesystem del server remoto.

Fondamentalmente, questi file di configurazione del supervisore indicano al supervisore come eseguire e gestire i nostri "programmi" (come vengono chiamati dal supervisore).

Negli esempi precedenti, abbiamo creato due programmi di supervisione denominati "pichacelery" e "pichacelerybeat".

Ora copia questi file sul server remoto nella directory "/etc/supervisor/conf.d/".

Abbiamo anche bisogno di creare i file di registro menzionati negli script sopra sul server remoto:

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

Infine, esegui i seguenti comandi per rendere il Supervisore consapevole dei programmi, ad esempio pichacelery e pichacelerybeat :

$ sudo supervisorctl reread
$ sudo supervisorctl update

Esegui i seguenti comandi per arrestare, avviare e/o controllare lo stato di pichacelery programma:

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

Puoi leggere ulteriori informazioni su Supervisor nella documentazione ufficiale.



Suggerimenti finali

  1. Non passare oggetti modello Django alle attività Celery. Per evitare casi in cui l'oggetto del modello è già cambiato prima di essere passato a un'attività Celery, passare la chiave primaria dell'oggetto a Celery. Dovresti quindi, ovviamente, utilizzare la chiave primaria per ottenere l'oggetto dal database prima di lavorarci sopra.
  2. L'utilità di pianificazione Celery predefinita crea alcuni file per archiviare la sua pianificazione in locale. Questi file sarebbero "celerybeat-schedule.db" e "celerybeat.pid". Se stai utilizzando un sistema di controllo della versione come Git (cosa che dovresti!), è una buona idea ignorare questi file e non aggiungerli al tuo repository poiché servono per eseguire processi localmente.


Passaggi successivi

Bene, questo è tutto per l'introduzione di base all'integrazione del sedano in un progetto Django.

Vuoi di più?

  1. Immergiti nella guida utente ufficiale di Celery per saperne di più.
  2. Crea un Fabfile per configurare Supervisor e i file di configurazione. Assicurati di aggiungere i comandi a reread e update Supervisore.
  3. Forcella il progetto dal repository e apri una richiesta pull per aggiungere una nuova attività Celery.

Bonus gratuito: Fai clic qui per accedere a una guida gratuita alle risorse di apprendimento di Django (PDF) che mostra suggerimenti e trucchi, nonché le insidie ​​​​comuni da evitare durante la creazione di applicazioni Web Python + Django.

Buona codifica!