Python com Kafka e RabbitMQ: Mensageria na Prática
Entenda quando usar Kafka, RabbitMQ, Redis Streams ou filas simples em projetos Python, com exemplos práticos, arquitetura, retries, idempotência e portfólio.
Mensageria aparece cada vez mais em vagas Python no Brasil porque sistemas reais raramente fazem tudo dentro de uma única requisição HTTP. Uma API recebe um pedido, publica um evento, outro serviço envia email, um worker atualiza o estoque, um pipeline alimenta analytics e um processo separado recalcula score de risco. Sem uma fila ou broker de mensagens, esse fluxo vira uma sequência frágil de chamadas síncronas: se uma integração cai, a experiência do usuário fica lenta ou a operação perde dados.
Python combina bem com esse tipo de arquitetura. A linguagem é usada em APIs, automações, pipelines de dados, integrações de CRM, tarefas assíncronas, machine learning e sistemas internos. O ponto não é decorar uma ferramenta específica, e sim entender quando usar uma fila simples, quando usar RabbitMQ, quando Kafka faz sentido e quais cuidados evitam duplicidade, perda de mensagem e debug impossível.
Neste guia, vamos comparar Kafka, RabbitMQ, Redis Streams e filas simples para projetos Python. Também veremos exemplos com pika e confluent-kafka, desenho de arquitetura, padrões de retry, idempotência, observabilidade e ideias de portfólio. Se sua dúvida é tirar trabalho pesado de uma API FastAPI, leia também FastAPI Background Tasks, Celery e Redis, Python e Redis, Docker Compose com PostgreSQL e observabilidade com OpenTelemetry em Python.
O que é mensageria
Mensageria é a prática de enviar dados entre partes de um sistema usando mensagens. Em vez de um serviço chamar outro diretamente e esperar resposta imediata, ele publica uma mensagem em um intermediário. Esse intermediário pode ser RabbitMQ, Kafka, Redis Streams, SQS, Pub/Sub, NATS ou outro broker. Um consumidor lê a mensagem e executa o trabalho quando estiver pronto.
Um exemplo simples:
- a API recebe um cadastro;
- salva o usuário no banco;
- publica
usuario_criadoem uma fila ou tópico; - um worker envia email de boas-vindas;
- outro worker registra o evento para analytics;
- outro processo atualiza uma ferramenta de CRM.
Essa separação reduz acoplamento. A API não precisa conhecer todos os detalhes de email, analytics e CRM. Ela registra o fato importante e deixa consumidores especializados reagirem. O fluxo também melhora resiliência: se o serviço de email ficar fora do ar, a mensagem pode esperar retry sem derrubar o cadastro.
Fila, tópico e evento não são a mesma coisa
Em conversas de arquitetura, muita gente chama tudo de fila. Isso confunde decisões.
| Conceito | Ideia central | Exemplo |
|---|---|---|
| Fila | uma mensagem normalmente é processada por um consumidor do grupo | processar PDFs enviados por usuários |
| Tópico | vários consumidores podem receber o mesmo evento | pedido_pago consumido por estoque, nota fiscal e analytics |
| Evento | registro de algo que aconteceu no domínio | curriculo_enviado, pagamento_confirmado |
| Comando | pedido para alguém executar uma ação | enviar_email_confirmacao |
RabbitMQ costuma ser excelente para filas, roteamento e comandos assíncronos. Kafka brilha quando você tem tópicos com histórico, alto volume, múltiplos consumidores e necessidade de replay. Redis Streams pode ser uma alternativa intermediária quando você já usa Redis e precisa de grupos de consumidores com persistência básica. Uma tabela no banco com status também pode ser suficiente para produtos pequenos.
Quando usar RabbitMQ
RabbitMQ é uma escolha comum para tarefas assíncronas, workflows operacionais e comunicação entre serviços quando você quer filas confiáveis e roteamento flexível. Ele trabalha com exchanges, queues, bindings e acknowledgements.
Use RabbitMQ quando:
- cada mensagem deve ser processada por um worker;
- você precisa de filas por prioridade, tipo de tarefa ou time;
- retry e dead letter queue são importantes;
- o volume é moderado e o fluxo é mais transacional;
- você quer roteamento por chave, como
email.confirmacaooupagamento.falha.
Um exemplo de produtor com pika:
import json
import pika
def publicar_email_confirmacao(user_id: str, email: str) -> None:
connection = pika.BlockingConnection(pika.URLParameters("amqp://guest:guest@localhost:5672/"))
channel = connection.channel()
channel.queue_declare(queue="emails", durable=True)
mensagem = {"tipo": "email_confirmacao", "user_id": user_id, "email": email}
channel.basic_publish(
exchange="",
routing_key="emails",
body=json.dumps(mensagem).encode("utf-8"),
properties=pika.BasicProperties(delivery_mode=2),
)
connection.close()
O delivery_mode=2 pede persistência da mensagem. Isso não elimina todos os riscos, mas evita tratar a fila como memória temporária. Em produção, você também configuraria usuário próprio, TLS, heartbeat, retry de conexão e observabilidade.
O consumidor básico:
import json
import pika
def processar(ch, method, properties, body):
mensagem = json.loads(body)
enviar_email(mensagem["email"], mensagem["user_id"])
ch.basic_ack(delivery_tag=method.delivery_tag)
connection = pika.BlockingConnection(pika.URLParameters("amqp://guest:guest@localhost:5672/"))
channel = connection.channel()
channel.queue_declare(queue="emails", durable=True)
channel.basic_qos(prefetch_count=10)
channel.basic_consume(queue="emails", on_message_callback=processar)
channel.start_consuming()
O basic_ack só deve acontecer depois do trabalho importante terminar. Se você confirmar antes e o worker cair no meio, a mensagem pode ser perdida. Se você nunca confirmar, a mensagem pode voltar para a fila e ser processada novamente. Por isso idempotência é obrigatória.
Quando usar Kafka
Kafka é mais do que uma fila. Ele funciona como um log distribuído de eventos. Produtores publicam mensagens em tópicos, consumidores leem em grupos, offsets indicam até onde cada grupo avançou e mensagens podem ser retidas por tempo ou tamanho. Isso permite replay: um novo consumidor pode reler eventos antigos para reconstruir estado, alimentar analytics ou recalcular uma projeção.
Use Kafka quando:
- o mesmo evento precisa alimentar vários consumidores independentes;
- você precisa de histórico e replay;
- o volume de eventos é alto;
- pipelines de dados e analytics fazem parte do produto;
- a ordem por chave, como
pedido_id, importa; - você aceita operar uma ferramenta mais complexa.
Um produtor com confluent-kafka:
import json
from confluent_kafka import Producer
producer = Producer({"bootstrap.servers": "localhost:9092"})
def publicar_pedido_pago(pedido_id: str, valor: float) -> None:
evento = {"tipo": "pedido_pago", "pedido_id": pedido_id, "valor": valor}
producer.produce(
"pedidos",
key=pedido_id,
value=json.dumps(evento).encode("utf-8"),
)
producer.flush()
A chave (key) ajuda Kafka a enviar eventos do mesmo pedido para a mesma partição, preservando ordem relativa dentro daquela chave. Isso é útil para fluxos como pedido_criado, pedido_pago, pedido_cancelado.
Um consumidor:
import json
from confluent_kafka import Consumer
consumer = Consumer(
{
"bootstrap.servers": "localhost:9092",
"group.id": "analytics-pedidos",
"auto.offset.reset": "earliest",
"enable.auto.commit": False,
}
)
consumer.subscribe(["pedidos"])
while True:
msg = consumer.poll(1.0)
if msg is None:
continue
if msg.error():
raise RuntimeError(msg.error())
evento = json.loads(msg.value())
salvar_evento_analytics(evento)
consumer.commit(msg)
Desabilitar commit automático deixa claro quando o offset avança: depois que o processamento relevante terminou. Ainda assim, consumidores Kafka também precisam ser idempotentes. Em falhas, a mesma mensagem pode ser lida mais de uma vez.
Redis Streams e filas simples
Redis aparece bastante em projetos Python por causa de cache, rate limit e filas simples. Para filas muito básicas, listas com LPUSH e BRPOP resolvem protótipos. Para algo mais estruturado, Redis Streams oferece stream persistente, consumer groups e acknowledgements.
Redis Streams pode fazer sentido quando:
- você já opera Redis;
- o volume é moderado;
- precisa de grupos de consumidores;
- quer menos complexidade operacional que Kafka;
- aceita limitações de retenção, replay e ecossistema.
Mas cuidado: Redis não deve virar banco principal disfarçado. Se a mensagem representa dinheiro, contrato, estoque ou outro fato crítico, grave também em banco transacional ou use um broker com estratégia operacional clara.
Para casos mínimos, uma tabela jobs no PostgreSQL pode ser melhor que introduzir broker cedo demais. A API insere um job com status = 'pendente', um worker busca pendentes com lock, processa, atualiza status e registra erro. É menos elegante, mas muito auditável e suficiente para muitos produtos internos.
Comparação rápida
| Ferramenta | Melhor uso | Custo operacional | Replay | Observação |
|---|---|---|---|---|
| BackgroundTasks | tarefas leves no mesmo processo | baixo | não | bom para notificações não críticas |
| Celery + Redis/RabbitMQ | tarefas assíncronas em workers | médio | limitado | ótimo para web apps Python |
| RabbitMQ | filas e roteamento transacional | médio | limitado | forte para comandos e retry |
| Kafka | eventos, streams e analytics | alto | sim | forte para escala e múltiplos consumidores |
| Redis Streams | fila persistente intermediária | médio | parcial | útil se Redis já existe |
| Tabela no banco | jobs auditáveis simples | baixo | por consulta | subestimada para produtos pequenos |
Não escolha Kafka só porque parece mais sênior. Se o produto tem dez tarefas por minuto e um único consumidor, RabbitMQ, Celery ou uma tabela de jobs podem ser mais saudáveis. Use Kafka quando o problema realmente pede log de eventos, múltiplos consumidores, retenção e escala.
Idempotência: o detalhe que separa demo de produção
Em mensageria, assuma que uma mensagem pode ser processada mais de uma vez. Rede cai, worker reinicia, commit falha, broker reentrega, deploy interrompe processo. O consumidor precisa produzir o mesmo resultado quando recebe a mesma mensagem novamente.
Algumas práticas:
- inclua
event_idúnico em toda mensagem importante; - grave eventos processados em uma tabela com chave única;
- use operações idempotentes, como
upsert; - não cobre duas vezes se o mesmo
pagamento_idreaparecer; - se enviar email, registre envio por template e destinatário antes de repetir;
- se chamar API externa, use idempotency key quando disponível.
Exemplo simples com banco:
def processar_pagamento(evento):
if evento_ja_processado(evento["event_id"]):
return
confirmar_pagamento(evento["pedido_id"], evento["valor"])
marcar_evento_processado(evento["event_id"])
Em projeto real, essa marcação deve estar na mesma transação das mudanças importantes sempre que possível. Caso contrário, você troca duplicidade por outro tipo de inconsistência.
Retry, dead letter queue e backoff
Falhas transitórias são normais: timeout em API externa, banco reiniciando, limite de rate, DNS instável. Reprocessar imediatamente em loop pode piorar tudo. O ideal é usar retry com backoff e limite.
Uma política comum:
- primeira falha: retry rápido;
- falhas seguintes: esperar 30 segundos, 2 minutos, 10 minutos;
- depois do limite: enviar para dead letter queue;
- registrar erro com contexto suficiente;
- criar alerta quando a fila morta cresce.
Dead letter queue não é lixeira. É uma fila de investigação. Ela guarda mensagens que não puderam ser processadas automaticamente. O time precisa de painel, logs e procedimento para corrigir dados, reprocessar ou descartar com justificativa.
Contrato de mensagem
Mensagem sem contrato vira acoplamento invisível. Se um produtor muda valor de número para string, um consumidor pode quebrar horas depois. Para evitar isso, defina um formato explícito.
Um evento simples:
{
"event_id": "evt_01HX...",
"event_type": "pedido_pago",
"occurred_at": "2026-06-04T11:00:00Z",
"schema_version": 1,
"payload": {
"pedido_id": "ped_123",
"cliente_id": "cli_456",
"valor": 199.9,
"moeda": "BRL"
}
}
Com Python, você pode validar esse contrato com Pydantic:
from datetime import datetime
from pydantic import BaseModel
class PedidoPagoPayload(BaseModel):
pedido_id: str
cliente_id: str
valor: float
moeda: str = "BRL"
class EventoPedidoPago(BaseModel):
event_id: str
event_type: str
occurred_at: datetime
schema_version: int
payload: PedidoPagoPayload
Validar mensagem na borda do consumidor economiza horas de debug. Se o contrato muda, incremente schema_version e mantenha compatibilidade enquanto consumidores antigos ainda existem.
Arquitetura para portfólio Python
Um projeto de portfólio convincente não precisa simular uma big tech. Ele precisa demonstrar decisões profissionais. Uma boa ideia:
api-vagas-python/
app/
main.py
settings.py
events.py
worker/
enviar_alertas.py
atualizar_metricas.py
tests/
compose.yaml
pyproject.toml
README.md
Fluxo:
- a API recebe uma nova vaga Python;
- salva no PostgreSQL;
- publica
vaga_publicadaem RabbitMQ; - um worker envia alerta para assinantes interessados;
- outro worker atualiza métricas por tecnologia;
- logs registram
event_id,job_ide tempo de processamento.
Esse projeto conversa diretamente com vagas que pedem Python, FastAPI, PostgreSQL, Docker, RabbitMQ, Kafka, Redis, observabilidade e testes. Para fortalecer, inclua testes de unidade para contrato de mensagem, um teste de integração simples e um README com diagrama do fluxo.
Cuidados de segurança e LGPD
Mensagens costumam carregar dados sensíveis sem que o time perceba. Não publique CPF, token, senha, endereço completo, dados bancários ou conteúdo pessoal em tópico compartilhado sem necessidade clara. Quanto mais consumidores têm acesso a um tópico, maior a superfície de vazamento.
Boas práticas:
- envie identificadores, não dados completos, quando possível;
- masque dados sensíveis nos logs;
- use TLS e autenticação no broker;
- separe ambientes de dev, staging e produção;
- controle permissões por tópico ou fila;
- defina retenção adequada;
- documente quem consome cada evento.
Essa disciplina vale para projetos pequenos também. Portfólio com dado fictício e cuidado explícito passa muito mais confiança do que demo que imprime segredo no terminal.
Observabilidade para filas e eventos
Quando uma requisição síncrona falha, o usuário normalmente vê erro. Quando uma mensagem falha, o problema pode ficar escondido. Por isso mensageria exige observabilidade.
Monitore pelo menos:
- tamanho da fila ou lag do consumer group;
- taxa de mensagens publicadas e consumidas;
- tempo médio de processamento;
- quantidade de retries;
- mensagens na dead letter queue;
- erros por tipo de evento;
- idade da mensagem mais antiga pendente.
Nos logs, inclua event_id, event_type, consumer, attempt, status e duração. Se usar OpenTelemetry, propague trace context quando possível. Isso conecta a requisição original ao processamento assíncrono e facilita entender onde o fluxo travou.
Erros comuns
Alguns erros aparecem repetidamente em projetos Python com mensageria:
- confirmar mensagem antes de terminar o trabalho;
- não ter idempotência;
- usar Kafka para tarefa simples de email;
- colocar payload gigante na mensagem em vez de referência para storage;
- não versionar contrato;
- logar dados sensíveis;
- não ter dead letter queue;
- ignorar lag e descobrir atraso só por reclamação de usuário;
- misturar evento de domínio com comando operacional;
- não documentar ordem esperada dos eventos.
Evitar esses pontos já coloca seu projeto acima da maioria das demos. O objetivo não é construir arquitetura perfeita, mas deixar claro quais falhas você antecipou.
Como escolher no seu projeto
Use esta regra prática:
| Situação | Escolha provável |
|---|---|
| enviar email simples depois da resposta HTTP | BackgroundTasks ou Celery |
| processar arquivos, PDFs ou planilhas com status | Celery, RabbitMQ ou tabela de jobs |
| vários serviços reagem ao mesmo evento de negócio | Kafka ou RabbitMQ com fanout |
| analytics precisa reler eventos antigos | Kafka |
| produto pequeno precisa de auditoria simples | tabela no PostgreSQL |
| cache, rate limit e fila leve no mesmo stack | Redis ou Redis Streams |
Se estiver estudando para backend Python, comece com RabbitMQ ou Celery porque o ciclo mental fica mais direto: produzir tarefa, consumir, confirmar, retry, dead letter. Depois avance para Kafka quando estudar eventos, streaming, data engineering e analytics.
Próximos passos
Mensageria é uma habilidade prática, não apenas tema de arquitetura. Para aprender de verdade, crie um projeto pequeno com Docker Compose, uma API FastAPI, PostgreSQL, RabbitMQ ou Kafka, dois consumidores e logs estruturados. Escreva no README como lidar com duplicidade, retry e falha de consumidor. Isso demonstra maturidade para vagas de backend, dados, DevOps e automação.
Para continuar, aprofunde em FastAPI Background Tasks, Celery e Redis, Python e Redis, OpenTelemetry em Python, testes com pytest e deploy de aplicações Python. Se quiser comparar com outra linguagem usada em alta concorrência, veja também o conteúdo sobre worker pools e filas em Go.
Equipe Python Brasil
Contribuidor do Python Brasil — Aprenda Python em Português