Cuando era nuevo en Django, una de las cosas más frustrantes que experimenté fue la necesidad de ejecutar un poco de código periódicamente. Escribí una buena función que realizaba una acción que necesitaba ejecutarse diariamente a las 12 am. Fácil, ¿verdad? Equivocado. Esto resultó ser un gran problema para mí, ya que en ese momento estaba acostumbrado al alojamiento web "tipo Cpanel", donde había una GUI agradable y práctica para configurar trabajos cron para este mismo propósito.
Después de mucha investigación, encontré una buena solución:Celery, una poderosa cola de trabajo asíncrona que se usa para ejecutar tareas en segundo plano. Pero esto generó problemas adicionales, ya que no pude encontrar un conjunto sencillo de instrucciones para integrar Celery en un proyecto Django.
Por supuesto, finalmente logré resolverlo, que es lo que tratará este artículo:Cómo integrar Celery en un proyecto Django y crear tareas periódicas.
Bono Gratis: Haga clic aquí para obtener acceso a una Guía de recursos de aprendizaje de Django (PDF) gratuita que le muestra consejos y trucos, así como las trampas comunes que debe evitar al crear aplicaciones web de Python + Django.
Este proyecto utiliza Python 3.4, Django 1.8.2, Celery 3.1.18 y Redis 3.0.2.
Resumen
Para su comodidad, dado que esta es una publicación tan grande, consulte esta tabla para obtener información breve sobre cada paso y obtener el código asociado.
Paso | Resumen | Etiqueta Git |
---|---|---|
Repetitivo | Descargar texto modelo | v1 |
Configuración | Integre Apio con Django | v2 |
Tareas de apio | Añadir tarea básica de apio | v3 |
Tareas periódicas | Añadir Tarea Periódica | v4 |
Ejecución local | Ejecute nuestra aplicación localmente | v5 |
Ejecución remota | Ejecute nuestra aplicación de forma remota | v6 |
¿Qué es el apio?
“Celery es una cola de tareas/cola de trabajos asíncrona basada en el paso de mensajes distribuidos. Se centra en la operación en tiempo real, pero también admite la programación”. Para esta publicación, nos centraremos en la función de programación para ejecutar periódicamente un trabajo/tarea.
¿Por qué es útil?
- Piense en todas las veces que ha tenido que ejecutar una determinada tarea en el futuro. Quizás necesitabas acceder a una API cada hora. O tal vez necesitabas enviar un lote de correos electrónicos al final del día. Grande o pequeño, Celery facilita la programación de tareas periódicas.
- Nunca querrá que los usuarios finales tengan que esperar innecesariamente a que se carguen las páginas o se completen las acciones. Si un proceso largo es parte del flujo de trabajo de su aplicación, puede usar Celery para ejecutar ese proceso en segundo plano, a medida que los recursos estén disponibles, para que su aplicación pueda continuar respondiendo a las solicitudes de los clientes. Esto mantiene la tarea fuera del contexto de la aplicación.
Configuración
Antes de sumergirse en Celery, tome el proyecto inicial del repositorio de Github. Asegúrese de activar un virtualenv, instalar los requisitos y ejecutar las migraciones. Luego inicie el servidor y navegue a http://localhost:8000/ en su navegador. Debería ver el texto familiar "Felicitaciones por su primera página impulsada por Django". Cuando haya terminado, elimine el servidor.
A continuación, instalemos Celery usando pip:
$ pip install celery==3.1.18
$ pip freeze > requirements.txt
Ahora podemos integrar Celery en nuestro Proyecto Django en solo tres sencillos pasos.
Paso 1:Agregar celery.py
Dentro del directorio "picha", cree un nuevo archivo llamado 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))
Tome nota de los comentarios en el código.
Paso 2:importa tu nueva aplicación Celery
Para asegurarse de que la aplicación Celery se carga cuando se inicia Django, agregue el siguiente código en __init__.py archivo que se encuentra junto a su settings.py archivo:
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
Habiendo hecho eso, el diseño de su proyecto ahora debería verse así:
├── manage.py
├── picha
│ ├── __init__.py
│ ├── celery.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── requirements.txt
Paso 3:Instale Redis como un "intermediario" de apio
Celery usa "intermediarios" para pasar mensajes entre un Proyecto Django y los trabajadores de Celery. En este tutorial, utilizaremos Redis como intermediario de mensajes.
Primero, instale Redis desde la página de descarga oficial o a través de brew (brew install redis
) y luego diríjase a su terminal, en una nueva ventana de terminal, inicie el servidor:
$ redis-server
Puede probar que Redis funciona correctamente escribiendo esto en su terminal:
$ redis-cli ping
Redis debería responder con PONG
- ¡pruébalo!
Una vez que Redis esté activo, agregue el siguiente código a su archivo 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'
También debe agregar Redis como una dependencia en el Proyecto Django:
$ pip install redis==2.10.3
$ pip freeze > requirements.txt
¡Eso es todo! Ahora debería poder usar Celery con Django. Para obtener más información sobre cómo configurar Celery con Django, consulte la documentación oficial de Celery.
Antes de continuar, hagamos algunas comprobaciones de cordura para asegurarnos de que todo está bien...
Pruebe que el trabajador de Celery está listo para recibir tareas:
$ 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
Mata el proceso con CTRL-C. Ahora, pruebe que el programador de tareas de Celery está listo para la acción:
$ celery -A picha beat -l info
...
[2015-07-07 14:08:23,054: INFO/MainProcess] beat: Starting...
¡Boom!
Una vez más, elimine el proceso cuando haya terminado.
Tareas de apio
Celery utiliza tareas, que se pueden considerar como funciones regulares de Python que se llaman con Celery.
Por ejemplo, convirtamos esta función básica en una tarea de Apio:
def add(x, y):
return x + y
Primero, agrega un decorador:
from celery.decorators import task
@task(name="sum_two_numbers")
def add(x, y):
return x + y
Luego puede ejecutar esta tarea de forma asíncrona con Celery así:
add.delay(7, 8)
Sencillo, ¿verdad?
Por lo tanto, este tipo de tareas son perfectas para cuando desea cargar una página web sin que el usuario tenga que esperar a que se complete algún proceso en segundo plano.
Veamos un ejemplo…
Volviendo al Proyecto Django, tome la versión tres, que incluye una aplicación que acepta comentarios de los usuarios, acertadamente llamada 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
Instale los nuevos requisitos, inicie la aplicación y vaya a http://localhost:8000/feedback/. Deberías ver:
Conectemos la tarea Apio.
Agregar la tarea
Básicamente, después de que el usuario envíe el formulario de comentarios, queremos dejarlo continuar de inmediato con su camino alegre mientras procesamos los comentarios, enviamos un correo electrónico, etc., todo en segundo plano.
Para lograr esto, primero agregue un archivo llamado tasks.py al directorio de “retroalimentación”:
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)
Luego actualice forms.py así:
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'])
En esencia, send_feedback_email_task.delay(email, message)
La función procesa y envía el correo electrónico de comentarios en segundo plano a medida que el usuario continúa usando el sitio.
NOTA :El success_url
en vistas.py está configurado para redirigir al usuario a /
, que aún no existe. Configuraremos este punto final en la siguiente sección.
Tareas periódicas
A menudo, deberá programar una tarea para que se ejecute en un momento específico de vez en cuando, es decir, es posible que un raspador web deba ejecutarse diariamente, por ejemplo. Tales tareas, llamadas tareas periódicas, son fáciles de configurar con Celery.
El apio utiliza el "ritmo de apio" para programar tareas periódicas. Celery beat ejecuta tareas a intervalos regulares, que luego ejecutan los trabajadores de apio.
Por ejemplo, la siguiente tarea está programada para ejecutarse cada quince minutos:
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
Veamos un ejemplo más sólido agregando esta funcionalidad al Proyecto Django...
Volviendo al Proyecto Django, tome la versión cuatro, que incluye otra aplicación nueva, llamada photos
, que usa la API de Flickr para obtener nuevas fotos para mostrarlas en el sitio:
├── 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
Instale los nuevos requisitos, ejecute las migraciones y luego encienda el servidor para asegurarse de que todo esté bien. Intente volver a probar el formulario de comentarios. Esta vez debería redirigir bien.
¿Qué sigue?
Bueno, dado que necesitaríamos llamar a la API de Flickr periódicamente para agregar más fotos a nuestro sitio, podemos agregar una tarea Celery.
Agregar la tarea
Agregue un tasks.py a las photos
aplicación:
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")
Aquí, ejecutamos save_latest_flickr_image()
función cada quince minutos envolviendo la llamada de función en una task
. El @periodic_task
decorador abstrae el código para ejecutar la tarea Celery, dejando tasks.py archivo limpio y fácil de leer!
Ejecución local
¿Listo para ejecutar esta cosa?
Con su aplicación Django y Redis en ejecución, abra dos nuevas ventanas/pestañas de terminal. En cada nueva ventana, navegue hasta el directorio de su proyecto, active su virtualenv y luego ejecute los siguientes comandos (uno en cada ventana):
$ celery -A picha worker -l info
$ celery -A picha beat -l info
Cuando visite el sitio en http://127.0.0.1:8000/, ahora debería ver una imagen. Nuestra aplicación obtiene una imagen de Flickr cada 15 minutos:
Eche un vistazo a photos/tasks.py
para ver el código. Al hacer clic en el botón "Comentarios" le permite... enviar algunos comentarios:
Esto funciona a través de una tarea de apio. Echa un vistazo a feedback/tasks.py
por más.
Eso es todo, ¡tienes el proyecto Picha en marcha!
Esto es bueno para probar mientras desarrolla su proyecto Django localmente, pero no funciona tan bien cuando necesita implementarlo en producción, como en DigitalOcean, tal vez. Para ello, se recomienda que ejecute Celery Worker y Scheduler en segundo plano como un demonio con Supervisor.
Ejecutar de forma remota
La instalación es sencilla. Tome la versión cinco del repositorio (si aún no la tiene). Luego SSH en su servidor remoto y ejecute:
$ sudo apt-get install supervisor
Luego, debemos informar a Supervisor sobre nuestros trabajadores de Celery agregando archivos de configuración al directorio "/etc/supervisor/conf.d/" en el servidor remoto. En nuestro caso, necesitamos dos archivos de configuración de este tipo:uno para el trabajador de Celery y otro para el programador de Celery.
Localmente, cree una carpeta llamada "supervisor" en la raíz del proyecto. Luego agregue los siguientes archivos...
Trabajador de apio: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
Programador de apio: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
Asegúrese de actualizar las rutas en estos archivos para que coincidan con el sistema de archivos del servidor remoto.
Básicamente, estos archivos de configuración de supervisor le dicen a supervisord cómo ejecutar y administrar nuestros "programas" (como los llama supervisord).
En los ejemplos anteriores, hemos creado dos programas supervisados llamados "pichacelery" y "pichacelerybeat".
Ahora simplemente copie estos archivos al servidor remoto en el directorio "/etc/supervisor/conf.d/".
También necesitamos crear los archivos de registro que se mencionan en los scripts anteriores en el servidor remoto:
$ touch /var/log/celery/picha_worker.log
$ touch /var/log/celery/picha_beat.log
Finalmente, ejecute los siguientes comandos para que Supervisor esté al tanto de los programas, por ejemplo, pichacelery
y pichacelerybeat
:
$ sudo supervisorctl reread
$ sudo supervisorctl update
Ejecute los siguientes comandos para detener, iniciar y/o verificar el estado del pichacelery
programa:
$ sudo supervisorctl stop pichacelery
$ sudo supervisorctl start pichacelery
$ sudo supervisorctl status pichacelery
Puede leer más sobre Supervisor en la documentación oficial.
Consejos finales
- No pase objetos modelo de Django a tareas de Celery. Para evitar casos en los que el objeto del modelo ya haya cambiado antes de pasar a una tarea de Celery, pase la clave principal del objeto a Celery. Entonces, por supuesto, tendría que usar la clave principal para obtener el objeto de la base de datos antes de trabajar en él.
- El programador de Celery predeterminado crea algunos archivos para almacenar su programación localmente. Estos archivos serían “celerybeat-schedule.db” y “celerybeat.pid”. Si está utilizando un sistema de control de versiones como Git (¡que debería hacerlo!), es una buena idea ignorar estos archivos y no agregarlos a su repositorio, ya que son para ejecutar procesos localmente.
Siguientes pasos
Bueno, eso es todo por la introducción básica a la integración de Celery en un proyecto Django.
¿Quieres más?
- Sumérjase en la Guía oficial del usuario de apio para obtener más información.
- Cree un Fabfile para configurar Supervisor y los archivos de configuración. Asegúrate de agregar los comandos a
reread
yupdate
supervisora. - Bifurque el proyecto desde el repositorio y abra una solicitud de extracción para agregar una nueva tarea de apio.
Bono gratis: Haga clic aquí para obtener acceso a una Guía de recursos de aprendizaje de Django (PDF) gratuita que le muestra consejos y trucos, así como las trampas comunes que debe evitar al crear aplicaciones web de Python + Django.
¡Feliz codificación!