Entenda o Celery: a biblioteca Python para filas de tarefas

Entenda o Celery: a biblioteca Python para filas de tarefas

Celery? Salsão? Vamos começar a falar de plantas agora na 4Linux?

Não, não. Outro Celery. A biblioteca do Python!

Aah, sim… É uma biblioteca do Python… É sobre jardinagem?

Olha, eu apreciaria se fosse! Jardinagem é um dos meus interesses, afinal de contas. 🙂

Mas vamos falar sobre Celery. O que é, para que serve, quando usar?

Um pouco sobre a ferramenta

Celery é uma ferramenta que nos possibilita criar filas de tarefas – do inglês task queues. E filas de tarefas nada mais são do que um mecanismo que nos permite distribuir tais tarefas entre threads ou processos diferentes. Ou seja, a execução dessas tarefas ocorre de forma independente do nosso programa e, dessa forma, podemos mandar o computador executar uma operação custosa pelo Celery em, por exemplo, um banco de dados, enquanto o nosso programa prossegue com o roteiro. Mais adiante, quando precisamos do resultado daquela operação, pedimos à task que o dê a nós.

Para quem já conhece ou faz uso de programação assíncrona, o Celery funciona como (mas não necessariamente é) uma abstração dela.

O Celery provê alta disponibilidade, sendo capaz de tentar reconexões em caso de falhas. Além disso, ele é flexível, podendo ser usado por si só ou estendido com serializadores, ferramentas de logging, variados mediadores (brokers) e agendadores. Também é possível integrar a ferramenta com frameworks web como Django, Flask, web2py, Pyramid e outros. Outra coisa que simplifica bastante o desenvolvimento com Celery é que ele não precisa de arquivos de configuração para ser usado: basta executar o worker passando a nossa aplicação e ele consegue autonomamente se configurar. Claro, não significa que setups mais complicados dispensem uma configuração básica.

Como funciona?

Para funcionar, o Celery precisa de pelo menos um mediador (broker) onde informações sobre as tarefas serão enfileiradas. As recomendações primárias presentes em seu site oficial são RabbitMQ e Redis, mas é possível usar até SQLite para desenvolvimento local. O Celery pode funcionar em modo multi-thread ou multi-process, e é possível subir múltiplos workers para lidar com múltiplas tarefas.

Mãos à obra!

Usaremos Docker para subir um container que executará o Redis e usá-lo como broker na nossa aplicação. Também usaremos um ambiente virtual do Python para instalar as nossas dependências. Comecemos por este.

Criei um diretório chamado app. Depois entre nele e crie os arquivos requirements.txt e main.py.

$ mkdir app
$ cd app
$ touch requirements.txt main.py

Em seguida, coloque as dependências redis e celery no arquivo requirements:

$ cat >> requirements.txt <<!
redis
celery
!

E agora crie e ative o ambiente virtual para instalar essas dependências:

$ python3 -m venv .venv
$ . .venv/bin/activate
$ pip install -r requirements.txt

O ambiente Python já está praticamente pronto. Agora suba o Redis via Docker.

$ docker run --name celery-app-redis -dp 6379:6379 redis:alpine

Com o Redis funcionando, você pode escrever o nosso código Python. Vamos abrir o arquivo main.py e criar nossa primeira task.

import celery  # A

REDIS_HOST = "redis://localhost:6379/0"  # B

# C
app = celery.Celery('main',
                    backend=REDIS_HOST,
                    broker=REDIS_HOST)

@app.task  # D
def add(x, y):
    return x + y

if __name__ == "__main__":
    print(add(1, 2))  # E

    # F
    task = add.delay(2, 3)
    print(task.get())

No ponto A temos a instrução de importação do celery; não conseguimos usá-lo antes disso. No ponto B definimos a nossa constante REDIS_HOST, que só aponta para a nossa instância do Redis rodando via Docker. Nós o usamos como tanto backend quanto broker – o broker é o que enfileira as tarefas e o backend é onde os resultados ficam salvos – no ponto C, que é onde instanciamos um objeto Celery. Passamos "main" como primeiro argumento para que o Celery consiga contextualizar as tarefas usando esse nome como namespace. No ponto D definimos a nossa função add como uma task do Celery usando o decorator app.task. (Quem conhece Flask já deve ter reconhecido a similaridade com a criação de rotas.) No ponto E podemos ver como é possível chamar a função normalmente ao apenas escrever os parênteses e passar os argumentos. Finalmente, no ponto F, chamamos o método .delay passando os argumentos da função e atribuindo o resultado, que é um AsyncResult do Celery. Podemos realizar outras operações depois de chamar .delay e, quando quisermos o resultado da tarefa, chamamos .get para obtê-lo. Vamos ver funcionando?

Primeiro, vamos revisar a a estrutura do projeto de teste:

$ tree
app
├── main.py
└── requirements.txt

Ou seja, estamos dentro da pasta app temos os arquivos main.py e requirements.txt, que editamos e usamos anteriormente. Também estamos com o ambiente virtual ativado. Neste ponto, precisamos digitar no terminal:

$ celery -A main worker -l INFO

Em que:

  • celery é o executável que rodará nosso processo que cuidará das tasks;
  • -A main é a opção que definimos para dizer ao Celery que a -Aplicação se encontra no módulo main;
  • worker é o subcomando que diz ao Celery para executar uma única instância de worker, que é uma entidade responsável por monitorar a fila de tarefas e executar quaisquer novas tarefas que nosso programa colocar nela;
  • -l INFO é a opção do worker que define o nível de log para INFO (mensagens informacionais).

Ao executar, o Celery  mostra algumas informações de inicialização e diz que está pronto para receber as tasks:

[tasks]
  . main.add

[2021-08-03 15:53:11,670: INFO/MainProcess] Connected to redis://localhost:6379/0
[2021-08-03 15:53:11,677: INFO/MainProcess] mingle: searching for neighbors
[2021-08-03 15:53:12,693: INFO/MainProcess] mingle: all alone
[2021-08-03 15:53:12,703: INFO/MainProcess] celery@4rchlinux ready.

Em outro terminal, também com o ambiente virtual ativado, apenas executamos o nosso programa:

$ python main.py 
3
5

E a saída do Celery:

[2021-08-03 16:01:27,060: INFO/MainProcess] Task main.add[ID_DA_TAREFA] received
[2021-08-03 16:01:27,065: INFO/ForkPoolWorker-4] Task main.add[ID_DA_TAREFA] succeeded in 0.0038968220001152076s: 5

Perceba que, onde está escrito ID_DA_TAREFA, deve haver um ID único no formato UUID que o Celery criou.

Vemos que a primeira saída do nosso programa é 3. Esta é a saída da chamada a add(1, 2) no ponto E do nosso programa. Depois, no ponto F, quando chamamos add.delay(2, 3), o nosso worker do Celery recebe a tarefa e prossegue a executá-la, imprimindo no terminal o resultado. Esse resultado é o mesmo que conseguimos quando chamamos task.get() no nosso programa.

Como mencionamos anteriormente, é possível realizarmos outras operações enquanto as nossas tarefas são executadas pelo Celery. Vamos colocar um “custo” de 5 segundos na nossa função:

from time import sleep

# resto do código aqui...

@app.task
def add(x, y):
    sleep(5)
    return x + y

if __name__ == "__main__":
    task = add.delay(2, 3)
    print("fazendo outras coisas enquanto a tarefa é executada")
    print("custo de 2 segundos")
    sleep(2)
    print("chamando get:")
    print(task.get())

Nota: é necessário reiniciar o worker do Celery para que as alterações sejam aplicadas.

Executamos e vemos que, de fato, o Celery possibilita continuar com o fluxo normal do programa enquanto ele se encarrega de executar as tarefas no background.

A partir disso, é só deixar a imaginação fluir e pensar nos cenários onde é possível ou necessário usar a ferramenta.

Quer saber mais sobre ela? Acessa a documentação oficial! Lá eles explicam de tudo. 🙂

 

Líder em Treinamento e serviços de Consultoria, Suporte e Implantação para o mundo open source. Conheça nossas soluções:

CURSOSCONSULTORIA

Anterior Rocket.Chat App: Criando seu primeiro aplicativo
Próxima Melhore a performance do seu banco Postgres com o PGbadger

About author

Você pode gostar também

DevOps

GitLab CI – Integração Contínua sem sair do repositório

Ferramentas de CI/CD hoje em dia andam de mãos dadas com os nossos projetos – dificilmente vemos um repositório no GitHub, GitLab, Bitbucket, Gitea… sem alguma configuração de integração contínua.

Desenvolvimento

Automatização de ambientes com Rundeck

Por que automatizar seu ambiente? Nos últimos anos, a automação tem crescido como um elemento imperativo em todos os negócios. Independente da área, temos observado um notável aumento no uso

DevOps

Como usar Linux no Windows sem Dual Boot: Guia Prático do WSL

Se você está começando seus estudos no linux e tem receio de realizar um dual boot em seu computador e correr o risco de perder todos seus arquivos e ou