Estou escrevendo esse post empolgado por uma lightning talk que fiz no GruPy-SP (16/07). Assim aproveito pra explicar um pouco mais o que estou dizendo nos slides.
O problema de tarefas demoradas do lado do servidor
Sempre que o cliente faz uma requisição web (request), o servidor faz um processamento, normalmente pra quem usa o django, ele lê a requisição, trata os dados recebidos, salva ou recupera registros do banco de dados (através dos models), faz algum processamento do que será exibido para o usuário, renderiza isso em um template e manda uma resposta (response) para o cliente.
Dependendo da tarefa que você executa no servidor a resposta pode demorar muito e isso leva à problemas de TimeOut, a experiência do usuário fica comprometida (quanto mais o servidor demorar pra responder maior a chance do usuário sair do seu site) e também pode acontecer da lentidão ser causada por outros, como por exemplo o uso de uma API externa.
Existem diversas tarefas que podem demorar pra ser executadas. Se você tiver que criar um relatório pesado, acionado por um client web. Se de repente você precisar enviar diferentes emails para uma lista ou por exemplo editar um vídeo depois que o usuário faz o upload na sua página.
Caso real
Esse é um problema que me deparei um dia na geração de um relatório. O relatório levava cerca de 20 minutos para ser gerado, o cliente recebia um erro de timeout e obviamente não dá pra esperar 20 minutos pra gerar alguma coisa. Pra driblar isso e deixar a tarefa em background, alguém resolveu usar um comando do sistema operacional. (No linux se você colocar & no final de um comando esse comando roda em background).
Note que em seguida o django envia mostra uma mensagem ao usuário de que ele será avisado por e-mail ao final da geração do relatório.
Alguma coisa não parece certa aqui.
Celery – A solução para esses problemas!
O Celery é um gerenciador de tarefas assíncronas. Com ele você pode executar uma fila de tarefas (que ele recebe por meio de mensagens), pode agendar tarefas direto no seu projeto sem precisar do cron e ele ainda tem integração fácil com a maioria dos frameworks python mais utilizados (django, flask, tornado, etc.)
Como o Celery funciona
Essa imagem kibada da internet dá uma visão geral de como fica o fluxo da sua aplicação.
- O User (ou Client ou Producer) é a sua aplicação Django.
- O AMPQ Broker é um Message Broker. Um programa responsável por manter a fila de mensagens que serão trocadas entre o seu programa e o Celery, geralmente é o RabbitMQ ou o Redis
- Os workers (ou consumers) que vão executar as tarefas que você quer que sejam assíncronas.
- O Result Store, que é onde os workers podem ou não salvar os resultados das tarefas que foram executadas.
O Client pode passar uma tarefa, uma lista de tarefas, tarefas periódicas ou o retry de alguma tarefa pra fila do Message Broker. O Message Broker distribui essas tarefas entre os workers e o resultado dessas tarefas pode ser escrito em um Result Store (Memcahed, RDBMS, MongoDB, etc…) que mais tarde pode ser lido pelo Client novamente.
Qual Broker utilizar?
O recomendado pelo Celery é o RabbitMQ.
Instalando e configurando o RabbitMQ
Existem vários exemplos de como utilizar o Redis, eu vou mostrar como usar o RabbitMQ.
Você só precisa:
- Instalar o RabbitMQ (Baixar o pacote no site ou adicionar o repo deles no sources.list e instalar com APT-GET)
sudo apt-get install rabbitmq-server
- Criar um usuário, um virtual host e dar permissões para esse usuário no virtualhost:
sudo rabbitmqctl add_user myuser mypassword sudo rabbitmqctl add_vhost myvhost sudo rabbitmqctl set_permissions -p myvhost myuser ".*" ".*" ".*"
Instalando e configurando o Celery
pip install celery
No seu settings.py:
#Celery Config
BROKER_URL = 'amqp://guest:guest@localhost:5672//'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
Na pasta do projeto (a mesma pasta do settings.py) crie um 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', 'nome_do_proj.settings')
app = Celery('nome_do_proj')
# 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)
Esse autodiscover_tasks permite que seu projeto ache todas as tarefas assíncronas de cada app instalada nele. Nessa mesma pasta do settings.py, você vai modificar o arquivo de pacote __init__.py:
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
Criando as tarefas assíncronas do seu app
Na pasta do seu app crie um arquivo chamado tasks.py:
from __future__ import absolute_import
from celery import shared_task
from relatorios import gerar_relatorio_excel
@shared_task
def gerar_relatorio(data_inicial, data_final, email):
gerar_relatorio_excel(
data_inicial = data_inicial,
data_final = data_final,
email = email
)
return "Relatorio Excel gerado"
Agora é só fazer importar essa função que foi criada usando o método delay que foi adicionado à sua função pelo decorator shared_task.
Rodando o Celery
Agora você precisa rodar o celery. Ele vai rodar os seus workers pra executar suas tarefas de forma assíncrona. Por padrão ele inicia um worker para cada núcleo de processador, mas você pode determinar quantos com -c Numero_de_workers
celery --app=nome_projeto worker --loglevel=INFO
E então você verá essa tela:
Você pode então testar sua função assíncrona com o shell do django mesmo:
E o que aparece na tela que está mostrando o celery:
Colocando o Celery em Produção
Para usar o Celery em produção eu recomendo que você use o Supervisor
Para instalar o supervisor:
sudo apt-get install supervisor
Depois crie um arquivo de configuração para o celery e coloque um link simbólico para esse arquivo no /etc/supervisor/conf.d/
[program:celery]
command=/home/deploy/.virtualenvs/meu_virtual_env/bin/celery --app=nome_proj worker --loglevel=INFO
directory=/home/deploy/webapps/projeto_django
user=nobody
autostart=true
autorestart=true
redirect_stderr=true
Para o supervisor ler novamente os arquivos que estão em sua pasta de configuração:
sudo supervisorctl reread
sudo supervisorctl update
Agora é só iniciar o celery:
sudo supervisorctl start celery
Bom, acho que é só isso. Esses são os slides da minha apresentação:
Continuação
Fontes
E aqui vão as fontes de pesquisa (ou algumas) que eu utilizei nesse processo:
Parabéns! Estava atrás de um artigo que ensinasse de forma simples e funcional.
Obrigado Thiago!
Uma aula e tanto de django e celery. Me mostrou o caminho para um projeto que estou atuando. Parabéns…
Valeu Flávio, obrigado cara. Você tem blog, vc sabe, o maior prazer de quem escreve é saber que o artigo serviu para mais alguém.
Um abraço!
Excelente artigo. Obrigado por compartilhar.