Database
 sql >> Base de Dados >  >> RDS >> Database

Tarefas assíncronas com Django e aipo


Quando eu era novo no Django, uma das coisas mais frustrantes que experimentei foi a necessidade de executar um pouco de código periodicamente. Eu escrevi uma função legal que executava uma ação que precisava ser executada diariamente às 12h. Fácil, certo? Errado. Isso acabou sendo um grande problema para mim, pois na época eu estava acostumado a hospedagem na web “tipo Cpanel”, onde havia uma GUI útil para configurar tarefas cron para esse propósito.

Depois de muita pesquisa, encontrei uma boa solução—Celery, uma poderosa fila de trabalho assíncrona usada para executar tarefas em segundo plano. Mas isso levou a problemas adicionais, já que não consegui encontrar um conjunto fácil de instruções para integrar o Celery em um projeto Django.

É claro que eu finalmente consegui descobrir - que é o que este artigo abordará:Como integrar o Celery em um projeto Django e criar tarefas periódicas.

Bônus grátis: Clique aqui para obter acesso a um Guia de Recursos de Aprendizagem Django (PDF) gratuito que mostra dicas e truques, bem como armadilhas comuns a serem evitadas ao construir aplicativos web Python + Django.

Este projeto utiliza Python 3.4, Django 1.8.2, Celery 3.1.18 e Redis 3.0.2.

Visão geral


Para sua conveniência, uma vez que esta é uma postagem tão grande, consulte esta tabela para obter informações breves sobre cada etapa e para obter o código associado.
Etapa Visão geral Git Tag
Padrão Fazer download do clichê v1
Configuração Integrar aipo com Django v2
Tarefas de aipo Adicionar tarefa básica de aipo v3
Tarefas periódicas Adicionar tarefa periódica v4
Executando localmente Execute nosso aplicativo localmente v5
Executando remotamente Execute nosso aplicativo remotamente v6


O que é aipo?


“Aipo é uma fila de tarefas/fila de tarefas assíncronas com base na passagem de mensagens distribuídas. Ele é focado na operação em tempo real, mas também suporta agendamento.” Para este post, vamos nos concentrar no recurso de agendamento para executar periodicamente um trabalho/tarefa.

Por que isso é útil?
  • Pense em todas as vezes que você teve que executar uma determinada tarefa no futuro. Talvez você precisasse acessar uma API a cada hora. Ou talvez você precise enviar um lote de e-mails no final do dia. Grande ou pequeno, o Celery facilita o agendamento dessas tarefas periódicas.
  • Você nunca quer que os usuários finais tenham que esperar desnecessariamente o carregamento das páginas ou a conclusão das ações. Se um processo longo fizer parte do fluxo de trabalho do seu aplicativo, você poderá usar o Celery para executar esse processo em segundo plano, à medida que os recursos forem disponibilizados, para que seu aplicativo possa continuar respondendo às solicitações do cliente. Isso mantém a tarefa fora do contexto do aplicativo.


Configuração


Antes de mergulhar no aipo, pegue o projeto inicial do repositório do Github. Certifique-se de ativar um virtualenv, instalar os requisitos e executar as migrações. Em seguida, inicie o servidor e navegue até http://localhost:8000/ em seu navegador. Você deve ver o familiar texto “Parabéns pela sua primeira página desenvolvida com Django”. Quando terminar, mate o servidor.

Em seguida, vamos instalar o Celery usando pip:
$ pip install celery==3.1.18
$ pip freeze > requirements.txt

Agora podemos integrar o Celery em nosso projeto Django em apenas três etapas fáceis.

Etapa 1:adicionar celery.py


Dentro do diretório “picha”, crie um novo arquivo chamado 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))

Observe os comentários no código.


Etapa 2:importe seu novo aplicativo Celery


Para garantir que o aplicativo Celery seja carregado quando o Django for iniciado, adicione o seguinte código ao __init__.py arquivo que fica ao lado de seu settings.py Arquivo:
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

Feito isso, o layout do seu projeto deve se parecer com:
├── manage.py
├── picha
│   ├── __init__.py
│   ├── celery.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── requirements.txt


Etapa 3:instale o Redis como um "corretor" de aipo


Celery usa “brokers” para passar mensagens entre um projeto Django e os trabalhadores do Celery. Neste tutorial, usaremos o Redis como o agente de mensagens.

Primeiro, instale o Redis na página oficial de download ou via brew (brew install redis ) e, em seguida, vá para o seu terminal, em uma nova janela de terminal, inicie o servidor:
$ redis-server

Você pode testar se o Redis está funcionando corretamente digitando isso no seu terminal:
$ redis-cli ping

O Redis deve responder com PONG - tente!

Quando o Redis estiver ativo, adicione o seguinte código ao seu arquivo 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'

Você também precisa adicionar o Redis como uma dependência no projeto Django:
$ pip install redis==2.10.3
$ pip freeze > requirements.txt

É isso! Agora você deve ser capaz de usar o Celery com o Django. Para mais informações sobre como configurar o Celery com o Django, confira a documentação oficial do Celery.

Antes de prosseguir, vamos fazer algumas verificações de sanidade para garantir que tudo esteja bem…

Teste se o trabalhador Aipo está pronto para receber tarefas:
$ 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

Mate o processo com CTRL-C. Agora, teste se o agendador de tarefas Celery está pronto para ação:
$ celery -A picha beat -l info
...
[2015-07-07 14:08:23,054: INFO/MainProcess] beat: Starting...

Estrondo!

Novamente, mate o processo quando terminar.



Tarefas de aipo


O Celery utiliza tarefas, que podem ser consideradas funções regulares do Python que são chamadas com o Celery.

Por exemplo, vamos transformar essa função básica em uma tarefa de aipo:
def add(x, y):
    return x + y

Primeiro, adicione um decorador:
from celery.decorators import task

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

Então você pode executar esta tarefa de forma assíncrona com o Celery assim:
add.delay(7, 8)

Simples, certo?

Portanto, esses tipos de tarefas são perfeitos para quando você deseja carregar uma página da Web sem fazer com que o usuário aguarde a conclusão de algum processo em segundo plano.

Vejamos um exemplo…

Voltando ao Projeto Django, pegue a versão três, que inclui um aplicativo que aceita feedback dos usuários, apropriadamente chamado de 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 os novos requisitos, inicie o aplicativo e navegue até http://localhost:8000/feedback/. Você deveria ver:

Vamos conectar a tarefa Aipo.

Adicionar a tarefa


Basicamente, depois que o usuário envia o formulário de feedback, queremos deixá-lo imediatamente seguir seu caminho enquanto processamos o feedback, enviamos um e-mail etc., tudo em segundo plano.

Para fazer isso, primeiro adicione um arquivo chamado tasks.py para o diretório “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)

Em seguida, atualize forms.py igual 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'])

Em essência, o send_feedback_email_task.delay(email, message) A função processa e envia o e-mail de feedback em segundo plano à medida que o usuário continua a usar o site.

OBSERVAÇÃO :O success_url em views.py está configurado para redirecionar o usuário para / , que ainda não existe. Vamos configurar esse endpoint na próxima seção.



Tarefas periódicas


Muitas vezes, você precisará agendar uma tarefa para ser executada em um horário específico de vez em quando - ou seja, um web scraper pode precisar ser executado diariamente, por exemplo. Essas tarefas, chamadas de tarefas periódicas, são fáceis de configurar com o Celery.

O aipo usa a “batida do aipo” para agendar tarefas periódicas. A batida de aipo executa tarefas em intervalos regulares, que são então executadas por trabalhadores de aipo.

Por exemplo, a seguinte tarefa está agendada para ser executada a cada quinze 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

Vamos ver um exemplo mais robusto adicionando essa funcionalidade ao Projeto Django…

De volta ao Projeto Django, pegue a versão quatro, que inclui outro novo aplicativo, chamado photos , que usa a API do Flickr para obter novas fotos para exibição no 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

Instale os novos requisitos, execute as migrações e, em seguida, inicie o servidor para garantir que tudo esteja bem. Tente testar o formulário de feedback novamente. Desta vez, deve redirecionar muito bem.

Qual é o próximo?

Bem, como precisaríamos chamar a API do Flickr periodicamente para adicionar mais fotos ao nosso site, podemos adicionar uma tarefa Celery.

Adicionar a tarefa


Adicione um tasks.py para as photos aplicativo:
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")

Aqui, executamos o save_latest_flickr_image() função a cada quinze minutos envolvendo a chamada de função em uma task . O @periodic_task decorador abstrai o código para executar a tarefa Celery, deixando o tasks.py arquivo limpo e fácil de ler!



Executando localmente


Pronto para executar essa coisa?

Com seu aplicativo Django e Redis em execução, abra duas novas janelas/guias de terminal. Em cada nova janela, navegue até o diretório do projeto, ative seu virtualenv e execute os seguintes comandos (um em cada janela):
$ celery -A picha worker -l info
$ celery -A picha beat -l info

Ao visitar o site em http://127.0.0.1:8000/ você deverá ver uma imagem. Nosso aplicativo recebe uma imagem do Flickr a cada 15 minutos:

Dê uma olhada em photos/tasks.py para ver o código. Clicar no botão “Feedback” permite que você… envie algum feedback:

Isso funciona por meio de uma tarefa de aipo. Dê uma olhada em feedback/tasks.py para mais.

Pronto, você tem o projeto Picha em funcionamento!

Isso é bom para testar enquanto desenvolve seu projeto Django localmente, mas não funciona tão bem quando você precisa implantar para produção - como no DigitalOcean, talvez. Para isso, é recomendável executar o trabalhador e o planejador Celery em segundo plano como um daemon com o Supervisor.


Executando remotamente


A instalação é simples. Pegue a versão cinco do repositório (se você ainda não a tiver). Em seguida, SSH em seu servidor remoto e execute:
$ sudo apt-get install supervisor

Em seguida, precisamos informar ao Supervisor sobre nossos trabalhadores do Celery adicionando arquivos de configuração ao diretório “/etc/supervisor/conf.d/” no servidor remoto. Em nosso caso, precisamos de dois desses arquivos de configuração - um para o trabalhador Celery e outro para o agendador Celery.

Localmente, crie uma pasta chamada “supervisor” na raiz do projeto. Em seguida, adicione os seguintes arquivos…

Trabalhador de aipo: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 aipo: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

Certifique-se de atualizar os caminhos nesses arquivos para corresponder ao sistema de arquivos do servidor remoto.

Basicamente, esses arquivos de configuração do supervisor informam ao supervisord como executar e gerenciar nossos ‘programas’ (como são chamados pelo supervisord).

Nos exemplos acima, criamos dois programas supervisionados chamados “pichacelery” e “pichacelerybeat”.

Agora basta copiar esses arquivos para o servidor remoto no diretório “/etc/supervisor/conf.d/”.

Também precisamos criar os arquivos de log mencionados nos scripts acima no servidor remoto:
$ touch /var/log/celery/picha_worker.log
$ touch /var/log/celery/picha_beat.log

Por fim, execute os comandos a seguir para informar o Supervisor sobre os programas - por exemplo, pichacelery e pichacelerybeat :
$ sudo supervisorctl reread
$ sudo supervisorctl update

Execute os seguintes comandos para parar, iniciar e/ou verificar o status do pichacelery programa:
$ sudo supervisorctl stop pichacelery
$ sudo supervisorctl start pichacelery
$ sudo supervisorctl status pichacelery

Você pode ler mais sobre o Supervisor na documentação oficial.


Dicas finais

  1. Não passe objetos de modelo Django para tarefas Celery. Para evitar casos em que o objeto de modelo já foi alterado antes de ser passado para uma tarefa Celery, passe a chave primária do objeto para Celery. Você teria, é claro, que usar a chave primária para obter o objeto do banco de dados antes de trabalhar nele.
  2. O agendador padrão do Celery cria alguns arquivos para armazenar seu agendamento localmente. Esses arquivos seriam “celerybeat-schedule.db” e “celerybeat.pid”. Se você estiver usando um sistema de controle de versão como o Git (o que você deveria!), é uma boa ideia ignorar esses arquivos e não adicioná-los ao seu repositório, pois eles são para executar processos localmente.


Próximas etapas


Bem, isso é tudo para a introdução básica à integração do Celery em um projeto Django.

Quer mais?
  1. Mergulhe no Guia do usuário oficial do Celery para saber mais.
  2. Crie um Fabfile para configurar o Supervisor e os arquivos de configuração. Certifique-se de adicionar os comandos para reread e update Supervisor.
  3. Fork o projeto do repositório e abra uma solicitação pull para adicionar uma nova tarefa de aipo.

Bônus grátis: Clique aqui para obter acesso a um Guia de Recursos de Aprendizagem Django (PDF) gratuito que mostra dicas e truques, bem como armadilhas comuns a serem evitadas ao construir aplicativos web Python + Django.

Boa codificação!